From cb6e0dd7e972f8c3779b57d75758bd85c9dbb73d Mon Sep 17 00:00:00 2001
From: yanmao <55792257+yanmao-cc@users.noreply.github.com>
Date: Thu, 30 Dec 2021 17:49:32 +0800
Subject: [PATCH] update
---
docs/plugin/plugin-mention.md | 4 +-
docs/plugin/plugin-mention.zh-CN.md | 4 +-
packages/engine/src/card/entry.ts | 10 +-
packages/engine/src/card/index.ts | 4 +-
packages/engine/src/card/maximize/index.ts | 9 +-
packages/engine/src/card/resize/index.ts | 2 +-
packages/engine/src/card/toolbar/index.ts | 8 +-
packages/engine/src/card/typing/down.ts | 24 +-
packages/engine/src/card/typing/left.ts | 11 +
packages/engine/src/card/typing/right.ts | 11 +
packages/engine/src/card/typing/up.ts | 24 +-
packages/engine/src/clipboard.ts | 3 +-
packages/engine/src/command.ts | 4 +-
packages/engine/src/editor.ts | 206 ++++++++
packages/engine/src/engine/index.ts | 189 +-------
packages/engine/src/index.ts | 2 +
packages/engine/src/inline/index.ts | 11 +-
packages/engine/src/node/event.ts | 8 +
packages/engine/src/parser/index.ts | 2 +-
packages/engine/src/plugin/base.ts | 5 +-
packages/engine/src/plugin/block.ts | 7 +-
packages/engine/src/plugin/element.ts | 8 +-
packages/engine/src/plugin/index.ts | 46 +-
packages/engine/src/plugin/inline.ts | 7 +-
packages/engine/src/plugin/list/index.ts | 6 +-
packages/engine/src/plugin/mark.ts | 7 +-
packages/engine/src/range.ts | 2 +-
packages/engine/src/types/block.ts | 5 +-
packages/engine/src/types/card.ts | 29 +-
packages/engine/src/types/editor.ts | 433 +++++++++++++++++
packages/engine/src/types/engine.ts | 444 +-----------------
packages/engine/src/types/index.ts | 1 +
packages/engine/src/types/inline.ts | 9 +-
packages/engine/src/types/list.ts | 4 +-
packages/engine/src/types/mark.ts | 9 +-
packages/engine/src/types/node.ts | 4 +
packages/engine/src/types/plugin.ts | 31 +-
packages/engine/src/types/range.ts | 2 +-
packages/engine/src/types/typing.ts | 13 +-
packages/engine/src/types/view.ts | 44 +-
.../engine/src/typing/keydown/backspace.ts | 33 +-
packages/engine/src/typing/keydown/default.ts | 14 +-
packages/engine/src/typing/keydown/delete.ts | 32 +-
packages/engine/src/typing/keydown/enter.ts | 32 +-
packages/engine/src/typing/keydown/left.ts | 33 +-
packages/engine/src/typing/keydown/right.ts | 41 +-
.../engine/src/typing/keydown/shift-enter.ts | 31 +-
packages/engine/src/typing/keydown/tab.ts | 28 +-
packages/engine/src/typing/keyup/backspace.ts | 31 +-
packages/engine/src/utils/index.ts | 9 +-
packages/engine/src/view.ts | 168 +------
.../toolbar-vue/src/plugin/component/popup.ts | 4 +-
packages/toolbar-vue/src/plugin/index.ts | 21 +-
.../toolbar/src/plugin/component/popup.tsx | 11 +-
packages/toolbar/src/plugin/index.ts | 22 +-
plugins/alignment/src/index.ts | 10 +-
plugins/mark-range/src/index.ts | 66 ++-
plugins/mention/README.md | 4 +-
plugins/mention/src/component/collapse.ts | 11 +-
plugins/mention/src/component/index.ts | 14 +-
plugins/status/src/components/index.ts | 8 +-
plugins/table/src/component/helper.ts | 20 +-
plugins/table/src/component/index.ts | 49 +-
plugins/table/src/index.ts | 15 +-
plugins/table/src/types.ts | 17 +-
65 files changed, 1143 insertions(+), 1233 deletions(-)
create mode 100644 packages/engine/src/editor.ts
create mode 100644 packages/engine/src/types/editor.ts
diff --git a/docs/plugin/plugin-mention.md b/docs/plugin/plugin-mention.md
index 6f66a8ef..532cdbfd 100644
--- a/docs/plugin/plugin-mention.md
+++ b/docs/plugin/plugin-mention.md
@@ -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(`
${data}
`);
- // or
+this.engine.on('mention:loading', (root) => {
ReactDOM.render(
Loading...
,
root.get()!,
diff --git a/docs/plugin/plugin-mention.zh-CN.md b/docs/plugin/plugin-mention.zh-CN.md
index a81ec0f4..99e8d9ae 100644
--- a/docs/plugin/plugin-mention.zh-CN.md
+++ b/docs/plugin/plugin-mention.zh-CN.md
@@ -195,9 +195,7 @@ this.engine.on('mention:render-item', (data, root) => {
`mention:loading`: 自定渲染加载状态
```ts
-this.engine.on('mention:loading', (data, root) => {
- root.html(`${data}
`);
- // or
+this.engine.on('mention:loading', (root) => {
ReactDOM.render(
Loading...
,
root.get()!,
diff --git a/packages/engine/src/card/entry.ts b/packages/engine/src/card/entry.ts
index e7e441b1..79c2d28d 100644
--- a/packages/engine/src/card/entry.ts
+++ b/packages/engine/src/card/entry.ts
@@ -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 implements CardInterface {
+abstract class CardEntry
+ implements CardInterface
+{
protected readonly editor: EditorInterface;
readonly root: NodeInterface;
toolbarModel?: CardToolbarInterface;
@@ -323,6 +325,10 @@ abstract class CardEntry implements CardInterface {
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();
diff --git a/packages/engine/src/card/index.ts b/packages/engine/src/card/index.ts
index 35110846..0abfd9ba 100644
--- a/packages/engine/src/card/index.ts
+++ b/packages/engine/src/card/index.ts
@@ -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()
diff --git a/packages/engine/src/card/maximize/index.ts b/packages/engine/src/card/maximize/index.ts
index 14656c79..b0fa5f09 100644
--- a/packages/engine/src/card/maximize/index.ts
+++ b/packages/engine/src/card/maximize/index.ts
@@ -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';
diff --git a/packages/engine/src/card/resize/index.ts b/packages/engine/src/card/resize/index.ts
index 5847aa0e..51c2e96e 100644
--- a/packages/engine/src/card/resize/index.ts
+++ b/packages/engine/src/card/resize/index.ts
@@ -63,7 +63,7 @@ class Resize implements ResizeInterface {
if (start) {
this.card.setValue({
height: container.height(),
- });
+ } as any);
start = false;
}
},
diff --git a/packages/engine/src/card/toolbar/index.ts b/packages/engine/src/card/toolbar/index.ts
index 131a187e..6b147607 100644
--- a/packages/engine/src/card/toolbar/index.ts
+++ b/packages/engine/src/card/toolbar/index.ts
@@ -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';
diff --git a/packages/engine/src/card/typing/down.ts b/packages/engine/src/card/typing/down.ts
index df6fbd0c..0c23e85c 100644
--- a/packages/engine/src/card/typing/down.ts
+++ b/packages/engine/src/card/typing/down.ts
@@ -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;
diff --git a/packages/engine/src/card/typing/left.ts b/packages/engine/src/card/typing/left.ts
index 284a58e1..590c313c 100644
--- a/packages/engine/src/card/typing/left.ts
+++ b/packages/engine/src/card/typing/left.ts
@@ -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;
diff --git a/packages/engine/src/card/typing/right.ts b/packages/engine/src/card/typing/right.ts
index 527375d7..3e2b347d 100644
--- a/packages/engine/src/card/typing/right.ts
+++ b/packages/engine/src/card/typing/right.ts
@@ -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;
diff --git a/packages/engine/src/card/typing/up.ts b/packages/engine/src/card/typing/up.ts
index 0bd2845b..e67a38c5 100644
--- a/packages/engine/src/card/typing/up.ts
+++ b/packages/engine/src/card/typing/up.ts
@@ -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;
diff --git a/packages/engine/src/clipboard.ts b/packages/engine/src/clipboard.ts
index b50ef642..8102b10a 100644
--- a/packages/engine/src/clipboard.ts
+++ b/packages/engine/src/clipboard.ts
@@ -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';
diff --git a/packages/engine/src/command.ts b/packages/engine/src/command.ts
index e49e7263..550b3813 100644
--- a/packages/engine/src/command.ts
+++ b/packages/engine/src/command.ts
@@ -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';
/**
diff --git a/packages/engine/src/editor.ts b/packages/engine/src/editor.ts
new file mode 100644
index 00000000..d6c0454f
--- /dev/null
+++ b/packages/engine/src/editor.ts
@@ -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
+ implements EditorInterface
+{
+ 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();
+ 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 = EventListener>(
+ 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(eventType: string, ...args: any): R {
+ return this.event.trigger(eventType, ...args);
+ }
+
+ messageSuccess(message: string) {
+ console.log(`success:${message}`);
+ }
+
+ messageError(error: string) {
+ console.log(`error:${error}`);
+ }
+
+ messageConfirm(message: string): Promise {
+ console.log(`confirm:${message}`);
+ return Promise.reject(false);
+ }
+
+ destroy() {
+ this.event.destroy();
+ this.plugin.destroy();
+ this.card.destroy();
+ this.container.empty();
+ }
+}
+
+export default Editor;
diff --git a/packages/engine/src/engine/index.ts b/packages/engine/src/engine/index.ts
index 591c0edb..d9b50a27 100644
--- a/packages/engine/src/engine/index.ts
+++ b/packages/engine/src/engine/index.ts
@@ -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
+ extends Editor
+ implements EngineInterface
+{
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();
- 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 = EventListener>(
- 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(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 {
- 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();
}
}
diff --git a/packages/engine/src/index.ts b/packages/engine/src/index.ts
index 9550145b..aa9ad611 100644
--- a/packages/engine/src/index.ts
+++ b/packages/engine/src/index.ts
@@ -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,
};
diff --git a/packages/engine/src/inline/index.ts b/packages/engine/src/inline/index.ts
index 2a850545..fb98972d 100644
--- a/packages/engine/src/inline/index.ts
+++ b/packages/engine/src/inline/index.ts
@@ -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';
diff --git a/packages/engine/src/node/event.ts b/packages/engine/src/node/event.ts
index ded0928a..27e3a8dc 100644
--- a/packages/engine/src/node/event.ts
+++ b/packages/engine/src/node/event.ts
@@ -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;
diff --git a/packages/engine/src/parser/index.ts b/packages/engine/src/parser/index.ts
index af5af2e1..f1c28aec 100644
--- a/packages/engine/src/parser/index.ts
+++ b/packages/engine/src/parser/index.ts
@@ -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,
diff --git a/packages/engine/src/plugin/base.ts b/packages/engine/src/plugin/base.ts
index a0d11e30..de6ddb66 100644
--- a/packages/engine/src/plugin/base.ts
+++ b/packages/engine/src/plugin/base.ts
@@ -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
+abstract class PluginEntry
implements PluginInterface
{
protected readonly editor: EditorInterface;
@@ -55,6 +55,7 @@ abstract class PluginEntry
...args: any
) => boolean | number | void,
): Promise;
+ destroy?(): void;
}
export default PluginEntry;
diff --git a/packages/engine/src/plugin/block.ts b/packages/engine/src/plugin/block.ts
index b61d040e..c22493ee 100644
--- a/packages/engine/src/plugin/block.ts
+++ b/packages/engine/src/plugin/block.ts
@@ -1,14 +1,15 @@
import ElementPluginEntry from './element';
-import {
+import type {
SchemaBlock,
BlockInterface,
NodeInterface,
PluginInterface,
+ PluginOptions,
} from '../types';
-abstract class BlockEntry
+abstract class BlockEntry
extends ElementPluginEntry
- implements BlockInterface
+ implements BlockInterface
{
readonly kind: string = 'block';
/**
diff --git a/packages/engine/src/plugin/element.ts b/packages/engine/src/plugin/element.ts
index 238df7aa..a0046c77 100644
--- a/packages/engine/src/plugin/element.ts
+++ b/packages/engine/src/plugin/element.ts
@@ -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
+abstract class ElementPluginEntry
extends PluginEntry
- implements ElementPluginInterface
+ implements ElementPluginInterface
{
readonly kind: string = 'element';
/**
diff --git a/packages/engine/src/plugin/index.ts b/packages/engine/src/plugin/index.ts
index 5ba148cb..752969eb 100644
--- a/packages/engine/src/plugin/index.ts
+++ b/packages/engine/src/plugin/index.ts
@@ -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(
+ pluginName: string,
+ ): PluginInterface | undefined {
const plugin = this.components[pluginName];
- return plugin;
+ if (!plugin) return;
+ return plugin as PluginInterface;
}
- findElementPlugin(pluginName: string) {
- const plugin = this.findPlugin(pluginName);
+ findElementPlugin(
+ pluginName: string,
+ ): ElementPluginInterface | undefined {
+ const plugin = this.findPlugin(pluginName);
+ if (!plugin) return;
if (isElementPlugin(plugin)) {
- return plugin as ElementPluginInterface;
+ return plugin as ElementPluginInterface;
}
return;
}
- findMarkPlugin(pluginName: string) {
+ findMarkPlugin(
+ pluginName: string,
+ ): MarkInterface | undefined {
const plugin = this.findPlugin(pluginName);
+ if (!plugin) return;
if (isMarkPlugin(plugin)) {
- return plugin as MarkInterface;
+ return plugin as MarkInterface;
}
return;
}
- findInlinePlugin(pluginName: string) {
+ findInlinePlugin(
+ pluginName: string,
+ ): InlineInterface | undefined {
const plugin = this.findPlugin(pluginName);
+ if (!plugin) return;
if (isInlinePlugin(plugin)) {
- return plugin as InlineInterface;
+ return plugin as InlineInterface;
}
return;
}
- findBlockPlugin(pluginName: string) {
+ findBlockPlugin(
+ pluginName: string,
+ ): BlockInterface | undefined {
const plugin = this.findPlugin(pluginName);
+ if (!plugin) return;
if (isBlockPlugin(plugin)) {
- return plugin as BlockInterface;
+ return plugin as BlockInterface;
}
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;
diff --git a/packages/engine/src/plugin/inline.ts b/packages/engine/src/plugin/inline.ts
index 56967955..0aa78cab 100644
--- a/packages/engine/src/plugin/inline.ts
+++ b/packages/engine/src/plugin/inline.ts
@@ -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
+abstract class InlineEntry
extends ElementPluginEntry
- implements InlineInterface
+ implements InlineInterface
{
readonly kind: string = 'inline';
/**
diff --git a/packages/engine/src/plugin/list/index.ts b/packages/engine/src/plugin/list/index.ts
index bb388993..c3704981 100644
--- a/packages/engine/src/plugin/list/index.ts
+++ b/packages/engine/src/plugin/list/index.ts
@@ -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
+abstract class ListEntry
extends BlockEntry
- implements ListInterface
+ implements ListInterface
{
cardName?: string;
private isPasteList: boolean = false;
diff --git a/packages/engine/src/plugin/mark.ts b/packages/engine/src/plugin/mark.ts
index ecc4351f..7d6b0092 100644
--- a/packages/engine/src/plugin/mark.ts
+++ b/packages/engine/src/plugin/mark.ts
@@ -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
+abstract class MarkEntry
extends ElementPluginEntry
- implements MarkInterface
+ implements MarkInterface
{
readonly kind: string = 'mark';
/**
diff --git a/packages/engine/src/range.ts b/packages/engine/src/range.ts
index 69c5f813..55613e2b 100644
--- a/packages/engine/src/range.ts
+++ b/packages/engine/src/range.ts
@@ -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';
diff --git a/packages/engine/src/types/block.ts b/packages/engine/src/types/block.ts
index d75783c4..f370ee9a 100644
--- a/packages/engine/src/types/block.ts
+++ b/packages/engine/src/types/block.ts
@@ -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
+ extends ElementPluginInterface {
readonly kind: string;
/**
* 标签名称
diff --git a/packages/engine/src/types/card.ts b/packages/engine/src/types/card.ts
index 2827ff35..9fe985de 100644
--- a/packages/engine/src/types/card.ts
+++ b/packages/engine/src/types/card.ts
@@ -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 {
+export interface CardOptions {
editor: EditorInterface;
value?: Partial;
root?: NodeInterface;
@@ -84,7 +84,7 @@ export type CardToolbarItemOptions =
items: Array;
};
-export interface CardEntry {
+export interface CardEntry {
prototype: CardInterface;
new (options: CardOptions): CardInterface;
/**
@@ -125,7 +125,7 @@ export interface CardEntry {
readonly lazyRender: boolean;
}
-export interface CardInterface {
+export interface CardInterface {
/**
* 初始化调用
*/
@@ -158,6 +158,9 @@ export interface CardInterface {
* 可编辑的节点
*/
readonly contenteditable: Array;
+ /**
+ * 卡片是否处于懒加载中
+ */
readonly loading: boolean;
/**
* 卡片类型,设置卡片类型会触发card重新渲染
@@ -254,6 +257,22 @@ export interface CardInterface {
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(
+ update(
selector: NodeInterface | Node | string,
value: Partial,
...args: any
diff --git a/packages/engine/src/types/editor.ts b/packages/engine/src/types/editor.ts
new file mode 100644
index 00000000..56f9991f
--- /dev/null
+++ b/packages/engine/src/types/editor.ts
@@ -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;
+ /**
+ * 插件配置
+ */
+ plugins?: Array;
+ /**
+ * 卡片配置
+ */
+ cards?: Array;
+ /**
+ * 插件选项,每个插件具体选项请在插件查看
+ */
+ config?: Record;
+ /**
+ * 阅读器根节点,默认为阅读器所在节点的父节点
+ */
+ root?: Node;
+ /**
+ * 滚动条节点,查找父级样式 overflow 或者 overflow-y 为 auto 或者 scroll 的节点
+ */
+ scrollNode?: Node | (() => Node | null);
+ /**
+ * 懒惰渲染卡片(仅限已启用 lazyRender 的卡片),默认为 true
+ */
+ lazyRender?: boolean;
+}
+
+export interface EditorInterface {
+ 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 = EventListener>(
+ 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,
+ ) => boolean | void,
+ rewrite?: boolean,
+ ): void;
+ /**
+ * 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
+ * @param value xml代码
+ */
+ on(
+ eventType: 'parse:value-after',
+ listener: (value: Array) => 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,
+ ) => boolean | void,
+ ): void;
+ /**
+ * 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
+ * @param value xml代码
+ */
+ off(
+ eventType: 'parse:value-after',
+ listener: (value: Array) => 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(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,
+ ): boolean | void;
+ /**
+ * 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
+ * @param value xml代码
+ */
+ trigger(eventType: 'parse:value-after', value: Array): 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;
+}
diff --git a/packages/engine/src/types/engine.ts b/packages/engine/src/types/engine.ts
index af3c1b00..30e40f7d 100644
--- a/packages/engine/src/types/engine.ts
+++ b/packages/engine/src/types/engine.ts
@@ -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 = EventListener>(
- 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,
- ) => boolean | void,
- rewrite?: boolean,
- ): void;
- /**
- * 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
- * @param value xml代码
- */
- on(
- eventType: 'parse:value-after',
- listener: (value: Array) => 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,
- ) => boolean | void,
- ): void;
- /**
- * 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
- * @param value xml代码
- */
- off(
- eventType: 'parse:value-after',
- listener: (value: Array) => 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(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,
- ): boolean | void;
- /**
- * 解析DOM节点,生成符合标准的 XML。生成xml代码结束后触发
- * @param value xml代码
- */
- trigger(eventType: 'parse:value-after', value: Array): 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;
-}
-
-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;
- /**
- * 卡片配置
- */
- cards?: Array;
- /**
- * 插件的可选项
- */
- config?: { [k: string]: PluginOptions };
/**
* 占位内容
*/
@@ -479,24 +62,21 @@ export type EngineOptions = {
* 是否只读
*/
readonly?: boolean;
- /**
- * 懒惰渲染卡片(仅限已启用 lazyRender 的卡片),默认为 true
- */
- lazyRender?: boolean;
-};
+}
-export interface Engine {
+export interface Engine {
/**
* 构造函数
*/
- new (selector: Selector, options?: EngineOptions): EngineInterface;
+ new (selector: Selector, options?: T): EngineInterface;
}
-export interface EngineInterface extends EditorInterface {
+export interface EngineInterface
+ extends EditorInterface {
/**
* 选项
*/
- options: EngineOptions;
+ options: T;
/**
* 是否只读
*/
diff --git a/packages/engine/src/types/index.ts b/packages/engine/src/types/index.ts
index 2d77dde0..7d87893a 100644
--- a/packages/engine/src/types/index.ts
+++ b/packages/engine/src/types/index.ts
@@ -23,3 +23,4 @@ export * from './tiny-canvas';
export * from './parser';
export * from './resizer';
export * from './position';
+export * from './editor';
diff --git a/packages/engine/src/types/inline.ts b/packages/engine/src/types/inline.ts
index 1b21df7e..0b5638e5 100644
--- a/packages/engine/src/types/inline.ts
+++ b/packages/engine/src/types/inline.ts
@@ -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
+ extends ElementPluginInterface {
readonly kind: string;
/**
* 标签名称
diff --git a/packages/engine/src/types/list.ts b/packages/engine/src/types/list.ts
index 9e90e617..3698a8d3 100644
--- a/packages/engine/src/types/list.ts
+++ b/packages/engine/src/types/list.ts
@@ -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
+ extends BlockInterface {
/**
* 自定义列表卡片名称
*/
diff --git a/packages/engine/src/types/mark.ts b/packages/engine/src/types/mark.ts
index 36da5033..bac83992 100644
--- a/packages/engine/src/types/mark.ts
+++ b/packages/engine/src/types/mark.ts
@@ -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
+ extends ElementPluginInterface {
readonly kind: string;
/**
* 标签名称
diff --git a/packages/engine/src/types/node.ts b/packages/engine/src/types/node.ts
index d575da82..81619ef6 100644
--- a/packages/engine/src/types/node.ts
+++ b/packages/engine/src/types/node.ts
@@ -38,6 +38,10 @@ export interface EventInterface {
* @param args 事件参数
*/
trigger(eventType: string, ...args: any): R;
+ /**
+ * 注销事件
+ */
+ destroy(): void;
}
export type Selector =
| string
diff --git a/packages/engine/src/types/plugin.ts b/packages/engine/src/types/plugin.ts
index 8c49bf63..a34825fa 100644
--- a/packages/engine/src/types/plugin.ts
+++ b/packages/engine/src/types/plugin.ts
@@ -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 {
+export interface PluginInterface {
readonly kind: string;
readonly name: string;
/**
@@ -77,9 +77,12 @@ export interface PluginInterface {
...args: any
) => boolean | number | void,
): Promise;
+
+ destroy?(): void;
}
-export interface ElementPluginInterface extends PluginInterface {
+export interface ElementPluginInterface
+ extends PluginInterface {
/**
* 标签名称
*/
@@ -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(
+ pluginName: string,
+ ): PluginInterface | undefined;
+ findElementPlugin(
+ pluginName: string,
+ ): ElementPluginInterface | undefined;
+ findMarkPlugin(
+ pluginName: string,
+ ): MarkInterface | undefined;
+ findInlinePlugin(
+ pluginName: string,
+ ): InlineInterface | undefined;
+ findBlockPlugin(
+ pluginName: string,
+ ): BlockInterface | undefined;
+
+ destroy(): void;
}
diff --git a/packages/engine/src/types/range.ts b/packages/engine/src/types/range.ts
index f8fb355a..af3b2bcc 100644
--- a/packages/engine/src/types/range.ts
+++ b/packages/engine/src/types/range.ts
@@ -1,5 +1,5 @@
import { Path } from 'sharedb';
-import { EditorInterface } from './engine';
+import { EditorInterface } from './editor';
import { NodeInterface } from './node';
import { SelectionInterface } from './selection';
diff --git a/packages/engine/src/types/typing.ts b/packages/engine/src/types/typing.ts
index 2b43e3f1..80cb9bc5 100644
--- a/packages/engine/src/types/typing.ts
+++ b/packages/engine/src/types/typing.ts
@@ -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;
+ listeners: Array;
/**
* 按键类型 键盘按下 | 键盘弹起
*/
@@ -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 键盘事件
diff --git a/packages/engine/src/types/view.ts b/packages/engine/src/types/view.ts
index 3137edc0..6a4292fd 100644
--- a/packages/engine/src/types/view.ts
+++ b/packages/engine/src/types/view.ts
@@ -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
+ extends EditorInterface {
+ 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;
- /**
- * 卡片配置
- */
- cards?: Array;
- /**
- * 插件选项,每个插件具体选项请在插件查看
- */
- config?: { [k: string]: {} };
- /**
- * 阅读器根节点,默认为阅读器所在节点的父节点
- */
- root?: Node;
- /**
- * 滚动条节点,查找父级样式 overflow 或者 overflow-y 为 auto 或者 scroll 的节点
- */
- scrollNode?: Node | (() => Node | null);
- /**
- * 懒惰渲染卡片(仅限已启用 lazyRender 的卡片),默认为 true
- */
- lazyRender?: boolean;
-};
diff --git a/packages/engine/src/typing/keydown/backspace.ts b/packages/engine/src/typing/keydown/backspace.ts
index 9fea694f..de7a1bcd 100644
--- a/packages/engine/src/typing/keydown/backspace.ts
+++ b/packages/engine/src/typing/keydown/backspace.ts
@@ -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 = 'backspace';
- private engine: EngineInterface;
- listeners: Array = [];
-
- 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;
diff --git a/packages/engine/src/typing/keydown/default.ts b/packages/engine/src/typing/keydown/default.ts
index 39bd3e75..a375b46d 100644
--- a/packages/engine/src/typing/keydown/default.ts
+++ b/packages/engine/src/typing/keydown/default.ts
@@ -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 = [];
- private engine: EngineInterface;
+ listeners: Array = [];
+ 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);
diff --git a/packages/engine/src/typing/keydown/delete.ts b/packages/engine/src/typing/keydown/delete.ts
index cac1a706..3967068b 100644
--- a/packages/engine/src/typing/keydown/delete.ts
+++ b/packages/engine/src/typing/keydown/delete.ts
@@ -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 = [];
-
- 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;
diff --git a/packages/engine/src/typing/keydown/enter.ts b/packages/engine/src/typing/keydown/enter.ts
index 751b2967..8b4bfaa9 100644
--- a/packages/engine/src/typing/keydown/enter.ts
+++ b/packages/engine/src/typing/keydown/enter.ts
@@ -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 = 'enter';
- listeners: Array = [];
- 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;
diff --git a/packages/engine/src/typing/keydown/left.ts b/packages/engine/src/typing/keydown/left.ts
index 04a28d15..8577b091 100644
--- a/packages/engine/src/typing/keydown/left.ts
+++ b/packages/engine/src/typing/keydown/left.ts
@@ -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 = [];
-
- 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;
diff --git a/packages/engine/src/typing/keydown/right.ts b/packages/engine/src/typing/keydown/right.ts
index 308dff85..50b31c1e 100644
--- a/packages/engine/src/typing/keydown/right.ts
+++ b/packages/engine/src/typing/keydown/right.ts
@@ -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 = [];
-
- 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;
diff --git a/packages/engine/src/typing/keydown/shift-enter.ts b/packages/engine/src/typing/keydown/shift-enter.ts
index 41053738..b18b2a67 100644
--- a/packages/engine/src/typing/keydown/shift-enter.ts
+++ b/packages/engine/src/typing/keydown/shift-enter.ts
@@ -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 = [];
-
- 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;
diff --git a/packages/engine/src/typing/keydown/tab.ts b/packages/engine/src/typing/keydown/tab.ts
index 18576370..37883dd1 100644
--- a/packages/engine/src/typing/keydown/tab.ts
+++ b/packages/engine/src/typing/keydown/tab.ts
@@ -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 = [];
-
- 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;
diff --git a/packages/engine/src/typing/keyup/backspace.ts b/packages/engine/src/typing/keyup/backspace.ts
index 3c815b1d..692fca78 100644
--- a/packages/engine/src/typing/keyup/backspace.ts
+++ b/packages/engine/src/typing/keyup/backspace.ts
@@ -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 = 'backspace';
- private engine: EngineInterface;
- listeners: Array = [];
- 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;
diff --git a/packages/engine/src/utils/index.ts b/packages/engine/src/utils/index.ts
index 6c5af09b..fc20b61c 100644
--- a/packages/engine/src/utils/index.ts
+++ b/packages/engine/src/utils/index.ts
@@ -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';
+};
diff --git a/packages/engine/src/view.ts b/packages/engine/src/view.ts
index bbec1c1f..c27eb534 100644
--- a/packages/engine/src/view.ts
+++ b/packages/engine/src/view.ts
@@ -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
+ extends Editor
+ implements ViewInterface
+{
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();
- break;
- } else {
- parent = parent.parent();
- }
- }
- if (sn === null) sn = document.documentElement;
- this.#_scrollNode = sn ? $(sn) : null;
- return this.#_scrollNode;
- }
-
- on = EventListener>(
- 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(eventType: string, ...args: any): R {
- return this.event.trigger(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 {
- console.log(`confirm:${message}`);
- return Promise.reject(false);
- }
}
export default View;
diff --git a/packages/toolbar-vue/src/plugin/component/popup.ts b/packages/toolbar-vue/src/plugin/component/popup.ts
index e6f38544..013e6645 100644
--- a/packages/toolbar-vue/src/plugin/component/popup.ts
+++ b/packages/toolbar-vue/src/plugin/component/popup.ts
@@ -19,7 +19,7 @@ export default class Popup {
constructor(editor: EditorInterface, options: PopupOptions = {}) {
this.#options = options;
this.#editor = editor;
- this.#root = $(``);
+ this.#root = $(``);
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);
}
diff --git a/packages/toolbar-vue/src/plugin/index.ts b/packages/toolbar-vue/src/plugin/index.ts
index 67e48cc8..d50659b0 100644
--- a/packages/toolbar-vue/src/plugin/index.ts
+++ b/packages/toolbar-vue/src/plugin/index.ts
@@ -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 };
diff --git a/packages/toolbar/src/plugin/component/popup.tsx b/packages/toolbar/src/plugin/component/popup.tsx
index 40fcf24b..dd93957c 100644
--- a/packages/toolbar/src/plugin/component/popup.tsx
+++ b/packages/toolbar/src/plugin/component/popup.tsx
@@ -19,7 +19,7 @@ export default class Popup {
constructor(editor: EditorInterface, options: PopupOptions = {}) {
this.#options = options;
this.#editor = editor;
- this.#root = $(``);
+ this.#root = $(``);
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);
}
diff --git a/packages/toolbar/src/plugin/index.ts b/packages/toolbar/src/plugin/index.ts
index 8f499643..597338ef 100644
--- a/packages/toolbar/src/plugin/index.ts
+++ b/packages/toolbar/src/plugin/index.ts
@@ -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 };
diff --git a/plugins/alignment/src/index.ts b/plugins/alignment/src/index.ts
index 8fe8eccb..d2a90bcd 100644
--- a/plugins/alignment/src/index.ts
+++ b/plugins/alignment/src/index.ts
@@ -34,7 +34,11 @@ export default class extends ElementPlugin {
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 extends ElementPlugin {
];
}
- 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 extends ElementPlugin {
return false;
}
return;
- }
+ };
}
diff --git a/plugins/mark-range/src/index.ts b/plugins/mark-range/src/index.ts
index 55eefd7e..df5a7698 100644
--- a/plugins/mark-range/src/index.ts
+++ b/plugins/mark-range/src/index.ts
@@ -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 extends MarkPlugin {
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 extends MarkPlugin {
}
return;
});
- } else {
+ } else if (isView(this.editor)) {
this.editor.container.document?.addEventListener(
'selectionchange',
- () => this.onSelectionChange(),
+ this.onSelectionChange,
);
}
}
@@ -553,7 +556,7 @@ export default class extends MarkPlugin {
* 光标选择改变触发
* @returns
*/
- onSelectionChange() {
+ onSelectionChange = () => {
if (this.executeBySelf) return;
const { window } = this.editor.container;
const selection = window?.getSelection();
@@ -577,7 +580,7 @@ export default class extends MarkPlugin {
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 } = {};
@@ -635,7 +638,6 @@ export default class extends MarkPlugin {
value: string;
paths: Array<{ id: Array; path: Array }>;
} {
- 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 extends MarkPlugin {
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 extends MarkPlugin {
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 extends MarkPlugin {
}
});
value = parser.toValue(schema, conversion);
+ editor.destroy();
container.remove();
return {
value,
@@ -735,7 +741,6 @@ export default class extends MarkPlugin {
paths: Array<{ id: Array; path: Array }>,
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 extends MarkPlugin {
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 extends MarkPlugin {
(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 extends MarkPlugin {
}
}
});
- 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 extends MarkPlugin {
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 extends MarkPlugin {
}
});
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,
+ );
+ }
}
diff --git a/plugins/mention/README.md b/plugins/mention/README.md
index a81ec0f4..99e8d9ae 100644
--- a/plugins/mention/README.md
+++ b/plugins/mention/README.md
@@ -195,9 +195,7 @@ this.engine.on('mention:render-item', (data, root) => {
`mention:loading`: 自定渲染加载状态
```ts
-this.engine.on('mention:loading', (data, root) => {
- root.html(`${data}
`);
- // or
+this.engine.on('mention:loading', (root) => {
ReactDOM.render(
Loading...
,
root.get()!,
diff --git a/plugins/mention/src/component/collapse.ts b/plugins/mention/src/component/collapse.ts
index 1c3cfbdf..0ea0c64c 100644
--- a/plugins/mention/src/component/collapse.ts
+++ b/plugins/mention/src/component/collapse.ts
@@ -23,7 +23,7 @@ export interface CollapseComponentInterface {
unbindEvents(): void;
bindEvents(): void;
remove(): void;
- render(target: NodeInterface, data: Array): void;
+ render(target: NodeInterface, data: Array | 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) {
+ render(target: NodeInterface, data: Array | true) {
this.remove();
this.root = $(
``,
@@ -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) {
diff --git a/plugins/mention/src/component/index.ts b/plugins/mention/src/component/index.ts
index cf9e991a..d449792b 100644
--- a/plugins/mention/src/component/index.ts
+++ b/plugins/mention/src/component/index.ts
@@ -220,11 +220,11 @@ class Mention extends Card {
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 extends Card {
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 extends Card {
} 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()?.outerHTML || '',
);
@@ -311,12 +311,12 @@ class Mention extends Card {
}
}
- 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 {
diff --git a/plugins/status/src/components/index.ts b/plugins/status/src/components/index.ts
index 85bb27b2..0a0110b3 100644
--- a/plugins/status/src/components/index.ts
+++ b/plugins/status/src/components/index.ts
@@ -177,7 +177,7 @@ class Status extends Card {
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 extends Card {
} 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()?.outerHTML || '',
);
@@ -210,12 +210,12 @@ class Status extends Card {
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() {
diff --git a/plugins/table/src/component/helper.ts b/plugins/table/src/component/helper.ts
index b8398abb..75023119 100644
--- a/plugins/table/src/component/helper.ts
+++ b/plugins/table/src/component/helper.ts
@@ -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('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('table')?.options
+ .rowMinHeight ||
+ 0;
$tr.css('height', height + 'px');
});
return table;
diff --git a/plugins/table/src/component/index.ts b/plugins/table/src/component/index.ts
index 37e5b39a..35743c55 100644
--- a/plugins/table/src/component/index.ts
+++ b/plugins/table/src/component/index.ts
@@ -19,6 +19,7 @@ import {
HelperInterface,
TableCommandInterface,
TableInterface,
+ TableOptions,
TableSelectionInterface,
TableValue,
TemplateInterface,
@@ -33,7 +34,7 @@ import { ColorTool, Palette } from './toolbar';
class TableComponent
extends Card
- implements TableInterface
+ implements TableInterface
{
readonly contenteditable: string[] = [
`div${Template.TABLE_TD_CONTENT_CLASS}`,
@@ -65,13 +66,20 @@ class TableComponent
}),
);
+ colMinWidth =
+ this.editor.plugin.findPlugin('table')?.options
+ .colMinWidth || 40;
+ rowMinHeight =
+ this.editor.plugin.findPlugin('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
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
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) => {
diff --git a/plugins/table/src/index.ts b/plugins/table/src/index.ts
index 865c03d0..dee8c900 100644
--- a/plugins/table/src/index.ts
+++ b/plugins/table/src/index.ts
@@ -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;
- overflow?: {
- maxLeftWidth?: () => number;
- maxRightWidth?: () => number;
- };
- markdown?: boolean;
-}
-
class Table extends Plugin {
static get pluginName() {
return 'table';
@@ -294,7 +285,7 @@ class Table extends Plugin {
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 extends Plugin {
export default Table;
export { TableComponent };
-export type { TableValue };
+export type { TableValue, TableOptions };
diff --git a/plugins/table/src/types.ts b/plugins/table/src/types.ts
index c5bf5793..9d159dbd 100644
--- a/plugins/table/src/types.ts
+++ b/plugins/table/src/types.ts
@@ -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>;
};
-export interface TableInterface extends CardInterface {
+export interface TableInterface
+ extends CardInterface {
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;
+ overflow?: {
+ maxLeftWidth?: () => number;
+ maxRightWidth?: () => number;
+ };
+ colMinWidth?: number;
+ rowMinHeight?: number;
+ markdown?: boolean;
+}
+
export type ControllOptions = {
col_min_width: number;
row_min_height: number;