update
This commit is contained in:
parent
012771fccc
commit
cb6e0dd7e9
|
@ -195,9 +195,7 @@ this.engine.on('mention:render-item', (data, root) => {
|
|||
`mention:loading`: custom rendering loading status
|
||||
|
||||
```ts
|
||||
this.engine.on('mention:loading', (data, root) => {
|
||||
root.html(`<div>${data}</div>`);
|
||||
// or
|
||||
this.engine.on('mention:loading', (root) => {
|
||||
ReactDOM.render(
|
||||
<div className="data-mention-loading">Loading...</div>,
|
||||
root.get<HTMLElement>()!,
|
||||
|
|
|
@ -195,9 +195,7 @@ this.engine.on('mention:render-item', (data, root) => {
|
|||
`mention:loading`: 自定渲染加载状态
|
||||
|
||||
```ts
|
||||
this.engine.on('mention:loading', (data, root) => {
|
||||
root.html(`<div>${data}</div>`);
|
||||
// or
|
||||
this.engine.on('mention:loading', (root) => {
|
||||
ReactDOM.render(
|
||||
<div className="data-mention-loading">Loading...</div>,
|
||||
root.get<HTMLElement>()!,
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
ResizeInterface,
|
||||
CardValue,
|
||||
} from '../types/card';
|
||||
import { EditorInterface } from '../types/engine';
|
||||
import { EditorInterface } from '../types/editor';
|
||||
import { NodeInterface } from '../types/node';
|
||||
import { RangeInterface } from '../types/range';
|
||||
import { ToolbarItemOptions } from '../types/toolbar';
|
||||
|
@ -31,7 +31,9 @@ import { $ } from '../node';
|
|||
import { CardType, SelectStyleType } from './enum';
|
||||
import { DATA_ELEMENT, UI } from '../constants';
|
||||
|
||||
abstract class CardEntry<T extends CardValue = {}> implements CardInterface<T> {
|
||||
abstract class CardEntry<T extends CardValue = CardValue>
|
||||
implements CardInterface<T>
|
||||
{
|
||||
protected readonly editor: EditorInterface;
|
||||
readonly root: NodeInterface;
|
||||
toolbarModel?: CardToolbarInterface;
|
||||
|
@ -323,6 +325,10 @@ abstract class CardEntry<T extends CardValue = {}> implements CardInterface<T> {
|
|||
else this.root.removeClass(className);
|
||||
return center;
|
||||
}
|
||||
onSelectLeft?(event: KeyboardEvent): boolean | void;
|
||||
onSelectRight?(event: KeyboardEvent): boolean | void;
|
||||
onSelectUp?(event: KeyboardEvent): boolean | void;
|
||||
onSelectDown?(event: KeyboardEvent): boolean | void;
|
||||
onActivate(activated: boolean) {
|
||||
if (!this.resize) return;
|
||||
if (activated) this.resizeModel?.show();
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from '../types/card';
|
||||
import { NodeInterface } from '../types/node';
|
||||
import { RangeInterface } from '../types/range';
|
||||
import { EditorInterface } from '../types/engine';
|
||||
import { EditorInterface } from '../types/editor';
|
||||
import {
|
||||
decodeCardValue,
|
||||
encodeCardValue,
|
||||
|
@ -104,7 +104,7 @@ class CardModel implements CardModelInterface {
|
|||
cards.forEach((card) => {
|
||||
this.classes[card.cardName] = card;
|
||||
});
|
||||
|
||||
if (!this.lazyRender) return;
|
||||
window.addEventListener('resize', this.renderAsyncComponents);
|
||||
this.editor.scrollNode
|
||||
?.get<HTMLElement>()
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { NodeInterface } from '../../types/node';
|
||||
import { CardInterface, MaximizeInterface } from '../../types/card';
|
||||
import { EditorInterface } from '../../types/engine';
|
||||
import {
|
||||
CardInterface,
|
||||
MaximizeInterface,
|
||||
EditorInterface,
|
||||
NodeInterface,
|
||||
} from '../../types';
|
||||
import { $ } from '../../node';
|
||||
import { DATA_ELEMENT, DATA_TRANSIENT_ELEMENT, UI } from '../../constants';
|
||||
import { isEngine } from '../../utils';
|
||||
|
|
|
@ -63,7 +63,7 @@ class Resize implements ResizeInterface {
|
|||
if (start) {
|
||||
this.card.setValue({
|
||||
height: container.height(),
|
||||
});
|
||||
} as any);
|
||||
start = false;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import Toolbar, { Tooltip } from '../../toolbar';
|
||||
import {
|
||||
import type {
|
||||
EditorInterface,
|
||||
CardEntry,
|
||||
CardInterface,
|
||||
CardToolbarInterface,
|
||||
CardToolbarItemOptions,
|
||||
} from '../../types/card';
|
||||
import {
|
||||
ToolbarItemOptions,
|
||||
ToolbarInterface as ToolbarBaseInterface,
|
||||
} from '../../types/toolbar';
|
||||
import { EditorInterface } from '../../types/engine';
|
||||
} from '../../types';
|
||||
import { DATA_ELEMENT, TRIGGER_CARD_ID, UI } from '../../constants';
|
||||
import { $ } from '../../node';
|
||||
import { isEngine, isMobile } from '../../utils';
|
||||
|
|
|
@ -28,16 +28,28 @@ class Down {
|
|||
}
|
||||
|
||||
trigger(event: KeyboardEvent) {
|
||||
const { change } = this.engine;
|
||||
const { change, block, card } = this.engine;
|
||||
const range = change.range.get();
|
||||
const card = this.engine.card.getSingleCard(range);
|
||||
if (!card) return true;
|
||||
const singleCard = card.getSingleCard(range);
|
||||
if (!singleCard) {
|
||||
if (range.collapsed) {
|
||||
const closetBlock = block.closest(range.startNode);
|
||||
const next = closetBlock.next();
|
||||
if (next?.isCard()) {
|
||||
const cardComponent = card.find(next);
|
||||
if (cardComponent && cardComponent.onSelectDown) {
|
||||
return cardComponent.onSelectDown(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (isHotkey('shift+down', event)) {
|
||||
return true;
|
||||
}
|
||||
return card.type === CardType.INLINE
|
||||
? this.inline(card, event)
|
||||
: this.block(card, event);
|
||||
return singleCard.type === CardType.INLINE
|
||||
? this.inline(singleCard, event)
|
||||
: this.block(singleCard, event);
|
||||
}
|
||||
}
|
||||
export default Down;
|
||||
|
|
|
@ -37,6 +37,11 @@ class Left {
|
|||
event.preventDefault();
|
||||
if (isCenter) {
|
||||
card.select(false);
|
||||
} else if (range.collapsed) {
|
||||
const cardComponent = this.engine.card.find(range.startNode);
|
||||
if (cardComponent && cardComponent.onSelectLeft) {
|
||||
return cardComponent.onSelectLeft(event);
|
||||
}
|
||||
}
|
||||
if (!isCenter && singleSelectable !== false) {
|
||||
this.engine.card.select(card);
|
||||
|
@ -63,6 +68,12 @@ class Left {
|
|||
// 右侧光标
|
||||
const cardRight = range.commonAncestorNode.closest(CARD_RIGHT_SELECTOR);
|
||||
if (cardRight.length > 0) {
|
||||
if (range.collapsed) {
|
||||
const cardComponent = card.find(range.startNode);
|
||||
if (cardComponent && cardComponent.onSelectLeft) {
|
||||
return cardComponent.onSelectLeft(event);
|
||||
}
|
||||
}
|
||||
event.preventDefault();
|
||||
card.select(component);
|
||||
return false;
|
||||
|
|
|
@ -21,6 +21,11 @@ class Right {
|
|||
event.preventDefault();
|
||||
if (isCenter) {
|
||||
card.select(false);
|
||||
} else if (range.collapsed) {
|
||||
const cardComponent = this.engine.card.find(range.startNode);
|
||||
if (cardComponent && cardComponent.onSelectRight) {
|
||||
return cardComponent.onSelectRight(event);
|
||||
}
|
||||
}
|
||||
if (!isCenter && singleSelectable !== false) {
|
||||
this.engine.card.select(card);
|
||||
|
@ -51,6 +56,12 @@ class Right {
|
|||
// 左侧光标
|
||||
const cardLeft = range.commonAncestorNode.closest(CARD_LEFT_SELECTOR);
|
||||
if (cardLeft.length > 0) {
|
||||
if (range.collapsed) {
|
||||
const cardComponent = this.engine.card.find(range.startNode);
|
||||
if (cardComponent && cardComponent.onSelectRight) {
|
||||
return cardComponent.onSelectRight(event);
|
||||
}
|
||||
}
|
||||
event.preventDefault();
|
||||
card.select(component);
|
||||
return false;
|
||||
|
|
|
@ -27,16 +27,28 @@ class Up {
|
|||
}
|
||||
|
||||
trigger(event: KeyboardEvent) {
|
||||
const { change } = this.engine;
|
||||
const { change, card, block } = this.engine;
|
||||
const range = change.range.get();
|
||||
const card = this.engine.card.getSingleCard(range);
|
||||
if (!card) return true;
|
||||
const singleCard = card.getSingleCard(range);
|
||||
if (!singleCard) {
|
||||
if (range.collapsed) {
|
||||
const closetBlock = block.closest(range.startNode);
|
||||
const prev = closetBlock.prev();
|
||||
if (prev?.isCard()) {
|
||||
const cardComponent = card.find(prev);
|
||||
if (cardComponent && cardComponent.onSelectUp) {
|
||||
return cardComponent.onSelectUp(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (isHotkey('shift+up', event)) {
|
||||
return;
|
||||
}
|
||||
return card.type === CardType.INLINE
|
||||
? this.inline(card, event)
|
||||
: this.block(card, event);
|
||||
return singleCard.type === CardType.INLINE
|
||||
? this.inline(singleCard, event)
|
||||
: this.block(singleCard, event);
|
||||
}
|
||||
}
|
||||
export default Up;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import copyTo from 'copy-to-clipboard';
|
||||
import Parser from './parser';
|
||||
import { ClipboardInterface } from './types/clipboard';
|
||||
import { EditorInterface, EngineInterface } from './types/engine';
|
||||
import { EditorInterface, EngineInterface, ClipboardInterface } from './types';
|
||||
import { RangeInterface } from './types/range';
|
||||
import { isEngine, isSafari } from './utils';
|
||||
import { $ } from './node';
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { isMarkPlugin } from './plugin';
|
||||
import { ChangeInterface } from './types';
|
||||
import { CommandInterface } from './types/command';
|
||||
import { EditorInterface } from './types/engine';
|
||||
import { ChangeInterface, EditorInterface, CommandInterface } from './types';
|
||||
import { isEngine } from './utils';
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
import { merge } from 'lodash';
|
||||
import Language from './language';
|
||||
import {
|
||||
BlockModelInterface,
|
||||
CardEntry,
|
||||
CardInterface,
|
||||
CardModelInterface,
|
||||
CardValue,
|
||||
ClipboardInterface,
|
||||
CommandInterface,
|
||||
ConversionInterface,
|
||||
EditorInterface,
|
||||
EditorOptions,
|
||||
EventInterface,
|
||||
EventListener,
|
||||
InlineModelInterface,
|
||||
LanguageInterface,
|
||||
MarkModelInterface,
|
||||
NodeIdInterface,
|
||||
NodeInterface,
|
||||
NodeModelInterface,
|
||||
PluginEntry,
|
||||
PluginModelInterface,
|
||||
RequestInterface,
|
||||
SchemaInterface,
|
||||
Selector,
|
||||
} from './types';
|
||||
import { ListModelInterface } from './types/list';
|
||||
import language from './locales';
|
||||
import NodeModel, { $, Event } from './node';
|
||||
import Command from './command';
|
||||
import Plugin from './plugin';
|
||||
import Schema from './schema';
|
||||
import schemaDefaultData from './constants/schema';
|
||||
import Conversion from './parser/conversion';
|
||||
import conversionDefault from './constants/conversion';
|
||||
import CardModel from './card';
|
||||
import NodeId from './node/id';
|
||||
import Clipboard from './clipboard';
|
||||
import Request from './request';
|
||||
import List from './list';
|
||||
import Mark from './mark';
|
||||
import Inline from './inline';
|
||||
import Block from './block';
|
||||
|
||||
class Editor<T extends EditorOptions = EditorOptions>
|
||||
implements EditorInterface<T>
|
||||
{
|
||||
readonly kind: 'editor' | 'engine' | 'view' = 'editor';
|
||||
options: T = {
|
||||
lang: 'zh-CN',
|
||||
locale: {},
|
||||
plugins: [] as PluginEntry[],
|
||||
cards: [] as CardEntry[],
|
||||
config: {},
|
||||
} as T;
|
||||
readonly container: NodeInterface;
|
||||
|
||||
language: LanguageInterface;
|
||||
root: NodeInterface;
|
||||
card: CardModelInterface;
|
||||
plugin: PluginModelInterface;
|
||||
node: NodeModelInterface;
|
||||
nodeId: NodeIdInterface;
|
||||
list: ListModelInterface;
|
||||
mark: MarkModelInterface;
|
||||
inline: InlineModelInterface;
|
||||
block: BlockModelInterface;
|
||||
event: EventInterface;
|
||||
schema: SchemaInterface;
|
||||
conversion: ConversionInterface;
|
||||
command: CommandInterface;
|
||||
clipboard: ClipboardInterface;
|
||||
request: RequestInterface;
|
||||
#_scrollNode: NodeInterface | null = null;
|
||||
|
||||
get scrollNode(): NodeInterface | null {
|
||||
if (this.#_scrollNode) return this.#_scrollNode;
|
||||
const { scrollNode } = this.options;
|
||||
let sn = scrollNode
|
||||
? typeof scrollNode === 'function'
|
||||
? scrollNode()
|
||||
: scrollNode
|
||||
: null;
|
||||
// 查找父级样式 overflow 或者 overflow-y 为 auto 或者 scroll 的节点
|
||||
const targetValues = ['auto', 'scroll'];
|
||||
let parent = this.container.parent();
|
||||
while (!sn && parent && parent.length > 0 && parent.name !== 'body') {
|
||||
if (
|
||||
targetValues.includes(parent.css('overflow')) ||
|
||||
targetValues.includes(parent.css('overflow-y'))
|
||||
) {
|
||||
sn = parent.get<HTMLElement>();
|
||||
break;
|
||||
} else {
|
||||
parent = parent.parent();
|
||||
}
|
||||
}
|
||||
if (sn === null) sn = document.documentElement;
|
||||
this.#_scrollNode = sn ? $(sn) : null;
|
||||
return this.#_scrollNode;
|
||||
}
|
||||
|
||||
constructor(selector: Selector, options?: EditorOptions) {
|
||||
this.options = { ...this.options, ...options };
|
||||
this.container = $(selector);
|
||||
// 多语言
|
||||
this.language = new Language(
|
||||
this.options.lang || 'zh-CN',
|
||||
merge(language, options?.locale),
|
||||
);
|
||||
// 事件管理
|
||||
this.event = new Event();
|
||||
// 命令
|
||||
this.command = new Command(this);
|
||||
// 节点规则
|
||||
this.schema = new Schema();
|
||||
this.schema.add(schemaDefaultData);
|
||||
// 节点转换规则
|
||||
this.conversion = new Conversion(this);
|
||||
conversionDefault.forEach((rule) =>
|
||||
this.conversion.add(rule.from, rule.to),
|
||||
);
|
||||
// 卡片
|
||||
this.card = new CardModel(this, this.options.lazyRender);
|
||||
// 剪贴板
|
||||
this.clipboard = new Clipboard(this);
|
||||
// http请求
|
||||
this.request = new Request();
|
||||
// 插件
|
||||
this.plugin = new Plugin(this);
|
||||
// 节点管理
|
||||
this.node = new NodeModel(this);
|
||||
this.nodeId = new NodeId(this);
|
||||
// 列表
|
||||
this.list = new List(this);
|
||||
// 样式标记
|
||||
this.mark = new Mark(this);
|
||||
// 行内样式
|
||||
this.inline = new Inline(this);
|
||||
// 块级节点
|
||||
this.block = new Block(this);
|
||||
// 编辑器父节点
|
||||
this.root = $(
|
||||
this.options.root || this.container.parent() || document.body,
|
||||
);
|
||||
const rootPosition = this.root.css('position');
|
||||
if (!rootPosition || rootPosition === 'static')
|
||||
this.root.css('position', 'relative');
|
||||
}
|
||||
|
||||
init() {
|
||||
// 实例化插件
|
||||
this.mark.init();
|
||||
this.inline.init();
|
||||
this.block.init();
|
||||
this.list.init();
|
||||
this.card.init(this.options.cards || []);
|
||||
this.plugin.init(this.options.plugins || [], this.options.config || {});
|
||||
this.nodeId.init();
|
||||
}
|
||||
|
||||
setScrollNode(node?: HTMLElement) {
|
||||
this.#_scrollNode = node ? $(node) : null;
|
||||
}
|
||||
|
||||
on<R = any, F extends EventListener<R> = EventListener<R>>(
|
||||
eventType: string,
|
||||
listener: F,
|
||||
rewrite?: boolean,
|
||||
) {
|
||||
this.event.on(eventType, listener, rewrite);
|
||||
return this;
|
||||
}
|
||||
|
||||
off(eventType: string, listener: EventListener) {
|
||||
this.event.off(eventType, listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
trigger<R = any>(eventType: string, ...args: any): R {
|
||||
return this.event.trigger<R>(eventType, ...args);
|
||||
}
|
||||
|
||||
messageSuccess(message: string) {
|
||||
console.log(`success:${message}`);
|
||||
}
|
||||
|
||||
messageError(error: string) {
|
||||
console.log(`error:${error}`);
|
||||
}
|
||||
|
||||
messageConfirm(message: string): Promise<boolean> {
|
||||
console.log(`confirm:${message}`);
|
||||
return Promise.reject(false);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.event.destroy();
|
||||
this.plugin.destroy();
|
||||
this.card.destroy();
|
||||
this.container.empty();
|
||||
}
|
||||
}
|
||||
|
||||
export default Editor;
|
|
@ -1,20 +1,7 @@
|
|||
import { merge } from 'lodash';
|
||||
import NodeModel, { Event, $ } from '../node';
|
||||
import language from '../locales';
|
||||
import Change from '../change';
|
||||
import { DATA_ELEMENT } from '../constants/root';
|
||||
import schemaDefaultData from '../constants/schema';
|
||||
import conversionDefault from '../constants/conversion';
|
||||
import Schema from '../schema';
|
||||
import OT from '../ot';
|
||||
import {
|
||||
Selector,
|
||||
NodeInterface,
|
||||
EventInterface,
|
||||
EventListener,
|
||||
NodeModelInterface,
|
||||
NodeIdInterface,
|
||||
} from '../types/node';
|
||||
import { Selector, NodeInterface } from '../types/node';
|
||||
import { ChangeInterface } from '../types/change';
|
||||
import {
|
||||
ContainerInterface,
|
||||
|
@ -23,112 +10,42 @@ import {
|
|||
} from '../types/engine';
|
||||
import { HistoryInterface } from '../types/history';
|
||||
import { OTInterface } from '../types/ot';
|
||||
import { SchemaInterface } from '../types/schema';
|
||||
import { ConversionInterface } from '../types/conversion';
|
||||
import { CommandInterface } from '../types/command';
|
||||
import { PluginModelInterface } from '../types/plugin';
|
||||
import { HotkeyInterface } from '../types/hotkey';
|
||||
import { CardInterface, CardModelInterface } from '../types/card';
|
||||
import { ClipboardInterface } from '../types/clipboard';
|
||||
import { LanguageInterface } from '../types/language';
|
||||
import { MarkModelInterface } from '../types/mark';
|
||||
import { ListModelInterface } from '../types/list';
|
||||
import { InlineModelInterface } from '../types/inline';
|
||||
import { BlockModelInterface } from '../types/block';
|
||||
import { RequestInterface } from '../types/request';
|
||||
import Conversion from '../parser/conversion';
|
||||
import { CardInterface } from '../types/card';
|
||||
import History from '../history';
|
||||
import Command from '../command';
|
||||
import Hotkey from '../hotkey';
|
||||
import Plugin from '../plugin';
|
||||
import CardModel from '../card';
|
||||
import { getDocument } from '../utils';
|
||||
import { ANCHOR, CURSOR, FOCUS } from '../constants/selection';
|
||||
import { toJSON0, toDOM } from '../ot/utils';
|
||||
import Clipboard from '../clipboard';
|
||||
import Parser from '../parser';
|
||||
import Language from '../language';
|
||||
import Mark from '../mark';
|
||||
import List from '../list';
|
||||
import { TypingInterface } from '../types';
|
||||
import Typing from '../typing';
|
||||
import Container from './container';
|
||||
import Inline from '../inline';
|
||||
import Block from '../block';
|
||||
import Selection from '../selection';
|
||||
import Request from '../request';
|
||||
import NodeId from '../node/id';
|
||||
import Editor from '../editor';
|
||||
import { $ } from '../node';
|
||||
import './index.css';
|
||||
|
||||
class Engine implements EngineInterface {
|
||||
class Engine<T extends EngineOptions = EngineOptions>
|
||||
extends Editor<T>
|
||||
implements EngineInterface<T>
|
||||
{
|
||||
private _readonly: boolean = false;
|
||||
private _container: ContainerInterface;
|
||||
readonly kind = 'engine';
|
||||
options: EngineOptions = {
|
||||
lang: 'zh-CN',
|
||||
locale: {},
|
||||
plugins: [],
|
||||
cards: [],
|
||||
config: {},
|
||||
};
|
||||
language: LanguageInterface;
|
||||
root: NodeInterface;
|
||||
change: ChangeInterface;
|
||||
card: CardModelInterface;
|
||||
plugin: PluginModelInterface;
|
||||
node: NodeModelInterface;
|
||||
nodeId: NodeIdInterface;
|
||||
list: ListModelInterface;
|
||||
mark: MarkModelInterface;
|
||||
inline: InlineModelInterface;
|
||||
block: BlockModelInterface;
|
||||
event: EventInterface;
|
||||
|
||||
typing: TypingInterface;
|
||||
ot: OTInterface;
|
||||
schema: SchemaInterface;
|
||||
conversion: ConversionInterface;
|
||||
change: ChangeInterface;
|
||||
history: HistoryInterface;
|
||||
command: CommandInterface;
|
||||
hotkey: HotkeyInterface;
|
||||
clipboard: ClipboardInterface;
|
||||
request: RequestInterface;
|
||||
#_scrollNode: NodeInterface | null = null;
|
||||
|
||||
get container(): NodeInterface {
|
||||
return this._container.getNode();
|
||||
}
|
||||
readonly container: NodeInterface;
|
||||
|
||||
get readonly(): boolean {
|
||||
return this._readonly;
|
||||
}
|
||||
|
||||
get scrollNode(): NodeInterface | null {
|
||||
if (this.#_scrollNode) return this.#_scrollNode;
|
||||
const { scrollNode } = this.options;
|
||||
let sn = scrollNode
|
||||
? typeof scrollNode === 'function'
|
||||
? scrollNode()
|
||||
: scrollNode
|
||||
: null;
|
||||
// 查找父级样式 overflow 或者 overflow-y 为 auto 或者 scroll 的节点
|
||||
const targetValues = ['auto', 'scroll'];
|
||||
let parent = this.container.parent();
|
||||
while (!sn && parent && parent.length > 0 && parent.name !== 'body') {
|
||||
if (
|
||||
targetValues.includes(parent.css('overflow')) ||
|
||||
targetValues.includes(parent.css('overflow-y'))
|
||||
) {
|
||||
sn = parent.get<HTMLElement>();
|
||||
break;
|
||||
} else {
|
||||
parent = parent.parent();
|
||||
}
|
||||
}
|
||||
if (sn === null) sn = document.documentElement;
|
||||
this.#_scrollNode = sn ? $(sn) : null;
|
||||
return this.#_scrollNode;
|
||||
}
|
||||
|
||||
set readonly(readonly: boolean) {
|
||||
if (this.readonly === readonly) return;
|
||||
if (readonly) {
|
||||
|
@ -145,45 +62,10 @@ class Engine implements EngineInterface {
|
|||
}
|
||||
|
||||
constructor(selector: Selector, options?: EngineOptions) {
|
||||
super(selector, options);
|
||||
this.options = { ...this.options, ...options };
|
||||
// 多语言
|
||||
this.language = new Language(
|
||||
this.options.lang || 'zh-CN',
|
||||
merge(language, options?.locale),
|
||||
);
|
||||
// 事件管理
|
||||
this.event = new Event();
|
||||
// 命令
|
||||
this.command = new Command(this);
|
||||
// 节点规则
|
||||
this.schema = new Schema();
|
||||
this.schema.add(schemaDefaultData);
|
||||
// 节点转换规则
|
||||
this.conversion = new Conversion(this);
|
||||
conversionDefault.forEach((rule) =>
|
||||
this.conversion.add(rule.from, rule.to),
|
||||
);
|
||||
// 历史
|
||||
this.history = new History(this);
|
||||
// 卡片
|
||||
this.card = new CardModel(this, this.options.lazyRender);
|
||||
// 剪贴板
|
||||
this.clipboard = new Clipboard(this);
|
||||
// http请求
|
||||
this.request = new Request();
|
||||
// 插件
|
||||
this.plugin = new Plugin(this);
|
||||
// 节点管理
|
||||
this.node = new NodeModel(this);
|
||||
this.nodeId = new NodeId(this);
|
||||
// 列表
|
||||
this.list = new List(this);
|
||||
// 样式标记
|
||||
this.mark = new Mark(this);
|
||||
// 行内样式
|
||||
this.inline = new Inline(this);
|
||||
// 块级节点
|
||||
this.block = new Block(this);
|
||||
// 编辑器容器
|
||||
this._container = new Container(selector, {
|
||||
engine: this,
|
||||
|
@ -192,6 +74,7 @@ class Engine implements EngineInterface {
|
|||
tabIndex: this.options.tabIndex,
|
||||
placeholder: this.options.placeholder,
|
||||
});
|
||||
this.container = this._container.getNode();
|
||||
// 编辑器父节点
|
||||
this.root = $(
|
||||
this.options.root || this.container.parent() || getDocument().body,
|
||||
|
@ -223,16 +106,9 @@ class Engine implements EngineInterface {
|
|||
this._readonly =
|
||||
this.options.readonly === undefined ? false : this.options.readonly;
|
||||
this._container.setReadonly(this._readonly);
|
||||
// 实例化插件
|
||||
this.mark.init();
|
||||
this.inline.init();
|
||||
this.block.init();
|
||||
this.list.init();
|
||||
// 快捷键
|
||||
this.hotkey = new Hotkey(this);
|
||||
this.card.init(this.options.cards || []);
|
||||
this.plugin.init(this.options.plugins || [], this.options.config || {});
|
||||
this.nodeId.init();
|
||||
this.init();
|
||||
// 协同
|
||||
this.ot = new OT(this);
|
||||
|
||||
|
@ -242,10 +118,6 @@ class Engine implements EngineInterface {
|
|||
this.ot.initLocal();
|
||||
}
|
||||
|
||||
setScrollNode(node?: HTMLElement) {
|
||||
this.#_scrollNode = node ? $(node) : null;
|
||||
}
|
||||
|
||||
isFocus() {
|
||||
return this._container.isFocus();
|
||||
}
|
||||
|
@ -262,24 +134,6 @@ class Engine implements EngineInterface {
|
|||
this.change.range.blur();
|
||||
}
|
||||
|
||||
on<R = any, F extends EventListener<R> = EventListener<R>>(
|
||||
eventType: string,
|
||||
listener: F,
|
||||
rewrite?: boolean,
|
||||
) {
|
||||
this.event.on(eventType, listener, rewrite);
|
||||
return this;
|
||||
}
|
||||
|
||||
off(eventType: string, listener: EventListener) {
|
||||
this.event.off(eventType, listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
trigger<R = any>(eventType: string, ...args: any): R {
|
||||
return this.event.trigger(eventType, ...args);
|
||||
}
|
||||
|
||||
getValue(ignoreCursor: boolean = false) {
|
||||
const value = this.change.getValue({});
|
||||
return ignoreCursor ? Selection.removeTags(value) : value;
|
||||
|
@ -420,19 +274,6 @@ class Engine implements EngineInterface {
|
|||
});
|
||||
}
|
||||
|
||||
messageSuccess(message: string) {
|
||||
console.log(`success:${message}`);
|
||||
}
|
||||
|
||||
messageError(error: string) {
|
||||
console.log(`error:${error}`);
|
||||
}
|
||||
|
||||
messageConfirm(message: string): Promise<boolean> {
|
||||
console.log(`confirm:${message}`);
|
||||
return Promise.reject(false);
|
||||
}
|
||||
|
||||
showPlaceholder() {
|
||||
this._container.showPlaceholder();
|
||||
}
|
||||
|
@ -445,10 +286,10 @@ class Engine implements EngineInterface {
|
|||
this._container.destroy();
|
||||
this.change.destroy();
|
||||
this.hotkey.destroy();
|
||||
this.card.destroy();
|
||||
if (this.ot) {
|
||||
this.ot.destroy();
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
isMarkPlugin,
|
||||
} from './plugin';
|
||||
import Card from './card/entry';
|
||||
import CardManage from './card';
|
||||
import View from './view';
|
||||
import Toolbar, { Tooltip } from './toolbar';
|
||||
import Range, { isRangeInterface, isRange, isSelection } from './range';
|
||||
|
@ -71,4 +72,5 @@ export {
|
|||
isRange,
|
||||
isSelection,
|
||||
Resizer,
|
||||
CardManage,
|
||||
};
|
||||
|
|
|
@ -4,10 +4,13 @@ import {
|
|||
CARD_SELECTOR,
|
||||
CARD_TYPE_KEY,
|
||||
} from '../constants';
|
||||
import { EditorInterface, EngineInterface } from '../types/engine';
|
||||
import { InlineModelInterface } from '../types/inline';
|
||||
import { NodeInterface } from '../types/node';
|
||||
import { RangeInterface } from '../types/range';
|
||||
import {
|
||||
EditorInterface,
|
||||
EngineInterface,
|
||||
InlineModelInterface,
|
||||
NodeInterface,
|
||||
RangeInterface,
|
||||
} from '../types';
|
||||
import { getDocument, isEngine } from '../utils';
|
||||
import { Backspace, Left, Right } from './typing';
|
||||
import { $ } from '../node';
|
||||
|
|
|
@ -62,6 +62,14 @@ class Event implements EventInterface {
|
|||
}
|
||||
return undefined as any;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
Object.keys(this.listeners).forEach((type) => {
|
||||
this.listeners[type].forEach((listener) => {
|
||||
this.off(type, listener);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Event;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { NodeInterface } from '../types/node';
|
||||
import { DATA_ELEMENT, DATA_ID, EDITABLE } from '../constants/root';
|
||||
import { EditorInterface } from '../types/engine';
|
||||
import { EditorInterface } from '../types/editor';
|
||||
import {
|
||||
SchemaInterface,
|
||||
ParserInterface,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { CardInterface } from '../types/card';
|
||||
import { EditorInterface } from '../types/engine';
|
||||
import { EditorInterface } from '../types/editor';
|
||||
import { PluginOptions, PluginInterface } from '../types/plugin';
|
||||
|
||||
abstract class PluginEntry<T extends PluginOptions = {}>
|
||||
abstract class PluginEntry<T extends PluginOptions = PluginOptions>
|
||||
implements PluginInterface<T>
|
||||
{
|
||||
protected readonly editor: EditorInterface;
|
||||
|
@ -55,6 +55,7 @@ abstract class PluginEntry<T extends PluginOptions = {}>
|
|||
...args: any
|
||||
) => boolean | number | void,
|
||||
): Promise<void>;
|
||||
destroy?(): void;
|
||||
}
|
||||
|
||||
export default PluginEntry;
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import ElementPluginEntry from './element';
|
||||
import {
|
||||
import type {
|
||||
SchemaBlock,
|
||||
BlockInterface,
|
||||
NodeInterface,
|
||||
PluginInterface,
|
||||
PluginOptions,
|
||||
} from '../types';
|
||||
|
||||
abstract class BlockEntry<T extends {} = {}>
|
||||
abstract class BlockEntry<T extends PluginOptions = PluginOptions>
|
||||
extends ElementPluginEntry<T>
|
||||
implements BlockInterface
|
||||
implements BlockInterface<T>
|
||||
{
|
||||
readonly kind: string = 'block';
|
||||
/**
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {
|
||||
import type {
|
||||
PluginOptions,
|
||||
ElementPluginInterface,
|
||||
NodeInterface,
|
||||
ConversionData,
|
||||
PluginInterface,
|
||||
} from '../types';
|
||||
import {
|
||||
import type {
|
||||
SchemaAttributes,
|
||||
SchemaGlobal,
|
||||
SchemaRule,
|
||||
|
@ -17,9 +17,9 @@ import { $ } from '../node';
|
|||
import PluginEntry from './base';
|
||||
import { isNode } from '../node/utils';
|
||||
|
||||
abstract class ElementPluginEntry<T extends PluginOptions>
|
||||
abstract class ElementPluginEntry<T extends PluginOptions = PluginOptions>
|
||||
extends PluginEntry<T>
|
||||
implements ElementPluginInterface
|
||||
implements ElementPluginInterface<T>
|
||||
{
|
||||
readonly kind: string = 'element';
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { EditorInterface } from '../types/engine';
|
||||
import { EditorInterface } from '../types/editor';
|
||||
import {
|
||||
ElementPluginInterface,
|
||||
PluginEntry,
|
||||
|
@ -45,37 +45,52 @@ class PluginModel implements PluginModelInterface {
|
|||
}
|
||||
}
|
||||
|
||||
findPlugin(pluginName: string) {
|
||||
findPlugin<T extends PluginOptions = PluginOptions>(
|
||||
pluginName: string,
|
||||
): PluginInterface<T> | undefined {
|
||||
const plugin = this.components[pluginName];
|
||||
return plugin;
|
||||
if (!plugin) return;
|
||||
return plugin as PluginInterface<T>;
|
||||
}
|
||||
|
||||
findElementPlugin(pluginName: string) {
|
||||
const plugin = this.findPlugin(pluginName);
|
||||
findElementPlugin<T extends PluginOptions = PluginOptions>(
|
||||
pluginName: string,
|
||||
): ElementPluginInterface<T> | undefined {
|
||||
const plugin = this.findPlugin<T>(pluginName);
|
||||
if (!plugin) return;
|
||||
if (isElementPlugin(plugin)) {
|
||||
return plugin as ElementPluginInterface;
|
||||
return plugin as ElementPluginInterface<T>;
|
||||
}
|
||||
return;
|
||||
}
|
||||
findMarkPlugin(pluginName: string) {
|
||||
findMarkPlugin<T extends PluginOptions = PluginOptions>(
|
||||
pluginName: string,
|
||||
): MarkInterface<T> | undefined {
|
||||
const plugin = this.findPlugin(pluginName);
|
||||
if (!plugin) return;
|
||||
if (isMarkPlugin(plugin)) {
|
||||
return plugin as MarkInterface;
|
||||
return plugin as MarkInterface<T>;
|
||||
}
|
||||
return;
|
||||
}
|
||||
findInlinePlugin(pluginName: string) {
|
||||
findInlinePlugin<T extends PluginOptions = PluginOptions>(
|
||||
pluginName: string,
|
||||
): InlineInterface<T> | undefined {
|
||||
const plugin = this.findPlugin(pluginName);
|
||||
if (!plugin) return;
|
||||
if (isInlinePlugin(plugin)) {
|
||||
return plugin as InlineInterface;
|
||||
return plugin as InlineInterface<T>;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
findBlockPlugin(pluginName: string) {
|
||||
findBlockPlugin<T extends PluginOptions = PluginOptions>(
|
||||
pluginName: string,
|
||||
): BlockInterface<T> | undefined {
|
||||
const plugin = this.findPlugin(pluginName);
|
||||
if (!plugin) return;
|
||||
if (isBlockPlugin(plugin)) {
|
||||
return plugin as BlockInterface;
|
||||
return plugin as BlockInterface<T>;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -92,6 +107,13 @@ class PluginModel implements PluginModelInterface {
|
|||
return;
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
Object.keys(this.components).forEach((pluginName) => {
|
||||
const plugin = this.components[pluginName];
|
||||
if (plugin.destroy) plugin.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
export default PluginModel;
|
||||
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import ElementPluginEntry from './element';
|
||||
|
||||
import {
|
||||
import type {
|
||||
InlineInterface,
|
||||
NodeInterface,
|
||||
PluginEntry as PluginEntryType,
|
||||
PluginInterface,
|
||||
PluginOptions,
|
||||
} from '../types';
|
||||
import { $ } from '../node';
|
||||
import { isEngine } from '../utils';
|
||||
|
||||
abstract class InlineEntry<T extends {} = {}>
|
||||
abstract class InlineEntry<T extends PluginOptions = PluginOptions>
|
||||
extends ElementPluginEntry<T>
|
||||
implements InlineInterface
|
||||
implements InlineInterface<T>
|
||||
{
|
||||
readonly kind: string = 'inline';
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { NodeInterface } from '../../types';
|
||||
import { NodeInterface, PluginOptions } from '../../types';
|
||||
import { CARD_KEY, READY_CARD_KEY } from '../../constants';
|
||||
import { ListInterface } from '../../types/list';
|
||||
import { PluginEntry as PluginEntryType } from '../../types/plugin';
|
||||
|
@ -7,9 +7,9 @@ import { $ } from '../../node';
|
|||
import { isEngine } from '../../utils';
|
||||
import './index.css';
|
||||
|
||||
abstract class ListEntry<T extends {} = {}>
|
||||
abstract class ListEntry<T extends PluginOptions = PluginOptions>
|
||||
extends BlockEntry<T>
|
||||
implements ListInterface
|
||||
implements ListInterface<T>
|
||||
{
|
||||
cardName?: string;
|
||||
private isPasteList: boolean = false;
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import ElementPluginEntry from './element';
|
||||
import {
|
||||
import type {
|
||||
MarkInterface,
|
||||
NodeInterface,
|
||||
SchemaMark,
|
||||
PluginEntry as PluginEntryType,
|
||||
PluginInterface,
|
||||
PluginOptions,
|
||||
} from '../types';
|
||||
import { $ } from '../node';
|
||||
import { isEngine } from '../utils';
|
||||
|
||||
abstract class MarkEntry<T extends {} = {}>
|
||||
abstract class MarkEntry<T extends PluginOptions = PluginOptions>
|
||||
extends ElementPluginEntry<T>
|
||||
implements MarkInterface
|
||||
implements MarkInterface<T>
|
||||
{
|
||||
readonly kind: string = 'mark';
|
||||
/**
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from './constants/root';
|
||||
import Selection from './selection';
|
||||
import { SelectionInterface } from './types/selection';
|
||||
import { EditorInterface } from './types/engine';
|
||||
import { EditorInterface } from './types/editor';
|
||||
import { Path } from 'sharedb';
|
||||
import { $ } from './node';
|
||||
import { CardEntry } from './types/card';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { NodeInterface } from './node';
|
||||
import { ElementPluginInterface } from './plugin';
|
||||
import { ElementPluginInterface, PluginOptions } from './plugin';
|
||||
import { RangeInterface } from './range';
|
||||
import { SchemaBlock } from './schema';
|
||||
/**
|
||||
|
@ -161,7 +161,8 @@ export interface BlockModelInterface {
|
|||
/**
|
||||
* block 插件
|
||||
*/
|
||||
export interface BlockInterface extends ElementPluginInterface {
|
||||
export interface BlockInterface<T extends PluginOptions = PluginOptions>
|
||||
extends ElementPluginInterface<T> {
|
||||
readonly kind: string;
|
||||
/**
|
||||
* 标签名称
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { EditorInterface } from './engine';
|
||||
import { EditorInterface } from './editor';
|
||||
import { NodeInterface } from './node';
|
||||
import { TinyCanvasInterface } from './tiny-canvas';
|
||||
import { RangeInterface } from './range';
|
||||
|
@ -10,7 +10,7 @@ import {
|
|||
import { CardActiveTrigger, CardType, SelectStyleType } from '../card/enum';
|
||||
import { Placement } from './position';
|
||||
|
||||
export interface CardOptions<T extends CardValue = {}> {
|
||||
export interface CardOptions<T extends CardValue = CardValue> {
|
||||
editor: EditorInterface;
|
||||
value?: Partial<T>;
|
||||
root?: NodeInterface;
|
||||
|
@ -84,7 +84,7 @@ export type CardToolbarItemOptions =
|
|||
items: Array<DropdownSwitchOptions | DropdownButtonOptions>;
|
||||
};
|
||||
|
||||
export interface CardEntry<T extends CardValue = {}> {
|
||||
export interface CardEntry<T extends CardValue = CardValue> {
|
||||
prototype: CardInterface;
|
||||
new (options: CardOptions<T>): CardInterface;
|
||||
/**
|
||||
|
@ -125,7 +125,7 @@ export interface CardEntry<T extends CardValue = {}> {
|
|||
readonly lazyRender: boolean;
|
||||
}
|
||||
|
||||
export interface CardInterface<T extends CardValue = {}> {
|
||||
export interface CardInterface<T extends CardValue = CardValue> {
|
||||
/**
|
||||
* 初始化调用
|
||||
*/
|
||||
|
@ -158,6 +158,9 @@ export interface CardInterface<T extends CardValue = {}> {
|
|||
* 可编辑的节点
|
||||
*/
|
||||
readonly contenteditable: Array<string>;
|
||||
/**
|
||||
* 卡片是否处于懒加载中
|
||||
*/
|
||||
readonly loading: boolean;
|
||||
/**
|
||||
* 卡片类型,设置卡片类型会触发card重新渲染
|
||||
|
@ -254,6 +257,22 @@ export interface CardInterface<T extends CardValue = {}> {
|
|||
rgb: string;
|
||||
},
|
||||
): NodeInterface | void;
|
||||
/**
|
||||
* 在卡片右侧光标容器位置按下左键触发,可以实现如何选中卡片内部自定义操作
|
||||
*/
|
||||
onSelectLeft?(event: KeyboardEvent): boolean | void;
|
||||
/**
|
||||
* 在卡片左侧光标容器位置按下右键触发,可以实现如何选中卡片内部自定义操作
|
||||
*/
|
||||
onSelectRight?(event: KeyboardEvent): boolean | void;
|
||||
/**
|
||||
* 在卡片下方按下上键触发(block类型有效),可以实现如何选中卡片内部自定义操作
|
||||
*/
|
||||
onSelectUp?(event: KeyboardEvent): boolean | void;
|
||||
/**
|
||||
* 在卡片上方按下下键触发(block类型有效),可以实现如何选中卡片内部自定义操作
|
||||
*/
|
||||
onSelectDown?(event: KeyboardEvent): boolean | void;
|
||||
/**
|
||||
* 激活状态变化时触发
|
||||
* @param activated 是否激活
|
||||
|
@ -530,7 +549,7 @@ export interface CardModelInterface {
|
|||
* @param value 要更新的卡片值
|
||||
* @param args 更新时渲染时额外的参数
|
||||
*/
|
||||
update<V extends CardValue = {}>(
|
||||
update<V extends CardValue = CardValue>(
|
||||
selector: NodeInterface | Node | string,
|
||||
value: Partial<V>,
|
||||
...args: any
|
||||
|
|
|
@ -0,0 +1,433 @@
|
|||
import {
|
||||
PluginEntry,
|
||||
CardEntry,
|
||||
LanguageInterface,
|
||||
NodeInterface,
|
||||
CommandInterface,
|
||||
RequestInterface,
|
||||
CardModelInterface,
|
||||
PluginModelInterface,
|
||||
NodeModelInterface,
|
||||
NodeIdInterface,
|
||||
MarkModelInterface,
|
||||
InlineModelInterface,
|
||||
BlockModelInterface,
|
||||
EventInterface,
|
||||
SchemaInterface,
|
||||
ConversionInterface,
|
||||
ClipboardInterface,
|
||||
CardInterface,
|
||||
PluginOptions,
|
||||
} from '.';
|
||||
import { ListModelInterface } from './list';
|
||||
import { EventListener } from './node';
|
||||
|
||||
export interface EditorOptions {
|
||||
/**
|
||||
* 语言,默认zh-CN
|
||||
*/
|
||||
lang?: string;
|
||||
/**
|
||||
* 本地化
|
||||
*/
|
||||
locale?: Record<string, {}>;
|
||||
/**
|
||||
* 插件配置
|
||||
*/
|
||||
plugins?: Array<PluginEntry>;
|
||||
/**
|
||||
* 卡片配置
|
||||
*/
|
||||
cards?: Array<CardEntry>;
|
||||
/**
|
||||
* 插件选项,每个插件具体选项请在插件查看
|
||||
*/
|
||||
config?: Record<string, PluginOptions>;
|
||||
/**
|
||||
* 阅读器根节点,默认为阅读器所在节点的父节点
|
||||
*/
|
||||
root?: Node;
|
||||
/**
|
||||
* 滚动条节点,查找父级样式 overflow 或者 overflow-y 为 auto 或者 scroll 的节点
|
||||
*/
|
||||
scrollNode?: Node | (() => Node | null);
|
||||
/**
|
||||
* 懒惰渲染卡片(仅限已启用 lazyRender 的卡片),默认为 true
|
||||
*/
|
||||
lazyRender?: boolean;
|
||||
}
|
||||
|
||||
export interface EditorInterface<T extends EditorOptions = EditorOptions> {
|
||||
options: T;
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
readonly kind: 'engine' | 'view' | 'editor';
|
||||
/**
|
||||
* 语言
|
||||
*/
|
||||
language: LanguageInterface;
|
||||
/**
|
||||
* 编辑器节点
|
||||
*/
|
||||
readonly container: NodeInterface;
|
||||
/**
|
||||
* 滚动条节点
|
||||
*/
|
||||
readonly scrollNode: NodeInterface | null;
|
||||
/**
|
||||
* 编辑器根节点,默认为编辑器父节点
|
||||
*/
|
||||
readonly root: NodeInterface;
|
||||
/**
|
||||
* 编辑器命令
|
||||
*/
|
||||
command: CommandInterface;
|
||||
/**
|
||||
* 请求
|
||||
*/
|
||||
request: RequestInterface;
|
||||
/**
|
||||
* 卡片
|
||||
*/
|
||||
card: CardModelInterface;
|
||||
/**
|
||||
* 插件
|
||||
*/
|
||||
plugin: PluginModelInterface;
|
||||
/**
|
||||
* 节点管理
|
||||
*/
|
||||
node: NodeModelInterface;
|
||||
/**
|
||||
* 节点id管理器
|
||||
*/
|
||||
nodeId: NodeIdInterface;
|
||||
/**
|
||||
* List 列表标签管理
|
||||
*/
|
||||
list: ListModelInterface;
|
||||
/**
|
||||
* Mark 标签管理
|
||||
*/
|
||||
mark: MarkModelInterface;
|
||||
/**
|
||||
* inline 标签管理
|
||||
*/
|
||||
inline: InlineModelInterface;
|
||||
/**
|
||||
* block 标签管理
|
||||
*/
|
||||
block: BlockModelInterface;
|
||||
/**
|
||||
* 事件
|
||||
*/
|
||||
event: EventInterface;
|
||||
/**
|
||||
* 标签过滤规则
|
||||
*/
|
||||
schema: SchemaInterface;
|
||||
/**
|
||||
* 标签转换规则
|
||||
*/
|
||||
conversion: ConversionInterface;
|
||||
/**
|
||||
* 剪切板
|
||||
*/
|
||||
clipboard: ClipboardInterface;
|
||||
/**
|
||||
* 设置滚动节点
|
||||
* @param node 节点
|
||||
*/
|
||||
setScrollNode(node: HTMLElement): void;
|
||||
destroy(): void;
|
||||
/**
|
||||
* 绑定事件
|
||||
* @param eventType 事件类型
|
||||
* @param listener 事件回调
|
||||
* @param rewrite 是否重写
|
||||
*/
|
||||
on<R = any, F extends EventListener<R> = EventListener<R>>(
|
||||
eventType: string,
|
||||
listener: F,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 全选ctrl+a键按下,返回false,终止处理其它监听
|
||||
* @param eventType
|
||||
* @param listener
|
||||
* @param rewrite
|
||||
*/
|
||||
on(
|
||||
eventType: 'keydown:all',
|
||||
listener: (event: KeyboardEvent) => boolean | void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 卡片最小化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
* @param rewrite
|
||||
*/
|
||||
on(
|
||||
eventType: 'card:minimize',
|
||||
listener: (card: CardInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 卡片最大化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
* @param rewrite
|
||||
*/
|
||||
on(
|
||||
eventType: 'card:maximize',
|
||||
listener: (card: CardInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML 代码之前触发
|
||||
* @param root DOM节点
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:value-before',
|
||||
listener: (root: NodeInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML,遍历子节点时触发。返回false跳过当前节点
|
||||
* @param node 当前遍历的节点
|
||||
* @param attributes 当前节点已过滤后的属性
|
||||
* @param styles 当前节点已过滤后的样式
|
||||
* @param value 当前已经生成的xml代码
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:value',
|
||||
listener: (
|
||||
node: NodeInterface,
|
||||
attributes: { [key: string]: string },
|
||||
styles: { [key: string]: string },
|
||||
value: Array<string>,
|
||||
) => boolean | void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
|
||||
* @param value xml代码
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:value-after',
|
||||
listener: (value: Array<string>) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 转换为HTML代码之前触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:html-before',
|
||||
listener: (root: NodeInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 转换为HTML代码
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:html',
|
||||
listener: (root: NodeInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 转换为HTML代码之后触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:html-after',
|
||||
listener: (root: NodeInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 复制DOM节点时触发
|
||||
* @param node 当前遍历的子节点
|
||||
*/
|
||||
on(
|
||||
eventType: 'copy',
|
||||
listener: (root: NodeInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 移除绑定事件
|
||||
* @param eventType 事件类型
|
||||
* @param listener 事件回调
|
||||
*/
|
||||
off(eventType: string, listener: EventListener): void;
|
||||
/**
|
||||
* 全选ctrl+a键按下,返回false,终止处理其它监听
|
||||
* @param eventType
|
||||
* @param listener
|
||||
*/
|
||||
off(
|
||||
eventType: 'keydown:all',
|
||||
listener: (event: KeyboardEvent) => boolean | void,
|
||||
): void;
|
||||
/**
|
||||
* 卡片最小化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
*/
|
||||
off(
|
||||
eventType: 'card:minimize',
|
||||
listener: (card: CardInterface) => void,
|
||||
): void;
|
||||
/**
|
||||
* 卡片最大化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
*/
|
||||
off(
|
||||
eventType: 'card:maximize',
|
||||
listener: (card: CardInterface) => void,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML 代码之前触发
|
||||
* @param root DOM节点
|
||||
*/
|
||||
off(
|
||||
eventType: 'parse:value-before',
|
||||
listener: (root: NodeInterface) => void,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML,遍历子节点时触发。返回false跳过当前节点
|
||||
* @param node 当前遍历的节点
|
||||
* @param attributes 当前节点已过滤后的属性
|
||||
* @param styles 当前节点已过滤后的样式
|
||||
* @param value 当前已经生成的xml代码
|
||||
*/
|
||||
off(
|
||||
eventType: 'parse:value',
|
||||
listener: (
|
||||
node: NodeInterface,
|
||||
attributes: { [key: string]: string },
|
||||
styles: { [key: string]: string },
|
||||
value: Array<string>,
|
||||
) => boolean | void,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
|
||||
* @param value xml代码
|
||||
*/
|
||||
off(
|
||||
eventType: 'parse:value-after',
|
||||
listener: (value: Array<string>) => void,
|
||||
): void;
|
||||
/**
|
||||
* 转换为HTML代码之前触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
off(
|
||||
eventType: 'parse:html-before',
|
||||
listener: (root: NodeInterface) => void,
|
||||
): void;
|
||||
/**
|
||||
* 转换为HTML代码
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
off(eventType: 'parse:html', listener: (root: NodeInterface) => void): void;
|
||||
/**
|
||||
* 转换为HTML代码之后触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
off(
|
||||
eventType: 'parse:html-after',
|
||||
listener: (root: NodeInterface) => void,
|
||||
): void;
|
||||
/**
|
||||
* 复制DOM节点时触发
|
||||
* @param node 当前遍历的子节点
|
||||
*/
|
||||
off(eventType: 'copy', listener: (root: NodeInterface) => void): void;
|
||||
/**
|
||||
* 触发事件
|
||||
* @param eventType 事件名称
|
||||
* @param args 触发参数
|
||||
*/
|
||||
trigger<R = any>(eventType: string, ...args: any): R;
|
||||
/**
|
||||
* 全选ctrl+a键按下,返回false,终止处理其它监听
|
||||
* @param eventType
|
||||
* @param listener
|
||||
*/
|
||||
trigger(eventType: 'keydown:all', event: KeyboardEvent): boolean | void;
|
||||
/**
|
||||
* 卡片最小化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
*/
|
||||
trigger(eventType: 'card:minimize', card: CardInterface): void;
|
||||
/**
|
||||
* 卡片最大化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
*/
|
||||
trigger(eventType: 'card:maximize', card: CardInterface): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML 代码之前触发
|
||||
* @param root DOM节点
|
||||
*/
|
||||
trigger(eventType: 'parse:value-before', root: NodeInterface): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML,遍历子节点时触发。返回false跳过当前节点
|
||||
* @param node 当前遍历的节点
|
||||
* @param attributes 当前节点已过滤后的属性
|
||||
* @param styles 当前节点已过滤后的样式
|
||||
* @param value 当前已经生成的xml代码
|
||||
*/
|
||||
trigger(
|
||||
eventType: 'parse:value',
|
||||
node: NodeInterface,
|
||||
attributes: { [key: string]: string },
|
||||
styles: { [key: string]: string },
|
||||
value: Array<string>,
|
||||
): boolean | void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
|
||||
* @param value xml代码
|
||||
*/
|
||||
trigger(eventType: 'parse:value-after', value: Array<string>): void;
|
||||
/**
|
||||
* 转换为HTML代码之前触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
trigger(eventType: 'parse:html-before', root: NodeInterface): void;
|
||||
/**
|
||||
* 转换为HTML代码
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
trigger(eventType: 'parse:html', root: NodeInterface): void;
|
||||
/**
|
||||
* 转换为HTML代码之后触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
trigger(eventType: 'parse:html-after', root: NodeInterface): void;
|
||||
/**
|
||||
* 复制DOM节点时触发
|
||||
* @param node 当前遍历的子节点
|
||||
*/
|
||||
trigger(eventType: 'copy', root: NodeInterface): void;
|
||||
/**
|
||||
* 显示成功的信息
|
||||
* @param message 信息
|
||||
*/
|
||||
messageSuccess(message: string): void;
|
||||
/**
|
||||
* 显示错误信息
|
||||
* @param error 错误信息
|
||||
*/
|
||||
messageError(error: string): void;
|
||||
/**
|
||||
* 消息确认
|
||||
* @param message 消息
|
||||
*/
|
||||
messageConfirm(message: string): Promise<boolean>;
|
||||
}
|
|
@ -1,29 +1,15 @@
|
|||
import {
|
||||
EventInterface,
|
||||
NodeInterface,
|
||||
Selector,
|
||||
EventListener,
|
||||
NodeModelInterface,
|
||||
} from './node';
|
||||
import { NodeInterface, Selector, EventListener } from './node';
|
||||
import { ChangeInterface } from './change';
|
||||
import { OTInterface } from './ot';
|
||||
import { SchemaInterface } from './schema';
|
||||
import { ConversionInterface } from './conversion';
|
||||
import { HistoryInterface } from './history';
|
||||
import { PluginEntry, PluginModelInterface, PluginOptions } from './plugin';
|
||||
import { CommandInterface } from './command';
|
||||
import { CardEntry, CardInterface, CardModelInterface } from './card';
|
||||
import { ClipboardData, ClipboardInterface } from './clipboard';
|
||||
import { LanguageInterface } from './language';
|
||||
import { MarkModelInterface } from './mark';
|
||||
import { ListModelInterface } from './list';
|
||||
import { CardInterface } from './card';
|
||||
import { ClipboardData } from './clipboard';
|
||||
import { TypingInterface } from './typing';
|
||||
import { InlineModelInterface } from './inline';
|
||||
import { BlockModelInterface } from './block';
|
||||
import { RequestInterface } from './request';
|
||||
import { RangeInterface } from './range';
|
||||
import { Op } from 'sharedb';
|
||||
import { HotkeyInterface, NodeIdInterface } from './';
|
||||
import { EditorInterface, EditorOptions } from './editor';
|
||||
import { HotkeyInterface } from './hotkey';
|
||||
|
||||
/**
|
||||
* 编辑器容器接口
|
||||
|
@ -59,390 +45,7 @@ export interface ContainerInterface {
|
|||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export interface EditorInterface {
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
readonly kind: 'engine' | 'view';
|
||||
/**
|
||||
* 语言
|
||||
*/
|
||||
language: LanguageInterface;
|
||||
/**
|
||||
* 编辑器节点
|
||||
*/
|
||||
container: NodeInterface;
|
||||
/**
|
||||
* 滚动条节点
|
||||
*/
|
||||
readonly scrollNode: NodeInterface | null;
|
||||
/**
|
||||
* 编辑器根节点,默认为编辑器父节点
|
||||
*/
|
||||
root: NodeInterface;
|
||||
/**
|
||||
* 编辑器命令
|
||||
*/
|
||||
command: CommandInterface;
|
||||
/**
|
||||
* 请求
|
||||
*/
|
||||
request: RequestInterface;
|
||||
/**
|
||||
* 卡片
|
||||
*/
|
||||
card: CardModelInterface;
|
||||
/**
|
||||
* 插件
|
||||
*/
|
||||
plugin: PluginModelInterface;
|
||||
/**
|
||||
* 节点管理
|
||||
*/
|
||||
node: NodeModelInterface;
|
||||
/**
|
||||
* 节点id管理器
|
||||
*/
|
||||
nodeId: NodeIdInterface;
|
||||
/**
|
||||
* List 列表标签管理
|
||||
*/
|
||||
list: ListModelInterface;
|
||||
/**
|
||||
* Mark 标签管理
|
||||
*/
|
||||
mark: MarkModelInterface;
|
||||
/**
|
||||
* inline 标签管理
|
||||
*/
|
||||
inline: InlineModelInterface;
|
||||
/**
|
||||
* block 标签管理
|
||||
*/
|
||||
block: BlockModelInterface;
|
||||
/**
|
||||
* 事件
|
||||
*/
|
||||
event: EventInterface;
|
||||
/**
|
||||
* 标签过滤规则
|
||||
*/
|
||||
schema: SchemaInterface;
|
||||
/**
|
||||
* 标签转换规则
|
||||
*/
|
||||
conversion: ConversionInterface;
|
||||
/**
|
||||
* 剪切板
|
||||
*/
|
||||
clipboard: ClipboardInterface;
|
||||
|
||||
/**
|
||||
* 设置滚动节点
|
||||
* @param node 节点
|
||||
*/
|
||||
setScrollNode(node: HTMLElement): void;
|
||||
/**
|
||||
* 绑定事件
|
||||
* @param eventType 事件类型
|
||||
* @param listener 事件回调
|
||||
* @param rewrite 是否重写
|
||||
*/
|
||||
on<R = any, F extends EventListener<R> = EventListener<R>>(
|
||||
eventType: string,
|
||||
listener: F,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 全选ctrl+a键按下,返回false,终止处理其它监听
|
||||
* @param eventType
|
||||
* @param listener
|
||||
* @param rewrite
|
||||
*/
|
||||
on(
|
||||
eventType: 'keydown:all',
|
||||
listener: (event: KeyboardEvent) => boolean | void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 卡片最小化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
* @param rewrite
|
||||
*/
|
||||
on(
|
||||
eventType: 'card:minimize',
|
||||
listener: (card: CardInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 卡片最大化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
* @param rewrite
|
||||
*/
|
||||
on(
|
||||
eventType: 'card:maximize',
|
||||
listener: (card: CardInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML 代码之前触发
|
||||
* @param root DOM节点
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:value-before',
|
||||
listener: (root: NodeInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML,遍历子节点时触发。返回false跳过当前节点
|
||||
* @param node 当前遍历的节点
|
||||
* @param attributes 当前节点已过滤后的属性
|
||||
* @param styles 当前节点已过滤后的样式
|
||||
* @param value 当前已经生成的xml代码
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:value',
|
||||
listener: (
|
||||
node: NodeInterface,
|
||||
attributes: { [key: string]: string },
|
||||
styles: { [key: string]: string },
|
||||
value: Array<string>,
|
||||
) => boolean | void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
|
||||
* @param value xml代码
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:value-after',
|
||||
listener: (value: Array<string>) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 转换为HTML代码之前触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:html-before',
|
||||
listener: (root: NodeInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 转换为HTML代码
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:html',
|
||||
listener: (root: NodeInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 转换为HTML代码之后触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
on(
|
||||
eventType: 'parse:html-after',
|
||||
listener: (root: NodeInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 复制DOM节点时触发
|
||||
* @param node 当前遍历的子节点
|
||||
*/
|
||||
on(
|
||||
eventType: 'copy',
|
||||
listener: (root: NodeInterface) => void,
|
||||
rewrite?: boolean,
|
||||
): void;
|
||||
/**
|
||||
* 移除绑定事件
|
||||
* @param eventType 事件类型
|
||||
* @param listener 事件回调
|
||||
*/
|
||||
off(eventType: string, listener: EventListener): void;
|
||||
/**
|
||||
* 全选ctrl+a键按下,返回false,终止处理其它监听
|
||||
* @param eventType
|
||||
* @param listener
|
||||
*/
|
||||
off(
|
||||
eventType: 'keydown:all',
|
||||
listener: (event: KeyboardEvent) => boolean | void,
|
||||
): void;
|
||||
/**
|
||||
* 卡片最小化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
*/
|
||||
off(
|
||||
eventType: 'card:minimize',
|
||||
listener: (card: CardInterface) => void,
|
||||
): void;
|
||||
/**
|
||||
* 卡片最大化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
*/
|
||||
off(
|
||||
eventType: 'card:maximize',
|
||||
listener: (card: CardInterface) => void,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML 代码之前触发
|
||||
* @param root DOM节点
|
||||
*/
|
||||
off(
|
||||
eventType: 'parse:value-before',
|
||||
listener: (root: NodeInterface) => void,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML,遍历子节点时触发。返回false跳过当前节点
|
||||
* @param node 当前遍历的节点
|
||||
* @param attributes 当前节点已过滤后的属性
|
||||
* @param styles 当前节点已过滤后的样式
|
||||
* @param value 当前已经生成的xml代码
|
||||
*/
|
||||
off(
|
||||
eventType: 'parse:value',
|
||||
listener: (
|
||||
node: NodeInterface,
|
||||
attributes: { [key: string]: string },
|
||||
styles: { [key: string]: string },
|
||||
value: Array<string>,
|
||||
) => boolean | void,
|
||||
): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
|
||||
* @param value xml代码
|
||||
*/
|
||||
off(
|
||||
eventType: 'parse:value-after',
|
||||
listener: (value: Array<string>) => void,
|
||||
): void;
|
||||
/**
|
||||
* 转换为HTML代码之前触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
off(
|
||||
eventType: 'parse:html-before',
|
||||
listener: (root: NodeInterface) => void,
|
||||
): void;
|
||||
/**
|
||||
* 转换为HTML代码
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
off(eventType: 'parse:html', listener: (root: NodeInterface) => void): void;
|
||||
/**
|
||||
* 转换为HTML代码之后触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
off(
|
||||
eventType: 'parse:html-after',
|
||||
listener: (root: NodeInterface) => void,
|
||||
): void;
|
||||
/**
|
||||
* 复制DOM节点时触发
|
||||
* @param node 当前遍历的子节点
|
||||
*/
|
||||
off(eventType: 'copy', listener: (root: NodeInterface) => void): void;
|
||||
/**
|
||||
* 触发事件
|
||||
* @param eventType 事件名称
|
||||
* @param args 触发参数
|
||||
*/
|
||||
trigger<R = any>(eventType: string, ...args: any): R;
|
||||
/**
|
||||
* 全选ctrl+a键按下,返回false,终止处理其它监听
|
||||
* @param eventType
|
||||
* @param listener
|
||||
*/
|
||||
trigger(eventType: 'keydown:all', event: KeyboardEvent): boolean | void;
|
||||
/**
|
||||
* 卡片最小化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
*/
|
||||
trigger(eventType: 'card:minimize', card: CardInterface): void;
|
||||
/**
|
||||
* 卡片最大化时触发
|
||||
* @param eventType
|
||||
* @param listener name:插件名称、args:参数
|
||||
*/
|
||||
trigger(eventType: 'card:maximize', card: CardInterface): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML 代码之前触发
|
||||
* @param root DOM节点
|
||||
*/
|
||||
trigger(eventType: 'parse:value-before', root: NodeInterface): void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML,遍历子节点时触发。返回false跳过当前节点
|
||||
* @param node 当前遍历的节点
|
||||
* @param attributes 当前节点已过滤后的属性
|
||||
* @param styles 当前节点已过滤后的样式
|
||||
* @param value 当前已经生成的xml代码
|
||||
*/
|
||||
trigger(
|
||||
eventType: 'parse:value',
|
||||
node: NodeInterface,
|
||||
attributes: { [key: string]: string },
|
||||
styles: { [key: string]: string },
|
||||
value: Array<string>,
|
||||
): boolean | void;
|
||||
/**
|
||||
* 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
|
||||
* @param value xml代码
|
||||
*/
|
||||
trigger(eventType: 'parse:value-after', value: Array<string>): void;
|
||||
/**
|
||||
* 转换为HTML代码之前触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
trigger(eventType: 'parse:html-before', root: NodeInterface): void;
|
||||
/**
|
||||
* 转换为HTML代码
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
trigger(eventType: 'parse:html', root: NodeInterface): void;
|
||||
/**
|
||||
* 转换为HTML代码之后触发
|
||||
* @param root 需要转换的根节点
|
||||
*/
|
||||
trigger(eventType: 'parse:html-after', root: NodeInterface): void;
|
||||
/**
|
||||
* 复制DOM节点时触发
|
||||
* @param node 当前遍历的子节点
|
||||
*/
|
||||
trigger(eventType: 'copy', root: NodeInterface): void;
|
||||
/**
|
||||
* 显示成功的信息
|
||||
* @param message 信息
|
||||
*/
|
||||
messageSuccess(message: string): void;
|
||||
/**
|
||||
* 显示错误信息
|
||||
* @param error 错误信息
|
||||
*/
|
||||
messageError(error: string): void;
|
||||
/**
|
||||
* 消息确认
|
||||
* @param message 消息
|
||||
*/
|
||||
messageConfirm(message: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
export type EngineOptions = {
|
||||
/**
|
||||
* 本地化语言,默认 zh-CN
|
||||
*/
|
||||
lang?: string;
|
||||
/**
|
||||
* 本地化语言
|
||||
*/
|
||||
locale?: { [key: string]: {} };
|
||||
export interface EngineOptions extends EditorOptions {
|
||||
/**
|
||||
* 样式名称
|
||||
*/
|
||||
|
@ -451,26 +54,6 @@ export type EngineOptions = {
|
|||
* tab 键的索引
|
||||
*/
|
||||
tabIndex?: number;
|
||||
/**
|
||||
* 根节点
|
||||
*/
|
||||
root?: Node;
|
||||
/**
|
||||
* 滚动条节点,查找父级样式 overflow 或者 overflow-y 为 auto 或者 scroll 的节点
|
||||
*/
|
||||
scrollNode?: Node | (() => Node | null);
|
||||
/**
|
||||
* 插件配置
|
||||
*/
|
||||
plugins?: Array<PluginEntry>;
|
||||
/**
|
||||
* 卡片配置
|
||||
*/
|
||||
cards?: Array<CardEntry>;
|
||||
/**
|
||||
* 插件的可选项
|
||||
*/
|
||||
config?: { [k: string]: PluginOptions };
|
||||
/**
|
||||
* 占位内容
|
||||
*/
|
||||
|
@ -479,24 +62,21 @@ export type EngineOptions = {
|
|||
* 是否只读
|
||||
*/
|
||||
readonly?: boolean;
|
||||
/**
|
||||
* 懒惰渲染卡片(仅限已启用 lazyRender 的卡片),默认为 true
|
||||
*/
|
||||
lazyRender?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Engine {
|
||||
export interface Engine<T extends EngineOptions = EngineOptions> {
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
new (selector: Selector, options?: EngineOptions): EngineInterface;
|
||||
new (selector: Selector, options?: T): EngineInterface<T>;
|
||||
}
|
||||
|
||||
export interface EngineInterface extends EditorInterface {
|
||||
export interface EngineInterface<T extends EngineOptions = EngineOptions>
|
||||
extends EditorInterface<T> {
|
||||
/**
|
||||
* 选项
|
||||
*/
|
||||
options: EngineOptions;
|
||||
options: T;
|
||||
/**
|
||||
* 是否只读
|
||||
*/
|
||||
|
|
|
@ -23,3 +23,4 @@ export * from './tiny-canvas';
|
|||
export * from './parser';
|
||||
export * from './resizer';
|
||||
export * from './position';
|
||||
export * from './editor';
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { NodeInterface } from './node';
|
||||
import { PluginInterface, ElementPluginInterface } from './plugin';
|
||||
import {
|
||||
PluginInterface,
|
||||
ElementPluginInterface,
|
||||
PluginOptions,
|
||||
} from './plugin';
|
||||
import { RangeInterface } from './range';
|
||||
import { SchemaInterface } from './schema';
|
||||
|
||||
|
@ -60,7 +64,8 @@ export interface InlineModelInterface {
|
|||
flat(node: NodeInterface | RangeInterface, schema?: SchemaInterface): void;
|
||||
}
|
||||
|
||||
export interface InlineInterface extends ElementPluginInterface {
|
||||
export interface InlineInterface<T extends PluginOptions = PluginOptions>
|
||||
extends ElementPluginInterface<T> {
|
||||
readonly kind: string;
|
||||
/**
|
||||
* 标签名称
|
||||
|
|
|
@ -2,6 +2,7 @@ import { CardInterface } from './card';
|
|||
import { NodeInterface } from './node';
|
||||
import { BlockInterface } from './block';
|
||||
import { RangeInterface } from './range';
|
||||
import { PluginOptions } from './plugin';
|
||||
|
||||
/**
|
||||
* 列表删除键处理器
|
||||
|
@ -12,7 +13,8 @@ export interface BackspaceInterface {
|
|||
/**
|
||||
* 列表接口
|
||||
*/
|
||||
export interface ListInterface extends BlockInterface {
|
||||
export interface ListInterface<T extends PluginOptions = PluginOptions>
|
||||
extends BlockInterface<T> {
|
||||
/**
|
||||
* 自定义列表卡片名称
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { NodeInterface } from './node';
|
||||
import { ElementPluginInterface, PluginInterface } from './plugin';
|
||||
import {
|
||||
ElementPluginInterface,
|
||||
PluginInterface,
|
||||
PluginOptions,
|
||||
} from './plugin';
|
||||
import { RangeInterface } from './range';
|
||||
import { SchemaMark } from './schema';
|
||||
|
||||
|
@ -115,7 +119,8 @@ export interface MarkModelInterface {
|
|||
repairCursor(node: NodeInterface | Node): void;
|
||||
}
|
||||
|
||||
export interface MarkInterface extends ElementPluginInterface {
|
||||
export interface MarkInterface<T extends PluginOptions = PluginOptions>
|
||||
extends ElementPluginInterface<T> {
|
||||
readonly kind: string;
|
||||
/**
|
||||
* 标签名称
|
||||
|
|
|
@ -38,6 +38,10 @@ export interface EventInterface {
|
|||
* @param args 事件参数
|
||||
*/
|
||||
trigger<R = any>(eventType: string, ...args: any): R;
|
||||
/**
|
||||
* 注销事件
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
export type Selector =
|
||||
| string
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { BlockInterface, InlineInterface, MarkInterface } from '.';
|
||||
import { CardInterface } from './card';
|
||||
import { ConversionData } from './conversion';
|
||||
import { EditorInterface } from './engine';
|
||||
import { EditorInterface } from './editor';
|
||||
import { NodeInterface } from './node';
|
||||
import { SchemaGlobal, SchemaRule, SchemaValue } from './schema';
|
||||
|
||||
|
@ -26,7 +26,7 @@ export interface PluginEntry {
|
|||
readonly pluginName: string;
|
||||
}
|
||||
|
||||
export interface PluginInterface<T extends PluginOptions = {}> {
|
||||
export interface PluginInterface<T extends PluginOptions = PluginOptions> {
|
||||
readonly kind: string;
|
||||
readonly name: string;
|
||||
/**
|
||||
|
@ -77,9 +77,12 @@ export interface PluginInterface<T extends PluginOptions = {}> {
|
|||
...args: any
|
||||
) => boolean | number | void,
|
||||
): Promise<void>;
|
||||
|
||||
destroy?(): void;
|
||||
}
|
||||
|
||||
export interface ElementPluginInterface extends PluginInterface {
|
||||
export interface ElementPluginInterface<T extends PluginOptions = PluginOptions>
|
||||
extends PluginInterface<T> {
|
||||
/**
|
||||
* 标签名称
|
||||
*/
|
||||
|
@ -190,9 +193,21 @@ export interface PluginModelInterface {
|
|||
* 获取一个插件
|
||||
* @param pluginName 插件名称
|
||||
*/
|
||||
findPlugin(pluginName: string): PluginInterface | undefined;
|
||||
findElementPlugin(pluginName: string): ElementPluginInterface | undefined;
|
||||
findMarkPlugin(pluginName: string): MarkInterface | undefined;
|
||||
findInlinePlugin(pluginName: string): InlineInterface | undefined;
|
||||
findBlockPlugin(pluginName: string): BlockInterface | undefined;
|
||||
findPlugin<T extends PluginOptions = PluginOptions>(
|
||||
pluginName: string,
|
||||
): PluginInterface<T> | undefined;
|
||||
findElementPlugin<T extends PluginOptions = PluginOptions>(
|
||||
pluginName: string,
|
||||
): ElementPluginInterface<T> | undefined;
|
||||
findMarkPlugin<T extends PluginOptions = PluginOptions>(
|
||||
pluginName: string,
|
||||
): MarkInterface<T> | undefined;
|
||||
findInlinePlugin<T extends PluginOptions = PluginOptions>(
|
||||
pluginName: string,
|
||||
): InlineInterface<T> | undefined;
|
||||
findBlockPlugin<T extends PluginOptions = PluginOptions>(
|
||||
pluginName: string,
|
||||
): BlockInterface<T> | undefined;
|
||||
|
||||
destroy(): void;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Path } from 'sharedb';
|
||||
import { EditorInterface } from './engine';
|
||||
import { EditorInterface } from './editor';
|
||||
import { NodeInterface } from './node';
|
||||
import { SelectionInterface } from './selection';
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { EngineInterface } from './engine';
|
||||
import { EventListener } from './node';
|
||||
|
||||
export interface TypingHandle {
|
||||
prototype: TypingHandleInterface;
|
||||
new (engine: EngineInterface): TypingHandleInterface;
|
||||
}
|
||||
export type TypingEventListener = (event: KeyboardEvent) => boolean | void;
|
||||
/**
|
||||
* 按键处理接口
|
||||
*/
|
||||
|
@ -12,7 +12,7 @@ export interface TypingHandleInterface {
|
|||
/**
|
||||
* 事件集合
|
||||
*/
|
||||
listeners: Array<EventListener>;
|
||||
listeners: Array<TypingEventListener>;
|
||||
/**
|
||||
* 按键类型 键盘按下 | 键盘弹起
|
||||
*/
|
||||
|
@ -25,12 +25,17 @@ export interface TypingHandleInterface {
|
|||
* 绑定事件
|
||||
* @param listener 事件方法
|
||||
*/
|
||||
on(listener: EventListener): void;
|
||||
on(listener: TypingEventListener): void;
|
||||
/**
|
||||
* 绑定到第一个事件
|
||||
* @param listener 事件方法
|
||||
*/
|
||||
unshiftOn(listener: TypingEventListener): void;
|
||||
/**
|
||||
* 移除事件
|
||||
* @param listener 事件方法
|
||||
*/
|
||||
off(listener: EventListener): void;
|
||||
off(listener: TypingEventListener): void;
|
||||
/**
|
||||
* 触发事件
|
||||
* @param event 键盘事件
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { CardEntry } from './card';
|
||||
import { EditorInterface } from './engine';
|
||||
import { EditorInterface, EditorOptions } from './editor';
|
||||
import { NodeInterface } from './node';
|
||||
import { PluginEntry } from './plugin';
|
||||
|
||||
export interface ViewOptions extends EditorOptions {}
|
||||
/**
|
||||
* 阅读器接口
|
||||
*/
|
||||
export interface ViewInterface extends EditorInterface {
|
||||
export interface ViewInterface<T extends ViewOptions = ViewOptions>
|
||||
extends EditorInterface<T> {
|
||||
options: T;
|
||||
/**
|
||||
* 渲染内容
|
||||
* @param content 渲染的内容
|
||||
|
@ -26,38 +27,3 @@ export interface ViewInterface extends EditorInterface {
|
|||
*/
|
||||
trigger(eventType: 'render', value: NodeInterface): void;
|
||||
}
|
||||
|
||||
export type ContentViewOptions = {
|
||||
/**
|
||||
* 语言,默认zh-CN
|
||||
*/
|
||||
lang?: string;
|
||||
/**
|
||||
* 本地化
|
||||
*/
|
||||
locale?: { [key: string]: {} };
|
||||
/**
|
||||
* 插件配置
|
||||
*/
|
||||
plugins?: Array<PluginEntry>;
|
||||
/**
|
||||
* 卡片配置
|
||||
*/
|
||||
cards?: Array<CardEntry>;
|
||||
/**
|
||||
* 插件选项,每个插件具体选项请在插件查看
|
||||
*/
|
||||
config?: { [k: string]: {} };
|
||||
/**
|
||||
* 阅读器根节点,默认为阅读器所在节点的父节点
|
||||
*/
|
||||
root?: Node;
|
||||
/**
|
||||
* 滚动条节点,查找父级样式 overflow 或者 overflow-y 为 auto 或者 scroll 的节点
|
||||
*/
|
||||
scrollNode?: Node | (() => Node | null);
|
||||
/**
|
||||
* 懒惰渲染卡片(仅限已启用 lazyRender 的卡片),默认为 true
|
||||
*/
|
||||
lazyRender?: boolean;
|
||||
};
|
||||
|
|
|
@ -1,33 +1,10 @@
|
|||
import {
|
||||
EngineInterface,
|
||||
EventListener,
|
||||
NodeInterface,
|
||||
TypingHandleInterface,
|
||||
} from '../../types';
|
||||
import { NodeInterface, TypingHandleInterface } from '../../types';
|
||||
import { $ } from '../../node';
|
||||
import DefaultKeydown from './default';
|
||||
|
||||
class Backspace implements TypingHandleInterface {
|
||||
class Backspace extends DefaultKeydown implements TypingHandleInterface {
|
||||
type: 'keydown' | 'keyup' = 'keydown';
|
||||
hotkey: Array<string> | string = 'backspace';
|
||||
private engine: EngineInterface;
|
||||
listeners: Array<EventListener> = [];
|
||||
|
||||
constructor(engine: EngineInterface) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
on(listener: EventListener) {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
off(listener: EventListener) {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i] === listener) {
|
||||
this.listeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trigger(event: KeyboardEvent) {
|
||||
const { change, container } = this.engine;
|
||||
|
@ -137,10 +114,6 @@ class Backspace implements TypingHandleInterface {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
||||
|
||||
export default Backspace;
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
import {
|
||||
EngineInterface,
|
||||
EventListener,
|
||||
TypingEventListener,
|
||||
TypingHandleInterface,
|
||||
} from '../../types';
|
||||
|
||||
class DefaultKeydown implements TypingHandleInterface {
|
||||
type: 'keydown' | 'keyup' = 'keydown';
|
||||
hotkey: string | string[] | ((event: KeyboardEvent) => boolean) = '';
|
||||
listeners: Array<EventListener> = [];
|
||||
private engine: EngineInterface;
|
||||
listeners: Array<TypingEventListener> = [];
|
||||
engine: EngineInterface;
|
||||
|
||||
constructor(engine: EngineInterface) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
on(listener: EventListener) {
|
||||
on(listener: TypingEventListener) {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
off(listener: EventListener) {
|
||||
unshiftOn(listener: TypingEventListener) {
|
||||
this.listeners.unshift(listener);
|
||||
}
|
||||
|
||||
off(listener: TypingEventListener) {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i] === listener) {
|
||||
this.listeners.splice(i, 1);
|
||||
|
|
|
@ -1,35 +1,12 @@
|
|||
import {
|
||||
EngineInterface,
|
||||
EventListener,
|
||||
RangeInterface,
|
||||
TypingHandleInterface,
|
||||
} from '../../types';
|
||||
import { RangeInterface, TypingHandleInterface } from '../../types';
|
||||
import { CARD_KEY } from '../../constants';
|
||||
import Range from '../../range';
|
||||
import { $ } from '../../node';
|
||||
import DefaultKeydown from './default';
|
||||
|
||||
class Delete implements TypingHandleInterface {
|
||||
private engine: EngineInterface;
|
||||
class Delete extends DefaultKeydown implements TypingHandleInterface {
|
||||
type: 'keydown' | 'keyup' = 'keydown';
|
||||
hotkey: string | string[] | ((event: KeyboardEvent) => boolean) = 'delete';
|
||||
listeners: Array<EventListener> = [];
|
||||
|
||||
constructor(engine: EngineInterface) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
on(listener: EventListener) {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
off(listener: EventListener) {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i] === listener) {
|
||||
this.listeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getNext(node: Node): Node | null {
|
||||
return $(node).isEditable()
|
||||
|
@ -153,8 +130,5 @@ class Delete implements TypingHandleInterface {
|
|||
if (result === false) break;
|
||||
}
|
||||
}
|
||||
destroy(): void {
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
||||
export default Delete;
|
||||
|
|
|
@ -1,31 +1,9 @@
|
|||
import {
|
||||
EngineInterface,
|
||||
EventListener,
|
||||
TypingHandleInterface,
|
||||
} from '../../types';
|
||||
import { TypingHandleInterface } from '../../types';
|
||||
import DefaultKeydown from './default';
|
||||
|
||||
class Enter implements TypingHandleInterface {
|
||||
class Enter extends DefaultKeydown implements TypingHandleInterface {
|
||||
type: 'keydown' | 'keyup' = 'keydown';
|
||||
hotkey: Array<string> | string = 'enter';
|
||||
listeners: Array<EventListener> = [];
|
||||
private engine: EngineInterface;
|
||||
|
||||
constructor(engine: EngineInterface) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
on(listener: EventListener) {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
off(listener: EventListener) {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i] === listener) {
|
||||
this.listeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trigger(event: KeyboardEvent) {
|
||||
const { change } = this.engine;
|
||||
|
@ -51,10 +29,6 @@ class Enter implements TypingHandleInterface {
|
|||
);
|
||||
this.engine.trigger('select');
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
||||
|
||||
export default Enter;
|
||||
|
|
|
@ -1,39 +1,12 @@
|
|||
import isHotkey from 'is-hotkey';
|
||||
import { EventListener, TypingHandleInterface } from '../../types';
|
||||
|
||||
class Left implements TypingHandleInterface {
|
||||
import { TypingHandleInterface } from '../../types';
|
||||
import Default from './default';
|
||||
class Left extends Default implements TypingHandleInterface {
|
||||
type: 'keydown' | 'keyup' = 'keydown';
|
||||
hotkey = (event: KeyboardEvent) =>
|
||||
isHotkey('left', event) ||
|
||||
isHotkey('shift+left', event) ||
|
||||
isHotkey('ctrl+a', event) ||
|
||||
isHotkey('ctrl+b', event);
|
||||
|
||||
listeners: Array<EventListener> = [];
|
||||
|
||||
on(listener: EventListener) {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
off(listener: EventListener) {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i] === listener) {
|
||||
this.listeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trigger(event: KeyboardEvent) {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
const listener = this.listeners[i];
|
||||
const result = listener(event);
|
||||
if (result === false) break;
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
||||
export default Left;
|
||||
|
|
|
@ -1,48 +1,13 @@
|
|||
import isHotkey from 'is-hotkey';
|
||||
import {
|
||||
EngineInterface,
|
||||
EventListener,
|
||||
TypingHandleInterface,
|
||||
} from '../../types';
|
||||
import { TypingHandleInterface } from '../../types';
|
||||
import DefaultKeydown from './default';
|
||||
|
||||
class Right implements TypingHandleInterface {
|
||||
class Right extends DefaultKeydown implements TypingHandleInterface {
|
||||
type: 'keydown' | 'keyup' = 'keydown';
|
||||
hotkey = (event: KeyboardEvent) =>
|
||||
isHotkey('right', event) ||
|
||||
isHotkey('shift+right', event) ||
|
||||
isHotkey('ctrl+e', event) ||
|
||||
isHotkey('ctrl+f', event);
|
||||
|
||||
private engine: EngineInterface;
|
||||
listeners: Array<EventListener> = [];
|
||||
|
||||
constructor(engine: EngineInterface) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
on(listener: EventListener) {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
off(listener: EventListener) {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i] === listener) {
|
||||
this.listeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trigger(event: KeyboardEvent) {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
const listener = this.listeners[i];
|
||||
const result = listener(event);
|
||||
if (result === false) break;
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
||||
export default Right;
|
||||
|
|
|
@ -1,33 +1,11 @@
|
|||
import {
|
||||
EngineInterface,
|
||||
EventListener,
|
||||
TypingHandleInterface,
|
||||
} from '../../types';
|
||||
import { TypingHandleInterface } from '../../types';
|
||||
import { $ } from '../../node';
|
||||
import DefaultKeydown from './default';
|
||||
|
||||
class ShitEnter implements TypingHandleInterface {
|
||||
private engine: EngineInterface;
|
||||
class ShitEnter extends DefaultKeydown implements TypingHandleInterface {
|
||||
type: 'keydown' | 'keyup' = 'keydown';
|
||||
hotkey: string | string[] | ((event: KeyboardEvent) => boolean) =
|
||||
'shift+enter';
|
||||
listeners: Array<EventListener> = [];
|
||||
|
||||
constructor(engine: EngineInterface) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
on(listener: EventListener) {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
off(listener: EventListener) {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i] === listener) {
|
||||
this.listeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trigger(event: KeyboardEvent): void {
|
||||
const { change, inline, block } = this.engine;
|
||||
|
@ -68,9 +46,6 @@ class ShitEnter implements TypingHandleInterface {
|
|||
this.engine.scrollNode,
|
||||
);
|
||||
}
|
||||
destroy(): void {
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
||||
|
||||
export default ShitEnter;
|
||||
|
|
|
@ -1,36 +1,14 @@
|
|||
import { EngineInterface, TypingHandleInterface } from '../../types';
|
||||
import { TypingHandleInterface } from '../../types';
|
||||
import DefaultKeydown from './default';
|
||||
|
||||
class Tab implements TypingHandleInterface {
|
||||
private engine: EngineInterface;
|
||||
class Tab extends DefaultKeydown implements TypingHandleInterface {
|
||||
type: 'keydown' | 'keyup' = 'keydown';
|
||||
hotkey: string | string[] | ((event: KeyboardEvent) => boolean) = 'tab';
|
||||
listeners: Array<EventListener> = [];
|
||||
|
||||
constructor(engine: EngineInterface) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
on(listener: EventListener) {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
off(listener: EventListener) {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i] === listener) {
|
||||
this.listeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trigger(event: KeyboardEvent): void {
|
||||
const { node } = this.engine;
|
||||
event.preventDefault();
|
||||
node.insertText(' ');
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
||||
export default Tab;
|
||||
|
|
|
@ -1,30 +1,9 @@
|
|||
import {
|
||||
EngineInterface,
|
||||
EventListener,
|
||||
TypingHandleInterface,
|
||||
} from '../../types';
|
||||
import { TypingHandleInterface } from '../../types';
|
||||
import DefaultKeyup from './default';
|
||||
|
||||
class Backspace implements TypingHandleInterface {
|
||||
class Backspace extends DefaultKeyup implements TypingHandleInterface {
|
||||
type: 'keydown' | 'keyup' = 'keyup';
|
||||
hotkey: Array<string> | string = 'backspace';
|
||||
private engine: EngineInterface;
|
||||
listeners: Array<EventListener> = [];
|
||||
constructor(engine: EngineInterface) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
on(listener: EventListener) {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
off(listener: EventListener) {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i] === listener) {
|
||||
this.listeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trigger(event: KeyboardEvent) {
|
||||
const { change } = this.engine;
|
||||
|
@ -41,10 +20,6 @@ class Backspace implements TypingHandleInterface {
|
|||
if (result === false) break;
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
||||
|
||||
export default Backspace;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { EditorInterface, EngineInterface } from '../types';
|
||||
import { EditorInterface, EngineInterface, ViewInterface } from '../types';
|
||||
import TinyCanvas from './tiny-canvas';
|
||||
export * from './string';
|
||||
export * from './user-agent';
|
||||
|
@ -15,3 +15,10 @@ export const isEngine = (
|
|||
): editor is EngineInterface => {
|
||||
return editor.kind === 'engine';
|
||||
};
|
||||
/**
|
||||
* 是否是View
|
||||
* @param editor 编辑器
|
||||
*/
|
||||
export const isView = (editor: EditorInterface): editor is ViewInterface => {
|
||||
return editor.kind === 'view';
|
||||
};
|
||||
|
|
|
@ -1,152 +1,17 @@
|
|||
import NodeModel, { Event, $ } from './node';
|
||||
import language from './locales';
|
||||
import {
|
||||
EventInterface,
|
||||
NodeInterface,
|
||||
Selector,
|
||||
EventListener,
|
||||
NodeModelInterface,
|
||||
} from './types/node';
|
||||
import schemaDefaultData from './constants/schema';
|
||||
import Schema from './schema';
|
||||
import Conversion from './parser/conversion';
|
||||
import { ViewInterface, ContentViewOptions } from './types/view';
|
||||
import { CardModelInterface } from './types/card';
|
||||
import { PluginModelInterface } from './types/plugin';
|
||||
import { SchemaInterface } from './types/schema';
|
||||
import { ConversionInterface } from './types/conversion';
|
||||
import CardModel from './card';
|
||||
import PluginModel from './plugin';
|
||||
import { ClipboardInterface } from './types/clipboard';
|
||||
import Clipboard from './clipboard';
|
||||
import { LanguageInterface } from './types/language';
|
||||
import Language from './language';
|
||||
import { ViewInterface, ViewOptions } from './types/view';
|
||||
import Parser from './parser';
|
||||
import {
|
||||
CommandInterface,
|
||||
MarkModelInterface,
|
||||
NodeIdInterface,
|
||||
RequestInterface,
|
||||
} from './types';
|
||||
import { BlockModelInterface } from './types/block';
|
||||
import { InlineModelInterface } from './types/inline';
|
||||
import { ListModelInterface } from './types/list';
|
||||
import List from './list';
|
||||
import Mark from './mark';
|
||||
import Inline from './inline';
|
||||
import Block from './block';
|
||||
import Command from './command';
|
||||
import Request from './request';
|
||||
import NodeId from './node/id';
|
||||
import { DATA_ELEMENT, ROOT } from './constants';
|
||||
import Editor from './editor';
|
||||
import { Selector } from './types';
|
||||
|
||||
class View implements ViewInterface {
|
||||
private options: ContentViewOptions = {
|
||||
lang: 'zh-CN',
|
||||
plugins: [],
|
||||
cards: [],
|
||||
};
|
||||
class View<T extends ViewOptions = ViewOptions>
|
||||
extends Editor<T>
|
||||
implements ViewInterface<T>
|
||||
{
|
||||
readonly kind = 'view';
|
||||
root: NodeInterface;
|
||||
language: LanguageInterface;
|
||||
container: NodeInterface;
|
||||
card: CardModelInterface;
|
||||
plugin: PluginModelInterface;
|
||||
node: NodeModelInterface;
|
||||
list: ListModelInterface;
|
||||
mark: MarkModelInterface;
|
||||
inline: InlineModelInterface;
|
||||
block: BlockModelInterface;
|
||||
clipboard: ClipboardInterface;
|
||||
event: EventInterface;
|
||||
schema: SchemaInterface;
|
||||
conversion: ConversionInterface;
|
||||
command: CommandInterface;
|
||||
request: RequestInterface;
|
||||
nodeId: NodeIdInterface;
|
||||
#_scrollNode: NodeInterface | null = null;
|
||||
|
||||
constructor(selector: Selector, options?: ContentViewOptions) {
|
||||
this.options = { ...this.options, ...options };
|
||||
this.language = new Language(this.options.lang || 'zh-CN', language);
|
||||
this.event = new Event();
|
||||
this.command = new Command(this);
|
||||
this.schema = new Schema();
|
||||
this.schema.add(schemaDefaultData);
|
||||
this.conversion = new Conversion(this);
|
||||
this.card = new CardModel(this, this.options.lazyRender);
|
||||
this.clipboard = new Clipboard(this);
|
||||
this.plugin = new PluginModel(this);
|
||||
this.node = new NodeModel(this);
|
||||
this.nodeId = new NodeId(this);
|
||||
this.list = new List(this);
|
||||
this.mark = new Mark(this);
|
||||
this.inline = new Inline(this);
|
||||
this.block = new Block(this);
|
||||
this.clipboard = new Clipboard(this);
|
||||
this.request = new Request();
|
||||
this.container = $(selector);
|
||||
this.root = $(
|
||||
this.options.root || this.container.parent() || document.body,
|
||||
);
|
||||
this.container.addClass('am-engine-view');
|
||||
this.container.attributes(DATA_ELEMENT, ROOT);
|
||||
this.mark.init();
|
||||
this.inline.init();
|
||||
this.block.init();
|
||||
this.list.init();
|
||||
this.card.init(this.options.cards || []);
|
||||
this.plugin.init(this.options.plugins || [], this.options.config || {});
|
||||
this.nodeId.init();
|
||||
}
|
||||
|
||||
setScrollNode(node?: HTMLElement) {
|
||||
this.#_scrollNode = node ? $(node) : null;
|
||||
}
|
||||
|
||||
get scrollNode(): NodeInterface | null {
|
||||
if (this.#_scrollNode) return this.#_scrollNode;
|
||||
const { scrollNode } = this.options;
|
||||
let sn = scrollNode
|
||||
? typeof scrollNode === 'function'
|
||||
? scrollNode()
|
||||
: scrollNode
|
||||
: null;
|
||||
// 查找父级样式 overflow 或者 overflow-y 为 auto 或者 scroll 的节点
|
||||
const targetValues = ['auto', 'scroll'];
|
||||
let parent = this.container.parent();
|
||||
while (!sn && parent && parent.length > 0 && parent.name !== 'body') {
|
||||
if (
|
||||
targetValues.includes(parent.css('overflow')) ||
|
||||
targetValues.includes(parent.css('overflow-y'))
|
||||
) {
|
||||
sn = parent.get<HTMLElement>();
|
||||
break;
|
||||
} else {
|
||||
parent = parent.parent();
|
||||
}
|
||||
}
|
||||
if (sn === null) sn = document.documentElement;
|
||||
this.#_scrollNode = sn ? $(sn) : null;
|
||||
return this.#_scrollNode;
|
||||
}
|
||||
|
||||
on<R = any, F extends EventListener<R> = EventListener<R>>(
|
||||
eventType: string,
|
||||
listener: F,
|
||||
rewrite?: boolean,
|
||||
) {
|
||||
this.event.on(eventType, listener, rewrite);
|
||||
return this;
|
||||
}
|
||||
|
||||
off(eventType: string, listener: EventListener) {
|
||||
this.event.off(eventType, listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
trigger<R = any>(eventType: string, ...args: any): R {
|
||||
return this.event.trigger<R>(eventType, ...args);
|
||||
constructor(selector: Selector, options?: ViewOptions) {
|
||||
super(selector, options);
|
||||
this.init();
|
||||
}
|
||||
|
||||
render(content: string, trigger: boolean = true) {
|
||||
|
@ -157,19 +22,6 @@ class View implements ViewInterface {
|
|||
if (trigger) this.trigger('render', this.container);
|
||||
});
|
||||
}
|
||||
|
||||
messageSuccess(message: string) {
|
||||
console.log(`success:${message}`);
|
||||
}
|
||||
|
||||
messageError(error: string) {
|
||||
console.log(`error:${error}`);
|
||||
}
|
||||
|
||||
messageConfirm(message: string): Promise<boolean> {
|
||||
console.log(`confirm:${message}`);
|
||||
return Promise.reject(false);
|
||||
}
|
||||
}
|
||||
|
||||
export default View;
|
||||
|
|
|
@ -19,7 +19,7 @@ export default class Popup {
|
|||
constructor(editor: EditorInterface, options: PopupOptions = {}) {
|
||||
this.#options = options;
|
||||
this.#editor = editor;
|
||||
this.#root = $(`<div class="data-toolbar-popup-wrapper">Test</div>`);
|
||||
this.#root = $(`<div class="data-toolbar-popup-wrapper"></div>`);
|
||||
document.body.append(this.#root[0]);
|
||||
if (isEngine(editor)) {
|
||||
this.#editor.on('select', this.onSelect);
|
||||
|
@ -127,7 +127,7 @@ export default class Popup {
|
|||
destroy() {
|
||||
this.#root.remove();
|
||||
if (isEngine(this.#editor)) {
|
||||
this.#editor.on('select', this.onSelect);
|
||||
this.#editor.off('select', this.onSelect);
|
||||
} else {
|
||||
document.removeEventListener('selectionchange', this.onSelect);
|
||||
}
|
||||
|
|
|
@ -49,21 +49,22 @@ class ToolbarPlugin<
|
|||
static get pluginName() {
|
||||
return 'toolbar';
|
||||
}
|
||||
private popup?: ToolbarPopup;
|
||||
|
||||
init() {
|
||||
if (isEngine(this.editor)) {
|
||||
this.editor.on('keydown:slash', (event) => this.onSlash(event));
|
||||
this.editor.on('parse:value', (node) => this.paserValue(node));
|
||||
this.editor.on('keydown:slash', this.onSlash);
|
||||
this.editor.on('parse:value', this.paserValue);
|
||||
}
|
||||
this.editor.language.add(locales);
|
||||
if (this.options.popup) {
|
||||
new ToolbarPopup(this.editor, {
|
||||
this.popup = new ToolbarPopup(this.editor, {
|
||||
items: this.options.popup.items,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
paserValue(node: NodeInterface) {
|
||||
paserValue = (node: NodeInterface) => {
|
||||
if (
|
||||
node.isCard() &&
|
||||
node.attributes('name') === ToolbarComponent.cardName
|
||||
|
@ -71,9 +72,9 @@ class ToolbarPlugin<
|
|||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
onSlash(event: KeyboardEvent) {
|
||||
onSlash = (event: KeyboardEvent) => {
|
||||
if (!isEngine(this.editor)) return;
|
||||
const { change } = this.editor;
|
||||
let range = change.range.get();
|
||||
|
@ -108,11 +109,17 @@ class ToolbarPlugin<
|
|||
change.range.select(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
execute(...args: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.popup?.destroy();
|
||||
this.editor.off('keydown:slash', this.onSlash);
|
||||
this.editor.off('parse:value', this.paserValue);
|
||||
}
|
||||
}
|
||||
export { ToolbarComponent };
|
||||
export type { ToolbarValue };
|
||||
|
|
|
@ -19,7 +19,7 @@ export default class Popup {
|
|||
constructor(editor: EditorInterface, options: PopupOptions = {}) {
|
||||
this.#options = options;
|
||||
this.#editor = editor;
|
||||
this.#root = $(`<div class="data-toolbar-popup-wrapper">Test</div>`);
|
||||
this.#root = $(`<div class="data-toolbar-popup-wrapper"></div>`);
|
||||
document.body.append(this.#root[0]);
|
||||
if (isEngine(editor)) {
|
||||
this.#editor.on('select', this.onSelect);
|
||||
|
@ -32,6 +32,7 @@ export default class Popup {
|
|||
}
|
||||
|
||||
onSelect = () => {
|
||||
if (this.#root.length === 0) return;
|
||||
const range = Range.from(this.#editor)
|
||||
?.cloneRange()
|
||||
.shrinkToTextNode();
|
||||
|
@ -48,10 +49,8 @@ export default class Popup {
|
|||
return;
|
||||
}
|
||||
const subRanges = range.getSubRanges();
|
||||
if (
|
||||
subRanges.length === 0 ||
|
||||
(this.#editor.card.active && !this.#editor.card.active.isEditable)
|
||||
) {
|
||||
const activeCard = this.#editor.card.active;
|
||||
if (subRanges.length === 0 || (activeCard && !activeCard.isEditable)) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
@ -132,7 +131,7 @@ export default class Popup {
|
|||
destroy() {
|
||||
this.#root.remove();
|
||||
if (isEngine(this.#editor)) {
|
||||
this.#editor.on('select', this.onSelect);
|
||||
this.#editor.off('select', this.onSelect);
|
||||
} else {
|
||||
document.removeEventListener('selectionchange', this.onSelect);
|
||||
}
|
||||
|
|
|
@ -54,20 +54,22 @@ class ToolbarPlugin<
|
|||
return 'toolbar';
|
||||
}
|
||||
|
||||
private popup?: ToolbarPopup;
|
||||
|
||||
init() {
|
||||
if (isEngine(this.editor)) {
|
||||
this.editor.on('keydown:slash', (event) => this.onSlash(event));
|
||||
this.editor.on('parse:value', (node) => this.paserValue(node));
|
||||
this.editor.on('keydown:slash', this.onSlash);
|
||||
this.editor.on('parse:value', this.paserValue);
|
||||
}
|
||||
this.editor.language.add(locales);
|
||||
if (this.options.popup) {
|
||||
new ToolbarPopup(this.editor, {
|
||||
this.popup = new ToolbarPopup(this.editor, {
|
||||
items: this.options.popup.items,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
paserValue(node: NodeInterface) {
|
||||
paserValue = (node: NodeInterface) => {
|
||||
if (
|
||||
node.isCard() &&
|
||||
node.attributes('name') === ToolbarComponent.cardName
|
||||
|
@ -75,9 +77,9 @@ class ToolbarPlugin<
|
|||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
onSlash(event: KeyboardEvent) {
|
||||
onSlash = (event: KeyboardEvent) => {
|
||||
if (!isEngine(this.editor)) return;
|
||||
const { change } = this.editor;
|
||||
let range = change.range.get();
|
||||
|
@ -113,11 +115,17 @@ class ToolbarPlugin<
|
|||
change.range.select(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
execute(...args: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.popup?.destroy();
|
||||
this.editor.off('keydown:slash', this.onSlash);
|
||||
this.editor.off('parse:value', this.paserValue);
|
||||
}
|
||||
}
|
||||
export { ToolbarComponent, ToolbarPopup };
|
||||
export type { ToolbarValue };
|
||||
|
|
|
@ -34,7 +34,11 @@ export default class<T extends AlignmentOptions> extends ElementPlugin<T> {
|
|||
|
||||
init() {
|
||||
super.init();
|
||||
this.editor.on('keydown:backspace', (event) => this.onBackspace(event));
|
||||
this.editor.on('keydown:backspace', this.onBackspace);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.editor.off('keydown:backspace', this.onBackspace);
|
||||
}
|
||||
|
||||
execute(align?: 'left' | 'center' | 'right' | 'justify') {
|
||||
|
@ -93,7 +97,7 @@ export default class<T extends AlignmentOptions> extends ElementPlugin<T> {
|
|||
];
|
||||
}
|
||||
|
||||
onBackspace(event: KeyboardEvent) {
|
||||
onBackspace = (event: KeyboardEvent) => {
|
||||
if (!isEngine(this.editor)) return;
|
||||
const { change, block } = this.editor;
|
||||
const range = change.range.get();
|
||||
|
@ -123,5 +127,5 @@ export default class<T extends AlignmentOptions> extends ElementPlugin<T> {
|
|||
return false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {
|
||||
import Engine, {
|
||||
$,
|
||||
CardEntry,
|
||||
DATA_TRANSIENT_ATTRIBUTES,
|
||||
|
@ -16,6 +16,9 @@ import {
|
|||
EDITABLE_SELECTOR,
|
||||
CARD_SELECTOR,
|
||||
transformCustomTags,
|
||||
isView,
|
||||
View,
|
||||
EditorInterface,
|
||||
} from '@aomao/engine';
|
||||
import { Path } from 'sharedb';
|
||||
|
||||
|
@ -93,7 +96,7 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
this.editor.on('change', (_, trigger) => {
|
||||
this.triggerChange(trigger !== 'local');
|
||||
});
|
||||
this.editor.on('select', () => this.onSelectionChange());
|
||||
this.editor.on('select', this.onSelectionChange);
|
||||
this.editor.on('parse:value', (node, atts) => {
|
||||
const key = node.attributes(this.MARK_KEY);
|
||||
if (!!key) {
|
||||
|
@ -129,10 +132,10 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
}
|
||||
return;
|
||||
});
|
||||
} else {
|
||||
} else if (isView(this.editor)) {
|
||||
this.editor.container.document?.addEventListener(
|
||||
'selectionchange',
|
||||
() => this.onSelectionChange(),
|
||||
this.onSelectionChange,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -553,7 +556,7 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
* 光标选择改变触发
|
||||
* @returns
|
||||
*/
|
||||
onSelectionChange() {
|
||||
onSelectionChange = () => {
|
||||
if (this.executeBySelf) return;
|
||||
const { window } = this.editor.container;
|
||||
const selection = window?.getSelection();
|
||||
|
@ -577,7 +580,7 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
const selectInfo = this.getSelectInfo(range, true);
|
||||
this.editor.trigger(`${PLUGIN_NAME}:select`, range, selectInfo);
|
||||
this.range = range;
|
||||
}
|
||||
};
|
||||
|
||||
triggerChange(remote: boolean = false) {
|
||||
const addIds: { [key: string]: Array<string> } = {};
|
||||
|
@ -635,7 +638,6 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
value: string;
|
||||
paths: Array<{ id: Array<string>; path: Array<Path> }>;
|
||||
} {
|
||||
const { node, card } = this.editor;
|
||||
const container = this.editor.container.clone(value ? false : true);
|
||||
container.css({
|
||||
position: 'fixed',
|
||||
|
@ -643,20 +645,23 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
clip: 'rect(0, 0, 0, 0)',
|
||||
});
|
||||
$(document.body).append(container);
|
||||
|
||||
const editor: EditorInterface = isEngine(this.editor)
|
||||
? new Engine(container, this.editor.options)
|
||||
: new View(container, this.editor.options);
|
||||
|
||||
const { node, card } = editor;
|
||||
if (value) container.html(transformCustomTags(value));
|
||||
|
||||
card.render(container, undefined, false);
|
||||
|
||||
const selection = container.window?.getSelection();
|
||||
const range = (
|
||||
selection
|
||||
? Range.from(this.editor, selection) ||
|
||||
Range.create(this.editor)
|
||||
: Range.create(this.editor)
|
||||
? Range.from(editor, selection) || Range.create(editor)
|
||||
: Range.create(editor)
|
||||
).cloneRange();
|
||||
|
||||
const parser = new Parser(container, this.editor, undefined, false);
|
||||
const { schema, conversion } = this.editor;
|
||||
const parser = new Parser(container, editor, undefined, false);
|
||||
const { schema, conversion } = editor;
|
||||
if (!range) {
|
||||
container.remove();
|
||||
return {
|
||||
|
@ -710,7 +715,7 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
cardNodes.each((_, index) => {
|
||||
const cardNode = cardNodes.eq(index);
|
||||
if (cardNode?.isEditableCard()) {
|
||||
const card = this.editor.card.find(cardNode);
|
||||
const card = editor.card.find(cardNode);
|
||||
if (card) {
|
||||
const value = card.getValue();
|
||||
card.setValue(value || {});
|
||||
|
@ -718,6 +723,7 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
}
|
||||
});
|
||||
value = parser.toValue(schema, conversion);
|
||||
editor.destroy();
|
||||
container.remove();
|
||||
return {
|
||||
value,
|
||||
|
@ -735,7 +741,6 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
paths: Array<{ id: Array<string>; path: Array<Path> }>,
|
||||
value?: string,
|
||||
): string {
|
||||
const { card } = this.editor;
|
||||
const container = this.editor.container.clone(value ? false : true);
|
||||
if (value) value = Selection.removeTags(value);
|
||||
container.css({
|
||||
|
@ -744,19 +749,21 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
clip: 'rect(0, 0, 0, 0)',
|
||||
});
|
||||
$(document.body).append(container);
|
||||
const editor: EditorInterface = isEngine(this.editor)
|
||||
? new Engine(container, this.editor.options)
|
||||
: new View(container, this.editor.options);
|
||||
const { card } = editor;
|
||||
if (value) container.html(transformCustomTags(value));
|
||||
|
||||
card.render(container, undefined, false);
|
||||
const selection = container.window?.getSelection();
|
||||
const range = (
|
||||
selection
|
||||
? Range.from(this.editor, selection) ||
|
||||
Range.create(this.editor)
|
||||
: Range.create(this.editor)
|
||||
? Range.from(editor, selection) || Range.create(editor)
|
||||
: Range.create(editor)
|
||||
).cloneRange();
|
||||
|
||||
const parser = new Parser(container, this.editor, undefined, false);
|
||||
const { schema, conversion } = this.editor;
|
||||
const parser = new Parser(container, editor, undefined, false);
|
||||
const { schema, conversion } = editor;
|
||||
if (!range) {
|
||||
container.remove();
|
||||
return value ? value : parser.toValue(schema, conversion);
|
||||
|
@ -766,7 +773,7 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
|
||||
(paths || []).forEach(({ id, path }) => {
|
||||
const pathRange = Range.fromPath(
|
||||
this.editor,
|
||||
editor,
|
||||
{
|
||||
start: { path: path[0] as number[], id: '', bi: -1 },
|
||||
end: { path: path[1] as number[], id: '', bi: -1 },
|
||||
|
@ -795,7 +802,7 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
}
|
||||
}
|
||||
});
|
||||
this.editor.mark.wrap(
|
||||
editor.mark.wrap(
|
||||
`<${this.tagName} ${this.MARK_KEY}="${key}" ${this.getIdName(
|
||||
key,
|
||||
)}="${id.join(',')}" />`,
|
||||
|
@ -806,7 +813,7 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
cardNodes.each((_, index) => {
|
||||
const cardNode = cardNodes.eq(index);
|
||||
if (cardNode?.isEditableCard()) {
|
||||
const card = this.editor.card.find(cardNode);
|
||||
const card = editor.card.find(cardNode);
|
||||
if (card) {
|
||||
const value = card.getValue();
|
||||
card.setValue(value || {});
|
||||
|
@ -814,7 +821,16 @@ export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
|||
}
|
||||
});
|
||||
value = parser.toValue(schema, conversion);
|
||||
editor.destroy();
|
||||
container.remove();
|
||||
return value;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.editor.off('select', this.onSelectionChange);
|
||||
this.editor.container.document?.removeEventListener(
|
||||
'selectionchange',
|
||||
this.onSelectionChange,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -195,9 +195,7 @@ this.engine.on('mention:render-item', (data, root) => {
|
|||
`mention:loading`: 自定渲染加载状态
|
||||
|
||||
```ts
|
||||
this.engine.on('mention:loading', (data, root) => {
|
||||
root.html(`<div>${data}</div>`);
|
||||
// or
|
||||
this.engine.on('mention:loading', (root) => {
|
||||
ReactDOM.render(
|
||||
<div className="data-mention-loading">Loading...</div>,
|
||||
root.get<HTMLElement>()!,
|
||||
|
|
|
@ -23,7 +23,7 @@ export interface CollapseComponentInterface {
|
|||
unbindEvents(): void;
|
||||
bindEvents(): void;
|
||||
remove(): void;
|
||||
render(target: NodeInterface, data: Array<MentionItem>): void;
|
||||
render(target: NodeInterface, data: Array<MentionItem> | true): void;
|
||||
}
|
||||
|
||||
class CollapseComponent implements CollapseComponentInterface {
|
||||
|
@ -221,7 +221,7 @@ class CollapseComponent implements CollapseComponentInterface {
|
|||
return this.root?.find('.data-mention-component-body');
|
||||
}
|
||||
|
||||
render(target: NodeInterface, data: Array<MentionItem>) {
|
||||
render(target: NodeInterface, data: Array<MentionItem> | true) {
|
||||
this.remove();
|
||||
this.root = $(
|
||||
`<div class="data-mention-component-list" ${DATA_ELEMENT}="${UI}"><div class="data-mention-component-body"></div></div>`,
|
||||
|
@ -232,13 +232,10 @@ class CollapseComponent implements CollapseComponentInterface {
|
|||
let body = this.getBody();
|
||||
let result = null;
|
||||
|
||||
if (
|
||||
CollapseComponent.renderLoading ||
|
||||
(result = this.engine.trigger('mention:loading', this.root))
|
||||
) {
|
||||
if (typeof data === 'boolean' && data === true) {
|
||||
result = CollapseComponent.renderLoading
|
||||
? CollapseComponent.renderLoading(this.root)
|
||||
: result;
|
||||
: this.engine.trigger('mention:loading', this.root);
|
||||
body = this.getBody();
|
||||
if (result) body?.append(result);
|
||||
} else if (data.filter((item) => !!item.key).length === 0) {
|
||||
|
|
|
@ -220,11 +220,11 @@ class Mention<T extends MentionValue = MentionValue> extends Card<T> {
|
|||
this.component?.render(this.root, defaultData);
|
||||
return;
|
||||
}
|
||||
//if (Mention.renderLoading) {
|
||||
// if (Mention.renderLoading) {
|
||||
CollapseComponent.renderLoading = Mention.renderLoading;
|
||||
this.component?.render(this.root, []);
|
||||
this.component?.render(this.root, true);
|
||||
CollapseComponent.renderLoading = undefined;
|
||||
//}
|
||||
// }
|
||||
Mention.search(keyword).then((data) => {
|
||||
this.component?.render(this.root, data);
|
||||
});
|
||||
|
@ -283,7 +283,7 @@ class Mention<T extends MentionValue = MentionValue> extends Card<T> {
|
|||
const children = this.#container.children();
|
||||
if (!mark) {
|
||||
// 移除所有标记
|
||||
this.editor.mark.unwrapByNodes(this.queryMarks());
|
||||
this.editor.mark.unwrapByNodes(this.queryMarks(false));
|
||||
this.setValue({
|
||||
marks: [] as string[],
|
||||
} as T);
|
||||
|
@ -301,7 +301,7 @@ class Mention<T extends MentionValue = MentionValue> extends Card<T> {
|
|||
} as T);
|
||||
} else {
|
||||
// 移除标记
|
||||
this.editor.mark.unwrapByNodes(this.queryMarks(), mark);
|
||||
this.editor.mark.unwrapByNodes(this.queryMarks(false), mark);
|
||||
const marks = this.queryMarks().map(
|
||||
(child) => child.get<HTMLElement>()?.outerHTML || '',
|
||||
);
|
||||
|
@ -311,12 +311,12 @@ class Mention<T extends MentionValue = MentionValue> extends Card<T> {
|
|||
}
|
||||
}
|
||||
|
||||
queryMarks() {
|
||||
queryMarks(clone: boolean = true) {
|
||||
if (!this.#container) return [];
|
||||
return this.#container
|
||||
.allChildren()
|
||||
.filter((child) => child.isElement())
|
||||
.map((c) => c.clone());
|
||||
.map((c) => (clone ? c.clone() : c));
|
||||
}
|
||||
|
||||
render(): string | void | NodeInterface {
|
||||
|
|
|
@ -177,7 +177,7 @@ class Status<T extends StatusValue = StatusValue> extends Card<T> {
|
|||
const children = this.#container.children();
|
||||
if (!mark) {
|
||||
// 移除所有标记
|
||||
this.editor.mark.unwrapByNodes(this.queryMarks());
|
||||
this.editor.mark.unwrapByNodes(this.queryMarks(false));
|
||||
this.setValue({
|
||||
marks: [] as string[],
|
||||
} as T);
|
||||
|
@ -199,7 +199,7 @@ class Status<T extends StatusValue = StatusValue> extends Card<T> {
|
|||
} as T);
|
||||
} else {
|
||||
// 移除标记
|
||||
this.editor.mark.unwrapByNodes(this.queryMarks(), mark);
|
||||
this.editor.mark.unwrapByNodes(this.queryMarks(false), mark);
|
||||
const marks = this.queryMarks().map(
|
||||
(child) => child.get<HTMLElement>()?.outerHTML || '',
|
||||
);
|
||||
|
@ -210,12 +210,12 @@ class Status<T extends StatusValue = StatusValue> extends Card<T> {
|
|||
this.#statusEditor?.updateActive(this.getColor());
|
||||
}
|
||||
|
||||
queryMarks() {
|
||||
queryMarks(clone: boolean = true) {
|
||||
if (!this.#container) return [];
|
||||
return this.#container
|
||||
.allChildren()
|
||||
.filter((child) => child.isElement())
|
||||
.map((c) => c.clone());
|
||||
.map((c) => (clone ? c.clone() : c));
|
||||
}
|
||||
|
||||
focusEditor() {
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
TableModel,
|
||||
TableModelCol,
|
||||
TableModelEmptyCol,
|
||||
TableOptions,
|
||||
} from '../types';
|
||||
import isInteger from 'lodash/isInteger';
|
||||
import {
|
||||
|
@ -12,6 +13,7 @@ import {
|
|||
isNode,
|
||||
NodeInterface,
|
||||
transformCustomTags,
|
||||
EditorInterface,
|
||||
} from '@aomao/engine';
|
||||
import Template from './template';
|
||||
|
||||
|
@ -21,6 +23,12 @@ class Helper implements HelperInterface {
|
|||
text: string;
|
||||
};
|
||||
|
||||
#editor: EditorInterface;
|
||||
|
||||
constructor(editor: EditorInterface) {
|
||||
this.#editor = editor;
|
||||
}
|
||||
|
||||
isEmptyModelCol(
|
||||
model: TableModelCol | TableModelEmptyCol,
|
||||
): model is TableModelEmptyCol {
|
||||
|
@ -251,7 +259,11 @@ class Helper implements HelperInterface {
|
|||
const $tr = trs.eq(index);
|
||||
if (!$tr) return;
|
||||
let height = parseInt($tr.css('height'));
|
||||
height = height || 35;
|
||||
height =
|
||||
height ||
|
||||
this.#editor.plugin.findPlugin<TableOptions>('table')?.options
|
||||
.rowMinHeight ||
|
||||
0;
|
||||
$tr.css('height', height + 'px');
|
||||
});
|
||||
//补充可编辑器区域
|
||||
|
@ -603,7 +615,11 @@ class Helper implements HelperInterface {
|
|||
const $tr = trs.eq(index);
|
||||
if (!$tr) return;
|
||||
let height = parseInt($tr.css('height'));
|
||||
height = height || 35;
|
||||
height =
|
||||
height ||
|
||||
this.#editor.plugin.findPlugin<TableOptions>('table')?.options
|
||||
.rowMinHeight ||
|
||||
0;
|
||||
$tr.css('height', height + 'px');
|
||||
});
|
||||
return table;
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
HelperInterface,
|
||||
TableCommandInterface,
|
||||
TableInterface,
|
||||
TableOptions,
|
||||
TableSelectionInterface,
|
||||
TableValue,
|
||||
TemplateInterface,
|
||||
|
@ -33,7 +34,7 @@ import { ColorTool, Palette } from './toolbar';
|
|||
|
||||
class TableComponent<V extends TableValue = TableValue>
|
||||
extends Card<V>
|
||||
implements TableInterface
|
||||
implements TableInterface<V>
|
||||
{
|
||||
readonly contenteditable: string[] = [
|
||||
`div${Template.TABLE_TD_CONTENT_CLASS}`,
|
||||
|
@ -65,13 +66,20 @@ class TableComponent<V extends TableValue = TableValue>
|
|||
}),
|
||||
);
|
||||
|
||||
colMinWidth =
|
||||
this.editor.plugin.findPlugin<TableOptions>('table')?.options
|
||||
.colMinWidth || 40;
|
||||
rowMinHeight =
|
||||
this.editor.plugin.findPlugin<TableOptions>('table')?.options
|
||||
.rowMinHeight || 35;
|
||||
|
||||
wrapper?: NodeInterface;
|
||||
helper: HelperInterface = new Helper();
|
||||
helper: HelperInterface = new Helper(this.editor);
|
||||
template: TemplateInterface = new Template(this);
|
||||
selection: TableSelectionInterface = new TableSelection(this.editor, this);
|
||||
conltrollBar: ControllBarInterface = new ControllBar(this.editor, this, {
|
||||
col_min_width: 40,
|
||||
row_min_height: 35,
|
||||
col_min_width: this.colMinWidth,
|
||||
row_min_height: this.rowMinHeight,
|
||||
});
|
||||
command: TableCommandInterface = new TableCommand(this.editor, this);
|
||||
scrollbar?: Scrollbar;
|
||||
|
@ -337,6 +345,38 @@ class TableComponent<V extends TableValue = TableValue>
|
|||
return toolbars;
|
||||
}
|
||||
|
||||
onSelectLeft(event: KeyboardEvent) {
|
||||
const { tableModel } = this.selection;
|
||||
if (!tableModel) return;
|
||||
for (let r = tableModel.rows - 1; r >= 0; r--) {
|
||||
for (let c = tableModel.cols - 1; c >= 0; c--) {
|
||||
const cell = tableModel.table[r][c];
|
||||
if (!this.helper.isEmptyModelCol(cell) && cell.element) {
|
||||
event.preventDefault();
|
||||
this.selection.focusCell(cell.element, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
onSelectRight(event: KeyboardEvent) {
|
||||
const { tableModel } = this.selection;
|
||||
if (!tableModel) return;
|
||||
for (let r = 0; r < tableModel.rows; r++) {
|
||||
for (let c = 0; c < tableModel.cols; c++) {
|
||||
const cell = tableModel.table[r][c];
|
||||
if (!this.helper.isEmptyModelCol(cell) && cell.element) {
|
||||
event.preventDefault();
|
||||
this.selection.focusCell(cell.element);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
updateAlign(event: MouseEvent, align: 'top' | 'middle' | 'bottom' = 'top') {
|
||||
event.preventDefault();
|
||||
this.conltrollBar.setAlign(align);
|
||||
|
@ -530,6 +570,7 @@ class TableComponent<V extends TableValue = TableValue>
|
|||
const tablePlugin = this.editor.plugin.components['table'];
|
||||
const tableOptions = tablePlugin?.options['overflow'] || {};
|
||||
if (this.viewport) {
|
||||
this.selection.refreshModel();
|
||||
const overflowLeftConfig = tableOptions['maxLeftWidth']
|
||||
? {
|
||||
onScrollX: (x: number) => {
|
||||
|
|
|
@ -16,17 +16,8 @@ import {
|
|||
} from '@aomao/engine';
|
||||
import TableComponent, { Template, Helper } from './component';
|
||||
import locales from './locale';
|
||||
import { TableInterface, TableValue } from './types';
|
||||
import { TableInterface, TableOptions, TableValue } from './types';
|
||||
import './index.css';
|
||||
export interface TableOptions extends PluginOptions {
|
||||
hotkey?: string | Array<string>;
|
||||
overflow?: {
|
||||
maxLeftWidth?: () => number;
|
||||
maxRightWidth?: () => number;
|
||||
};
|
||||
markdown?: boolean;
|
||||
}
|
||||
|
||||
class Table<T extends TableOptions = TableOptions> extends Plugin<T> {
|
||||
static get pluginName() {
|
||||
return 'table';
|
||||
|
@ -294,7 +285,7 @@ class Table<T extends TableOptions = TableOptions> extends Plugin<T> {
|
|||
if (width.endsWith('pt')) node.css(type, this.convertToPX(width));
|
||||
};
|
||||
const tables = root.find('table');
|
||||
const helper = new Helper();
|
||||
const helper = new Helper(this.editor);
|
||||
tables.each((_, index) => {
|
||||
let node = tables.eq(index);
|
||||
if (!node) return;
|
||||
|
@ -554,4 +545,4 @@ class Table<T extends TableOptions = TableOptions> extends Plugin<T> {
|
|||
export default Table;
|
||||
|
||||
export { TableComponent };
|
||||
export type { TableValue };
|
||||
export type { TableValue, TableOptions };
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
CardValue,
|
||||
ClipboardData,
|
||||
NodeInterface,
|
||||
PluginOptions,
|
||||
} from '@aomao/engine';
|
||||
import { EventEmitter2 } from 'eventemitter2';
|
||||
|
||||
|
@ -149,19 +150,33 @@ export type TableModel = {
|
|||
table: Array<Array<TableModelCol | TableModelEmptyCol>>;
|
||||
};
|
||||
|
||||
export interface TableInterface extends CardInterface {
|
||||
export interface TableInterface<V extends TableValue = TableValue>
|
||||
extends CardInterface<V> {
|
||||
wrapper?: NodeInterface;
|
||||
helper: HelperInterface;
|
||||
template: TemplateInterface;
|
||||
selection: TableSelectionInterface;
|
||||
conltrollBar: ControllBarInterface;
|
||||
command: TableCommandInterface;
|
||||
colMinWidth: number;
|
||||
rowMinHeight: number;
|
||||
/**
|
||||
* 渲染
|
||||
*/
|
||||
render(): string | NodeInterface | void;
|
||||
}
|
||||
|
||||
export interface TableOptions extends PluginOptions {
|
||||
hotkey?: string | Array<string>;
|
||||
overflow?: {
|
||||
maxLeftWidth?: () => number;
|
||||
maxRightWidth?: () => number;
|
||||
};
|
||||
colMinWidth?: number;
|
||||
rowMinHeight?: number;
|
||||
markdown?: boolean;
|
||||
}
|
||||
|
||||
export type ControllOptions = {
|
||||
col_min_width: number;
|
||||
row_min_height: number;
|
||||
|
|
Loading…
Reference in New Issue