diff --git a/examples/react/editor.tsx b/examples/react/editor.tsx index 2d974086..81d6f4ba 100644 --- a/examples/react/editor.tsx +++ b/examples/react/editor.tsx @@ -98,17 +98,7 @@ export default () => { readonly={isReadonly} onLoad={setEngine} toc={true} - ot={{ - url: `${wsUrl}${member ? '?uid=' + member.id : ''}`, - docId: 'demo', - onReady: (member) => { - if (member) - localStorage.setItem( - 'member', - JSON.stringify(member), - ); - }, - }} + ot={false} onSave={onSave} /> diff --git a/packages/engine/src/card/index.ts b/packages/engine/src/card/index.ts index 0abfd9ba..8207a13c 100644 --- a/packages/engine/src/card/index.ts +++ b/packages/engine/src/card/index.ts @@ -422,7 +422,7 @@ class CardModel implements CardModelInterface { } } - select(card: CardInterface, event?: MouseEvent) { + select(card: CardInterface, event?: MouseEvent | KeyboardEvent) { const editor = this.editor; if (!isEngine(editor)) return; if ( @@ -433,8 +433,12 @@ class CardModel implements CardModelInterface { if ( (range.startNode.closest(EDITABLE_SELECTOR).length > 0 && (!event || - !event?.target || - !this.closest(event.target as Node, false))) || + (event instanceof MouseEvent && + (!event.target || + !this.closest( + event.target as Node, + false, + ))))) || card.isEditable || card.isMaximize ) diff --git a/packages/engine/src/card/typing/down.ts b/packages/engine/src/card/typing/down.ts index 0c23e85c..7ea11f70 100644 --- a/packages/engine/src/card/typing/down.ts +++ b/packages/engine/src/card/typing/down.ts @@ -11,37 +11,44 @@ class Down { inline(component: CardInterface, event: KeyboardEvent) { const { change, card } = this.engine; const range = change.range.get(); - event.preventDefault(); - card.focusNextBlock(component, range, false); - change.range.select(range); - return false; + const next = component.root.next(); + if (next) { + event.preventDefault(); + card.focusNextBlock(component, range, false); + change.range.select(range); + return false; + } + return; } block(component: CardInterface, event: KeyboardEvent) { const { change, card } = this.engine; const range = change.range.get(); - - event.preventDefault(); - card.focusNextBlock(component, range, false); - change.range.select(range); - return false; + const next = component.root.next(); + if (next) { + event.preventDefault(); + card.focusNextBlock(component, range, false); + change.range.select(range); + return false; + } + return; } trigger(event: KeyboardEvent) { const { change, block, card } = this.engine; const range = change.range.get(); 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); - } + 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); } } + } + if (!singleCard) { return true; } if (isHotkey('shift+down', event)) { diff --git a/packages/engine/src/card/typing/left.ts b/packages/engine/src/card/typing/left.ts index 590c313c..1acaff4c 100644 --- a/packages/engine/src/card/typing/left.ts +++ b/packages/engine/src/card/typing/left.ts @@ -37,6 +37,8 @@ class Left { event.preventDefault(); if (isCenter) { card.select(false); + card.toolbarModel?.hide(); + card.activate(false); } else if (range.collapsed) { const cardComponent = this.engine.card.find(range.startNode); if (cardComponent && cardComponent.onSelectLeft) { @@ -44,9 +46,11 @@ class Left { } } if (!isCenter && singleSelectable !== false) { - this.engine.card.select(card); + this.engine.card.select(card, event); } else { card.focus(range, true); + card.select(false); + card.toolbarModel?.hide(); change.range.select(range); } return false; @@ -60,10 +64,14 @@ class Left { // 左侧光标 const cardLeft = range.commonAncestorNode.closest(CARD_LEFT_SELECTOR); if (cardLeft.length > 0) { - event.preventDefault(); - card.focusPrevBlock(component, range, false); - change.range.select(range); - return false; + const prev = component.root.prev(); + if (prev) { + event.preventDefault(); + card.focusPrevBlock(component, range, false); + change.range.select(range); + return false; + } + return; } // 右侧光标 const cardRight = range.commonAncestorNode.closest(CARD_RIGHT_SELECTOR); @@ -75,12 +83,14 @@ class Left { } } event.preventDefault(); - card.select(component); + card.select(component, event); return false; } if (card.getSingleSelectedCard(range)) { event.preventDefault(); component.focus(range, true); + component.select(false); + component.toolbarModel?.hide(); change.range.select(range); return false; } diff --git a/packages/engine/src/card/typing/right.ts b/packages/engine/src/card/typing/right.ts index 3e2b347d..c55bfad0 100644 --- a/packages/engine/src/card/typing/right.ts +++ b/packages/engine/src/card/typing/right.ts @@ -21,6 +21,8 @@ class Right { event.preventDefault(); if (isCenter) { card.select(false); + card.select(false); + card.toolbarModel?.hide(); } else if (range.collapsed) { const cardComponent = this.engine.card.find(range.startNode); if (cardComponent && cardComponent.onSelectRight) { @@ -28,9 +30,11 @@ class Right { } } if (!isCenter && singleSelectable !== false) { - this.engine.card.select(card); + this.engine.card.select(card, event); } else { card.focus(range, false); + card.select(false); + card.toolbarModel?.hide(); change.range.select(range); } return false; @@ -63,20 +67,26 @@ class Right { } } event.preventDefault(); - card.select(component); + card.select(component, event); return false; } // 右侧光标 const cardRight = range.commonAncestorNode.closest(CARD_RIGHT_SELECTOR); if (cardRight.length > 0) { - event.preventDefault(); - card.focusNextBlock(component, range, false); - change.range.select(range); - return false; + const next = component.root.next(); + if (next) { + event.preventDefault(); + card.focusNextBlock(component, range, false); + change.range.select(range); + return false; + } + return; } if (this.engine.card.getSingleSelectedCard(range)) { event.preventDefault(); component.focus(range, false); + component.select(false); + component.toolbarModel?.hide(); change.range.select(range); return false; } diff --git a/packages/engine/src/card/typing/up.ts b/packages/engine/src/card/typing/up.ts index e67a38c5..a4ee18fb 100644 --- a/packages/engine/src/card/typing/up.ts +++ b/packages/engine/src/card/typing/up.ts @@ -11,36 +11,44 @@ class Up { inline(component: CardInterface, event: KeyboardEvent) { const { change, card } = this.engine; const range = change.range.get(); - event.preventDefault(); - card.focusPrevBlock(component, range, false); - change.range.select(range); - return false; + const prev = component.root.prev(); + if (prev) { + event.preventDefault(); + card.focusPrevBlock(component, range, false); + change.range.select(range); + return false; + } + return; } block(component: CardInterface, event: KeyboardEvent) { const { change, card } = this.engine; const range = change.range.get(); - event.preventDefault(); - card.focusPrevBlock(component, range, false); - change.range.select(range); - return false; + const prev = component.root.prev(); + if (prev) { + event.preventDefault(); + card.focusPrevBlock(component, range, false); + change.range.select(range); + return false; + } + return; } trigger(event: KeyboardEvent) { const { change, card, block } = this.engine; const range = change.range.get(); - 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); - } + 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); } } + } + const singleCard = card.getSingleCard(range); + if (!singleCard) { return true; } if (isHotkey('shift+up', event)) { diff --git a/packages/engine/src/change/index.ts b/packages/engine/src/change/index.ts index 9032b6fc..e08e346e 100644 --- a/packages/engine/src/change/index.ts +++ b/packages/engine/src/change/index.ts @@ -413,7 +413,8 @@ class ChangeModel implements ChangeInterface { } let nextNode = firstNode.next(); let beforeNode = firstNode; - nodeApi.insert(firstNode, range); + const newRange = nodeApi.insert(firstNode, range); + if (newRange) range = newRange; while (nextNode && !nodeApi.isBlock(nextNode)) { if (range.startContainer.nodeType === Node.TEXT_NODE) range.enlargeToElementNode().collapse(false); diff --git a/packages/engine/src/change/range.ts b/packages/engine/src/change/range.ts index 91d82a04..9f364d8b 100644 --- a/packages/engine/src/change/range.ts +++ b/packages/engine/src/change/range.ts @@ -226,8 +226,9 @@ class ChangeRange implements ChangeRangeInterface { if (node.isCustomize(endNode) && endOffset === 0) { range.setEnd(endNode, 1); } + const otStopped = this.engine.ot.isStopped(); // 空节点添加br - if (startNode.name === 'p') { + if (startNode.name === 'p' && !otStopped) { if (startChildNodes.length === 0) startNode.append('
'); else if ( startChildNodes.length > 1 && @@ -239,6 +240,7 @@ class ChangeRange implements ChangeRangeInterface { } if ( !range.collapsed && + !otStopped && endNode.name === 'p' && endNode.children().length === 0 ) { @@ -248,6 +250,7 @@ class ChangeRange implements ChangeRangeInterface { // 列表节点没有子节点 if ( node.isList(startNode) && + !otStopped && (startChildren.length === 0 || startChildren[0].nodeName === 'BR') ) { const newNode = $('


'); @@ -257,7 +260,7 @@ class ChangeRange implements ChangeRangeInterface { startNode = newNode; } // 空列表添加br - if (startNode.name === 'li') { + if (startNode.name === 'li' && !otStopped) { if (startChildNodes.length === 0) { startNode.append('
'); } else if ( @@ -281,7 +284,7 @@ class ChangeRange implements ChangeRangeInterface { startNode.last()?.remove(); } } - if (!range.collapsed && endNode.name === 'li') { + if (!range.collapsed && endNode.name === 'li' && !otStopped) { const endChildNodes = endNode.children(); if (endChildNodes.length === 0) { endNode.append('
'); @@ -309,6 +312,7 @@ class ChangeRange implements ChangeRangeInterface { if ( startNode.isEditable() && + !otStopped && startNode.children().length === 0 && !this.engine.ot.isStopped ) { diff --git a/packages/engine/src/node/index.ts b/packages/engine/src/node/index.ts index b846f6aa..d9c75af9 100644 --- a/packages/engine/src/node/index.ts +++ b/packages/engine/src/node/index.ts @@ -656,6 +656,13 @@ class NodeModel implements NodeModelInterface { if (nodeDom.length === 0) return range; } else range.insertNode(node); } + if ( + node.nodeType === Node.ELEMENT_NODE && + ((node as Element).hasAttribute(READY_CARD_KEY) || + (node as Element).hasAttribute(CARD_KEY)) + ) { + return range.collapse(false); + } return range .select( node, diff --git a/packages/engine/src/position/index.ts b/packages/engine/src/position/index.ts index 72224d0e..01c75222 100644 --- a/packages/engine/src/position/index.ts +++ b/packages/engine/src/position/index.ts @@ -93,6 +93,7 @@ class Position { points[0] === rect.points[0] && points[1] === rect.points[1] ); }); + this.#container.attributes('data-placement', align); this.#onUpdate({ ...rect, align }); } }; diff --git a/packages/engine/src/range.ts b/packages/engine/src/range.ts index 55613e2b..97318a7e 100644 --- a/packages/engine/src/range.ts +++ b/packages/engine/src/range.ts @@ -108,7 +108,7 @@ class Range implements RangeInterface { } else if (startNode.name === 'br') { startNode.remove(); } - return this.base.insertNode(node); + this.base.insertNode(node); } isPointInRange(node: Node | NodeInterface, offset: number): boolean { diff --git a/packages/engine/src/toolbar/button.ts b/packages/engine/src/toolbar/button.ts index 630113e1..0464f555 100644 --- a/packages/engine/src/toolbar/button.ts +++ b/packages/engine/src/toolbar/button.ts @@ -28,15 +28,26 @@ export default class Button implements ButtonInterface { } } + getPlacement() { + const dataPlacement = + this.root.closest('.data-toolbar').attributes('data-placement') || + 'top'; + return dataPlacement.startsWith('top') ? 'top' : 'bottom'; + } + render(container: NodeInterface) { const { title, didMount, onClick } = this.options; container.append(this.root); if (title) { this.root.on('mouseenter', () => { + const placement = this.getPlacement(); Tooltip.show( this.root, typeof title === 'function' ? title() : title, + { + placement, + }, ); }); this.root.on('mouseleave', () => { diff --git a/packages/engine/src/toolbar/index.css b/packages/engine/src/toolbar/index.css index f8724857..e62e10d5 100644 --- a/packages/engine/src/toolbar/index.css +++ b/packages/engine/src/toolbar/index.css @@ -238,6 +238,7 @@ align-items: center; padding: 0 4px; cursor: pointer; + width: max-content; } .data-toolbar-switch:hover { diff --git a/packages/engine/src/toolbar/index.ts b/packages/engine/src/toolbar/index.ts index 42ce3491..78d1201f 100644 --- a/packages/engine/src/toolbar/index.ts +++ b/packages/engine/src/toolbar/index.ts @@ -32,6 +32,11 @@ class Toolbar implements ToolbarInterface { this.root = $(template()); } + getPlacement() { + const dataPlacement = this.root.attributes('data-placement') || 'top'; + return dataPlacement.startsWith('top') ? 'top' : 'bottom'; + } + addItems(node: NodeInterface) { this.options.items.forEach((options) => { let item; @@ -59,9 +64,13 @@ class Toolbar implements ToolbarInterface { const { title } = nodeOptions; if (title) { nodeItem.on('mouseenter', () => { + const placement = this.getPlacement(); Tooltip.show( nodeItem, typeof title === 'function' ? title() : title, + { + placement, + }, ); }); nodeItem.on('mouseleave', () => { diff --git a/packages/engine/src/toolbar/tooltip/index.ts b/packages/engine/src/toolbar/tooltip/index.ts index 2a206bd3..9bc0e81d 100644 --- a/packages/engine/src/toolbar/tooltip/index.ts +++ b/packages/engine/src/toolbar/tooltip/index.ts @@ -38,7 +38,10 @@ class Tooltip { const left = Math.round( window.pageXOffset + nodeRect.left + nodeWidth / 2 - width / 2, ); - const top = Math.round(window.pageYOffset + nodeRect.top - height - 2); + let top = Math.round(window.pageYOffset + nodeRect.top - height - 2); + if (options.placement === 'bottom') { + top += nodeRect.height + height + 2; + } root.css({ left: left + 'px', top: top + 'px', diff --git a/packages/engine/src/types/card.ts b/packages/engine/src/types/card.ts index 9fe985de..69115365 100644 --- a/packages/engine/src/types/card.ts +++ b/packages/engine/src/types/card.ts @@ -521,8 +521,9 @@ export interface CardModelInterface { /** * 选中卡片 * @param card 卡片 + * @param event 触发事件 */ - select(card: CardInterface): void; + select(card: CardInterface, event?: MouseEvent | KeyboardEvent): void; /** * 聚焦卡片 * @param card 卡片 diff --git a/packages/toolbar/src/plugin/component/popup.tsx b/packages/toolbar/src/plugin/component/popup.tsx index dd93957c..7f0b0e70 100644 --- a/packages/toolbar/src/plugin/component/popup.tsx +++ b/packages/toolbar/src/plugin/component/popup.tsx @@ -36,19 +36,44 @@ export default class Popup { const range = Range.from(this.#editor) ?.cloneRange() .shrinkToTextNode(); + const selection = window.getSelection(); if ( !range || !selection || !selection.focusNode || range.collapsed || + this.#editor.card.getSingleSelectedCard(range) || (!range.commonAncestorNode.inEditor() && !range.commonAncestorNode.isRoot()) ) { this.hide(); return; } - const subRanges = range.getSubRanges(); + const next = range.startNode.next(); + if ( + next?.isElement() && + Math.abs(range.endOffset - range.startOffset) === 1 + ) { + const component = this.#editor.card.closest(next); + if (component) { + this.hide(); + return; + } + } + const prev = range.startNode.prev(); + if ( + prev?.isElement() && + Math.abs(range.startOffset - range.endOffset) === 1 + ) { + const component = this.#editor.card.closest(prev); + if (component) { + this.hide(); + return; + } + } + + const subRanges = range.getSubRanges(true); const activeCard = this.#editor.card.active; if (subRanges.length === 0 || (activeCard && !activeCard.isEditable)) { this.hide(); diff --git a/plugins/codeblock-vue/src/component/editor.ts b/plugins/codeblock-vue/src/component/editor.ts index aa1d7090..0388c49d 100644 --- a/plugins/codeblock-vue/src/component/editor.ts +++ b/plugins/codeblock-vue/src/component/editor.ts @@ -132,15 +132,14 @@ class CodeBlockEditor implements CodeBlockEditorInterface { }, }); this.codeMirror.on('keydown', (editor, event) => { - console.log(editor.getCursor(), editor.lineCount()); const lineCount = editor.lineCount(); const { line, ch } = editor.getCursor(); const { onUpFocus, onDownFocus, onLeftFocus, onRightFocus } = this.options; + + const content = editor.getLine(line); // 在最后一行 - if (line === lineCount - 1) { - const content = editor.getLine(line); - if (ch !== content.length) return; + if (line === lineCount - 1 && ch === content.length) { // 按下下键 if (isHotkey('down', event) || isHotkey('ctrl+n', event)) { if (onDownFocus) onDownFocus(event); diff --git a/plugins/codeblock-vue/src/component/index.ts b/plugins/codeblock-vue/src/component/index.ts index dbd41be8..82a09bfc 100644 --- a/plugins/codeblock-vue/src/component/index.ts +++ b/plugins/codeblock-vue/src/component/index.ts @@ -94,9 +94,13 @@ class CodeBlcok extends Card { const cardComponent = prev ? card.find(prev) : undefined; if (cardComponent?.onSelectUp) { cardComponent.onSelectUp(event); - } else { + } else if (prev) { card.focusPrevBlock(this, range, false); change.range.select(range); + } else { + this.focus(range, true); + change.range.select(range); + return; } this.activate(false); this.toolbarModel?.hide(); @@ -110,9 +114,13 @@ class CodeBlcok extends Card { const cardComponent = next ? card.find(next) : undefined; if (cardComponent?.onSelectDown) { cardComponent.onSelectDown(event); - } else { + } else if (next) { card.focusNextBlock(this, range, false); change.range.select(range); + } else { + this.focus(range, false); + change.range.select(range); + return; } this.activate(false); this.toolbarModel?.hide(); diff --git a/plugins/codeblock/src/component/editor.ts b/plugins/codeblock/src/component/editor.ts index 6a20f139..7d1b537d 100644 --- a/plugins/codeblock/src/component/editor.ts +++ b/plugins/codeblock/src/component/editor.ts @@ -96,15 +96,14 @@ class CodeBlockEditor implements CodeBlockEditorInterface { if (onFocus) onFocus(); }); this.codeMirror.on('keydown', (editor, event) => { - console.log(editor.getCursor(), editor.lineCount()); const lineCount = editor.lineCount(); const { line, ch } = editor.getCursor(); const { onUpFocus, onDownFocus, onLeftFocus, onRightFocus } = this.options; + + const content = editor.getLine(line); // 在最后一行 - if (line === lineCount - 1) { - const content = editor.getLine(line); - if (ch !== content.length) return; + if (line === lineCount - 1 && ch === content.length) { // 按下下键 if (isHotkey('down', event) || isHotkey('ctrl+n', event)) { if (onDownFocus) onDownFocus(event); diff --git a/plugins/codeblock/src/component/index.ts b/plugins/codeblock/src/component/index.ts index 6fde486c..29888a82 100644 --- a/plugins/codeblock/src/component/index.ts +++ b/plugins/codeblock/src/component/index.ts @@ -91,9 +91,13 @@ class CodeBlcok extends Card { const cardComponent = prev ? card.find(prev) : undefined; if (cardComponent?.onSelectUp) { cardComponent.onSelectUp(event); - } else { + } else if (prev) { card.focusPrevBlock(this, range, false); change.range.select(range); + } else { + this.focus(range, true); + change.range.select(range); + return; } this.activate(false); this.toolbarModel?.hide(); @@ -107,9 +111,13 @@ class CodeBlcok extends Card { const cardComponent = next ? card.find(next) : undefined; if (cardComponent?.onSelectDown) { cardComponent.onSelectDown(event); - } else { + } else if (next) { card.focusNextBlock(this, range, false); change.range.select(range); + } else { + this.focus(range, false); + change.range.select(range); + return; } this.activate(false); this.toolbarModel?.hide(); diff --git a/plugins/file/src/component/index.css b/plugins/file/src/component/index.css index 7c09fd68..480bc60b 100644 --- a/plugins/file/src/component/index.css +++ b/plugins/file/src/component/index.css @@ -4,6 +4,8 @@ line-height: 24px; cursor: pointer; white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .data-file .data-file-icon { flex: auto; diff --git a/plugins/table/src/component/index.ts b/plugins/table/src/component/index.ts index b2c9f8ef..cf9c7be0 100644 --- a/plugins/table/src/component/index.ts +++ b/plugins/table/src/component/index.ts @@ -100,11 +100,13 @@ class TableComponent this.editor.event.listeners['keydown:tab'].unshift( (event: KeyboardEvent) => { if (!isEngine(this.editor)) return; - const { change, block, node } = this.editor; + const { change, block, node, card } = this.editor; const range = change.range.get(); const td = range.endNode.closest('td'); if (td.length === 0) return; + const component = card.closest(td, true); + if (!component?.equal(this.root)) return; const closestBlock = block.closest(range.endNode); if ( td.length > 0 && @@ -150,6 +152,8 @@ class TableComponent const range = change.range.get(); const td = range.endNode.closest('td'); if (td.length === 0) return; + const component = card.closest(td, true); + if (!component?.equal(this.root)) return; const contentElement = td.find('.table-main-content'); if (!contentElement) return; const tdRect = contentElement @@ -216,6 +220,8 @@ class TableComponent const range = change.range.get(); const td = range.endNode.closest('td'); if (td.length === 0) return; + const component = card.closest(td, true); + if (!component?.equal(this.root)) return; const contentElement = td.find('.table-main-content'); if (!contentElement) return; const tdRect = contentElement @@ -276,11 +282,13 @@ class TableComponent // 左键选择 this.editor.on('keydown:left', () => { if (!isEngine(this.editor)) return; - const { change } = this.editor; + const { change, card } = this.editor; const range = change.range.get(); const td = range.endNode.closest('td'); if (td.length === 0) return; + const component = card.closest(td, true); + if (!component?.equal(this.root)) return; const contentElement = td.find('.table-main-content'); if (!contentElement) return; if (td.length > 0) { @@ -292,11 +300,13 @@ class TableComponent // 右键选择 this.editor.on('keydown:right', () => { if (!isEngine(this.editor)) return; - const { change } = this.editor; + const { change, card } = this.editor; const range = change.range.get(); const td = range.endNode.closest('td'); if (td.length === 0) return; + const component = card.closest(td, true); + if (!component?.equal(this.root)) return; const contentElement = td.find('.table-main-content'); if (!contentElement) return; if (td.length > 0) { diff --git a/plugins/table/src/index.css b/plugins/table/src/index.css index c5f36e7c..7b33c13f 100644 --- a/plugins/table/src/index.css +++ b/plugins/table/src/index.css @@ -469,12 +469,15 @@ div[data-card-key="table"].card-selected .data-table, div[data-card-key="table"] } .table-wrapper .table-main-content { - margin: 4px 8px; + margin: 4px 4px; + padding: 0 2px; position: relative; z-index: 3; + overflow: hidden; + text-overflow: ellipsis; } -.table-wrapper .table-main-content * { +.table-wrapper .table-main-content [data-card-key] { max-width: 100%; }