From cc9a74de3b7dc78efffa4bb3c195024605496b79 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Thu, 18 Aug 2022 14:03:57 +0800 Subject: [PATCH 001/117] constants: update empty document data --- packages/constants/lib/index.js | 13 ++----------- packages/constants/src/index.ts | 15 ++------------- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/packages/constants/lib/index.js b/packages/constants/lib/index.js index c069d354..eafc1d0d 100644 --- a/packages/constants/lib/index.js +++ b/packages/constants/lib/index.js @@ -16,17 +16,8 @@ exports.EMPTY_DOCUMNENT = { content: JSON.stringify({ "default": { type: 'doc', - content: [{ type: 'title', content: [{ type: 'text', text: '未命名文档' }] }] + content: [{ type: 'title', content: [{ type: 'text', text: '' }] }] } }), - state: Buffer.from(new Uint8Array([ - 1, 14, 204, 224, 154, 225, 13, 0, 7, 1, 7, 100, 101, 102, 97, 117, 108, 116, 3, 5, 116, 105, 116, 108, 101, 1, 0, - 204, 224, 154, 225, 13, 0, 1, 0, 1, 135, 204, 224, 154, 225, 13, 0, 3, 9, 112, 97, 114, 97, 103, 114, 97, 112, - 104, 40, 0, 204, 224, 154, 225, 13, 3, 6, 105, 110, 100, 101, 110, 116, 1, 125, 0, 40, 0, 204, 224, 154, 225, 13, - 3, 9, 116, 101, 120, 116, 65, 108, 105, 103, 110, 1, 119, 4, 108, 101, 102, 116, 0, 4, 71, 204, 224, 154, 225, 13, - 1, 6, 1, 0, 204, 224, 154, 225, 13, 10, 3, 132, 204, 224, 154, 225, 13, 13, 3, 230, 156, 170, 129, 204, 224, 154, - 225, 13, 14, 6, 132, 204, 224, 154, 225, 13, 20, 6, 229, 145, 189, 229, 144, 141, 129, 204, 224, 154, 225, 13, 22, - 5, 132, 204, 224, 154, 225, 13, 27, 6, 230, 150, 135, 230, 161, 163, 1, 204, 224, 154, 225, 13, 5, 1, 2, 6, 4, 11, - 3, 15, 6, 23, 5, - ])) + state: Buffer.from(new Uint8Array([])) }; diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts index 40639593..e68afae3 100644 --- a/packages/constants/src/index.ts +++ b/packages/constants/src/index.ts @@ -17,19 +17,8 @@ export const EMPTY_DOCUMNENT = { content: JSON.stringify({ default: { type: 'doc', - content: [{ type: 'title', content: [{ type: 'text', text: '未命名文档' }] }], + content: [{ type: 'title', content: [{ type: 'text', text: '' }] }], }, }), - state: Buffer.from( - new Uint8Array([ - 1, 14, 204, 224, 154, 225, 13, 0, 7, 1, 7, 100, 101, 102, 97, 117, 108, 116, 3, 5, 116, 105, 116, 108, 101, 1, 0, - 204, 224, 154, 225, 13, 0, 1, 0, 1, 135, 204, 224, 154, 225, 13, 0, 3, 9, 112, 97, 114, 97, 103, 114, 97, 112, - 104, 40, 0, 204, 224, 154, 225, 13, 3, 6, 105, 110, 100, 101, 110, 116, 1, 125, 0, 40, 0, 204, 224, 154, 225, 13, - 3, 9, 116, 101, 120, 116, 65, 108, 105, 103, 110, 1, 119, 4, 108, 101, 102, 116, 0, 4, 71, 204, 224, 154, 225, 13, - 1, 6, 1, 0, 204, 224, 154, 225, 13, 10, 3, 132, 204, 224, 154, 225, 13, 13, 3, 230, 156, 170, 129, 204, 224, 154, - 225, 13, 14, 6, 132, 204, 224, 154, 225, 13, 20, 6, 229, 145, 189, 229, 144, 141, 129, 204, 224, 154, 225, 13, 22, - 5, 132, 204, 224, 154, 225, 13, 27, 6, 230, 150, 135, 230, 161, 163, 1, 204, 224, 154, 225, 13, 5, 1, 2, 6, 4, 11, - 3, 15, 6, 23, 5, - ]) - ), + state: Buffer.from(new Uint8Array([])), }; From 56738ea330b0394992a82a3b5fd924f5b0f16ad0 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Thu, 18 Aug 2022 14:04:19 +0800 Subject: [PATCH 002/117] tiptap: scroll to end and focus in empty title in edit mode --- .../core/extensions/scroll-into-view.ts | 8 +- .../src/tiptap/core/extensions/title.ts | 121 +++++++++++++----- packages/client/src/tiptap/core/index.tsx | 10 ++ .../collaboration/index.module.scss | 1 + .../src/tiptap/editor/collaboration/kit.ts | 1 + .../client/src/tiptap/prose-utils/node.ts | 1 + 6 files changed, 107 insertions(+), 35 deletions(-) diff --git a/packages/client/src/tiptap/core/extensions/scroll-into-view.ts b/packages/client/src/tiptap/core/extensions/scroll-into-view.ts index 4991ebc8..28f2cb0d 100644 --- a/packages/client/src/tiptap/core/extensions/scroll-into-view.ts +++ b/packages/client/src/tiptap/core/extensions/scroll-into-view.ts @@ -1,4 +1,5 @@ import { Editor, Extension } from '@tiptap/core'; +import { throttle } from 'helpers/throttle'; import { Plugin, PluginKey, Transaction } from 'prosemirror-state'; export const scrollIntoViewPluginKey = new PluginKey('scrollIntoViewPlugin'); @@ -8,7 +9,7 @@ type TransactionWithScroll = Transaction & { scrolledIntoView: boolean }; interface IScrollIntoViewOptions { /** * - * 将 markdown 转换为 html + * 滚动编辑器 */ onScroll: (editor: Editor) => void; } @@ -24,6 +25,9 @@ export const ScrollIntoView = Extension.create({ addProseMirrorPlugins() { const { editor } = this; + + const onScroll = this.options.onScroll ? throttle(this.options.onScroll, 200) : (editor) => {}; + return [ new Plugin({ key: scrollIntoViewPluginKey, @@ -38,7 +42,7 @@ export const ScrollIntoView = Extension.create({ tr.getMeta('scrollIntoView') !== false && tr.getMeta('addToHistory') !== false ) { - this.options.onScroll(editor); + onScroll(editor); return newState.tr.scrollIntoView(); } }, diff --git a/packages/client/src/tiptap/core/extensions/title.ts b/packages/client/src/tiptap/core/extensions/title.ts index 02c75b47..a92aaea7 100644 --- a/packages/client/src/tiptap/core/extensions/title.ts +++ b/packages/client/src/tiptap/core/extensions/title.ts @@ -2,7 +2,7 @@ import { mergeAttributes, Node } from '@tiptap/core'; import { ReactNodeViewRenderer } from '@tiptap/react'; import { Plugin, PluginKey, TextSelection } from 'prosemirror-state'; import { Decoration, DecorationSet } from 'prosemirror-view'; -import { getDatasetAttribute, isInTitle } from 'tiptap/prose-utils'; +import { getDatasetAttribute, isInTitle, nodeAttrsToDataset } from 'tiptap/prose-utils'; import { TitleWrapper } from '../wrappers/title'; @@ -21,11 +21,15 @@ declare module '@tiptap/core' { export const TitleExtensionName = 'title'; +const TitlePluginKey = new PluginKey(TitleExtensionName); + export const Title = Node.create({ name: TitleExtensionName, content: 'inline*', group: 'block', - selectable: true, + defining: true, + isolating: true, + showGapCursor: true, addOptions() { return { @@ -52,8 +56,19 @@ export const Title = Node.create({ ]; }, - renderHTML({ HTMLAttributes }) { - return ['h1', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]; + renderHTML({ HTMLAttributes, node }) { + const { cover } = node.attrs; + return [ + 'h1', + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, nodeAttrsToDataset(node)), + [ + 'img', + { + src: cover, + }, + ], + ['div', 0], + ]; }, addNodeView() { @@ -62,38 +77,11 @@ export const Title = Node.create({ addProseMirrorPlugins() { const { editor } = this; + let shouldSelectTitleNode = true; return [ new Plugin({ - key: new PluginKey(this.name), - props: { - handleKeyDown(view, evt) { - const { state, dispatch } = view; - - if (isInTitle(view.state) && evt.code === 'Enter') { - evt.preventDefault(); - - const paragraph = state.schema.nodes.paragraph; - - if (!paragraph) { - return; - } - - const $head = state.selection.$head; - const titleNode = $head.node($head.depth); - const endPos = ((titleNode.firstChild && titleNode.firstChild.nodeSize) || 0) + 1; - - dispatch(state.tr.insert(endPos, paragraph.create())); - - const newState = view.state; - const next = new TextSelection(newState.doc.resolve(endPos + 2)); - dispatch(newState.tr.setSelection(next)); - return true; - } - }, - }, - }), - new Plugin({ + key: TitlePluginKey, props: { decorations: (state) => { const { doc } = state; @@ -111,6 +99,73 @@ export const Title = Node.create({ return DecorationSet.create(doc, decorations); }, + handleClick() { + shouldSelectTitleNode = false; + return; + }, + handleDOMEvents: { + click() { + shouldSelectTitleNode = false; + return; + }, + mousedown() { + shouldSelectTitleNode = false; + return; + }, + pointerdown() { + shouldSelectTitleNode = false; + return; + }, + touchstart() { + shouldSelectTitleNode = false; + return; + }, + }, + handleKeyDown(view, evt) { + const { state, dispatch } = view; + shouldSelectTitleNode = false; + + if (isInTitle(view.state) && evt.code === 'Enter') { + evt.preventDefault(); + + const paragraph = state.schema.nodes.paragraph; + + if (!paragraph) { + return true; + } + + const $head = state.selection.$head; + const titleNode = $head.node($head.depth); + + const endPos = ((titleNode.firstChild && titleNode.firstChild.nodeSize) || 0) + 1; + + dispatch(state.tr.insert(endPos, paragraph.create())); + + const newState = view.state; + const next = new TextSelection(newState.doc.resolve(endPos + 2)); + dispatch(newState.tr.setSelection(next)); + + return true; + } + }, + }, + appendTransaction: (transactions, oldState, newState) => { + if (!editor.isEditable) return; + + if (!shouldSelectTitleNode) return; + + const tr = newState.tr; + + const firstNode = newState?.doc?.content?.content?.[0]; + + if (firstNode && firstNode.type.name === this.name && firstNode.nodeSize === 2) { + const selection = new TextSelection(newState.tr.doc.resolve(firstNode?.attrs?.cover ? 1 : 0)); + tr.setSelection(selection).scrollIntoView(); + tr.setMeta('addToHistory', false); + return tr; + } + + return; }, }), ]; diff --git a/packages/client/src/tiptap/core/index.tsx b/packages/client/src/tiptap/core/index.tsx index ec6459cd..538ea1c1 100644 --- a/packages/client/src/tiptap/core/index.tsx +++ b/packages/client/src/tiptap/core/index.tsx @@ -2,6 +2,7 @@ import { EditorOptions } from '@tiptap/core'; import { Editor as BuiltInEditor } from '@tiptap/react'; import { EditorContent, NodeViewContent, NodeViewWrapper } from '@tiptap/react'; import { EventEmitter } from 'helpers/event-emitter'; +import { throttle } from 'helpers/throttle'; import { DependencyList, useEffect, useState } from 'react'; function useForceUpdate() { @@ -36,6 +37,15 @@ export const useEditor = (options: Partial = {}, deps: Dependency setEditor(instance); + if (options.editable) { + instance.on( + 'update', + throttle(() => { + instance.chain().focus().scrollIntoView().run(); + }, 200) + ); + } + instance.on('transaction', () => { requestAnimationFrame(() => { requestAnimationFrame(() => { diff --git a/packages/client/src/tiptap/editor/collaboration/collaboration/index.module.scss b/packages/client/src/tiptap/editor/collaboration/collaboration/index.module.scss index 1d46b9d0..1da0c864 100644 --- a/packages/client/src/tiptap/editor/collaboration/collaboration/index.module.scss +++ b/packages/client/src/tiptap/editor/collaboration/collaboration/index.module.scss @@ -50,6 +50,7 @@ flex: 1; justify-content: center; flex-wrap: nowrap; + scroll-behavior: smooth; .contentWrap { width: 100%; diff --git a/packages/client/src/tiptap/editor/collaboration/kit.ts b/packages/client/src/tiptap/editor/collaboration/kit.ts index fa761d43..1c236848 100644 --- a/packages/client/src/tiptap/editor/collaboration/kit.ts +++ b/packages/client/src/tiptap/editor/collaboration/kit.ts @@ -1,4 +1,5 @@ import { Toast } from '@douyinfe/semi-ui'; +import scrollIntoView from 'scroll-into-view-if-needed'; // 自定义节点扩展 import { Attachment } from 'tiptap/core/extensions/attachment'; import { BackgroundColor } from 'tiptap/core/extensions/background-color'; diff --git a/packages/client/src/tiptap/prose-utils/node.ts b/packages/client/src/tiptap/prose-utils/node.ts index 7400f8d6..6dc8e975 100644 --- a/packages/client/src/tiptap/prose-utils/node.ts +++ b/packages/client/src/tiptap/prose-utils/node.ts @@ -61,6 +61,7 @@ export function isInCodeBlock(state: EditorState): boolean { } export function isInTitle(state: EditorState): boolean { + if (state?.selection?.$head?.pos === 0) return true; return isInCustomNode(state, 'title'); } From a7e787d758f2a7482fc7ec31c374a370a52ef381 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Fri, 19 Aug 2022 10:36:05 +0800 Subject: [PATCH 003/117] tiptap: ensure only one title node, do not create paragraph if next sibling is exists --- .../src/tiptap/core/extensions/title.ts | 96 ++++++++++++------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/packages/client/src/tiptap/core/extensions/title.ts b/packages/client/src/tiptap/core/extensions/title.ts index a92aaea7..63993262 100644 --- a/packages/client/src/tiptap/core/extensions/title.ts +++ b/packages/client/src/tiptap/core/extensions/title.ts @@ -2,7 +2,7 @@ import { mergeAttributes, Node } from '@tiptap/core'; import { ReactNodeViewRenderer } from '@tiptap/react'; import { Plugin, PluginKey, TextSelection } from 'prosemirror-state'; import { Decoration, DecorationSet } from 'prosemirror-view'; -import { getDatasetAttribute, isInTitle, nodeAttrsToDataset } from 'tiptap/prose-utils'; +import { getDatasetAttribute, getNodeAtPos, isInTitle, nodeAttrsToDataset } from 'tiptap/prose-utils'; import { TitleWrapper } from '../wrappers/title'; @@ -78,15 +78,29 @@ export const Title = Node.create({ addProseMirrorPlugins() { const { editor } = this; let shouldSelectTitleNode = true; + let editorView; + + const closeSelectTitleNode = () => { + shouldSelectTitleNode = false; + return; + }; return [ new Plugin({ key: TitlePluginKey, + view: (view) => { + editorView = view; + + return { + update(view) { + editorView = view; + }, + }; + }, props: { decorations: (state) => { const { doc } = state; const decorations = []; - doc.descendants((node, pos) => { if (node.type.name !== this.name) return; @@ -96,34 +110,22 @@ export const Title = Node.create({ }) ); }); - return DecorationSet.create(doc, decorations); }, handleClick() { - shouldSelectTitleNode = false; + closeSelectTitleNode(); return; }, handleDOMEvents: { - click() { - shouldSelectTitleNode = false; - return; - }, - mousedown() { - shouldSelectTitleNode = false; - return; - }, - pointerdown() { - shouldSelectTitleNode = false; - return; - }, - touchstart() { - shouldSelectTitleNode = false; - return; - }, + click: closeSelectTitleNode, + mousedown: closeSelectTitleNode, + pointerdown: closeSelectTitleNode, + touchstart: closeSelectTitleNode, }, handleKeyDown(view, evt) { const { state, dispatch } = view; - shouldSelectTitleNode = false; + + closeSelectTitleNode(); if (isInTitle(view.state) && evt.code === 'Enter') { evt.preventDefault(); @@ -136,10 +138,13 @@ export const Title = Node.create({ const $head = state.selection.$head; const titleNode = $head.node($head.depth); - const endPos = ((titleNode.firstChild && titleNode.firstChild.nodeSize) || 0) + 1; - dispatch(state.tr.insert(endPos, paragraph.create())); + const nextNode = getNodeAtPos(state, endPos + 2); + + if (!nextNode) { + dispatch(state.tr.insert(endPos, paragraph.create())); + } const newState = view.state; const next = new TextSelection(newState.doc.resolve(endPos + 2)); @@ -152,20 +157,45 @@ export const Title = Node.create({ appendTransaction: (transactions, oldState, newState) => { if (!editor.isEditable) return; - if (!shouldSelectTitleNode) return; - const tr = newState.tr; + let shouldReturnTr = false; - const firstNode = newState?.doc?.content?.content?.[0]; - - if (firstNode && firstNode.type.name === this.name && firstNode.nodeSize === 2) { - const selection = new TextSelection(newState.tr.doc.resolve(firstNode?.attrs?.cover ? 1 : 0)); - tr.setSelection(selection).scrollIntoView(); - tr.setMeta('addToHistory', false); - return tr; + if (shouldSelectTitleNode) { + const firstNode = newState?.doc?.content?.content?.[0]; + if (firstNode && firstNode.type.name === this.name && firstNode.nodeSize === 2) { + const selection = new TextSelection(newState.tr.doc.resolve(firstNode?.attrs?.cover ? 1 : 0)); + tr.setSelection(selection).scrollIntoView(); + tr.setMeta('addToHistory', false); + shouldReturnTr = true; + } } - return; + const newTitleNodes = (newState.tr.doc.content.content || []).filter((item) => item.type.name === this.name); + + if (newTitleNodes.length > 1) { + const oldTitleNode = (oldState.tr.doc.content.content || []).filter((item) => item.type.name === this.name); + + const otherNewNodes = (newState.tr.doc.content.content || []).filter( + (item) => item.type.name !== this.name + ); + + const fixedDoc = { + ...newState.tr.doc.toJSON(), + content: [].concat( + ((oldTitleNode && oldTitleNode[0]) || newTitleNodes[0]).toJSON(), + otherNewNodes.map((node) => node.toJSON()) + ), + }; + + tr.replaceWith(0, newState.doc.content.size, newState.schema.nodeFromJSON(fixedDoc)); + + if (tr.docChanged) { + shouldReturnTr = true; + tr.setMeta('addToHistory', false); + } + } + + return shouldReturnTr ? tr : undefined; }, }), ]; From ef2ed7eea9753a41dbb7cd029261ce0b285781df Mon Sep 17 00:00:00 2001 From: fantasticit Date: Fri, 19 Aug 2022 10:37:29 +0800 Subject: [PATCH 004/117] tiptap: remove unused code --- packages/client/src/tiptap/core/extensions/title.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/client/src/tiptap/core/extensions/title.ts b/packages/client/src/tiptap/core/extensions/title.ts index 63993262..c501d18d 100644 --- a/packages/client/src/tiptap/core/extensions/title.ts +++ b/packages/client/src/tiptap/core/extensions/title.ts @@ -78,7 +78,6 @@ export const Title = Node.create({ addProseMirrorPlugins() { const { editor } = this; let shouldSelectTitleNode = true; - let editorView; const closeSelectTitleNode = () => { shouldSelectTitleNode = false; @@ -88,15 +87,6 @@ export const Title = Node.create({ return [ new Plugin({ key: TitlePluginKey, - view: (view) => { - editorView = view; - - return { - update(view) { - editorView = view; - }, - }; - }, props: { decorations: (state) => { const { doc } = state; From 5146b0f5a2e5b427821d7de8d2202642abe46d2f Mon Sep 17 00:00:00 2001 From: fantasticit Date: Fri, 19 Aug 2022 11:03:09 +0800 Subject: [PATCH 005/117] tiptap: dynamic placeholder --- .../src/tiptap/core/extensions/placeholder.ts | 96 ++++++++++++++++++- .../src/tiptap/editor/collaboration/kit.ts | 11 ++- 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/packages/client/src/tiptap/core/extensions/placeholder.ts b/packages/client/src/tiptap/core/extensions/placeholder.ts index a60a49f5..d4c8b56d 100644 --- a/packages/client/src/tiptap/core/extensions/placeholder.ts +++ b/packages/client/src/tiptap/core/extensions/placeholder.ts @@ -1,3 +1,95 @@ -import BuiltInPlaceholder from '@tiptap/extension-placeholder'; +import { Editor, Extension } from '@tiptap/core'; +import { Node as ProsemirrorNode } from 'prosemirror-model'; +import { Plugin } from 'prosemirror-state'; +import { Decoration, DecorationSet } from 'prosemirror-view'; -export const Placeholder = BuiltInPlaceholder; +export interface PlaceholderOptions { + emptyEditorClass: string; + emptyNodeClass: string; + placeholder: ((PlaceholderProps: { editor: Editor; node: ProsemirrorNode; pos: number }) => string) | string; + showOnlyWhenEditable: boolean; + showOnlyCurrent: boolean; + includeChildren: boolean; +} + +export const Placeholder = Extension.create({ + name: 'placeholder', + + addOptions() { + return { + emptyEditorClass: 'is-editor-empty', + emptyNodeClass: 'is-empty', + placeholder: 'Write something …', + showOnlyWhenEditable: true, + showOnlyCurrent: true, + includeChildren: false, + }; + }, + + addStorage() { + return new Map(); + }, + + addProseMirrorPlugins() { + return [ + new Plugin({ + props: { + decorations: ({ doc, selection }) => { + const active = this.editor.isEditable || !this.options.showOnlyWhenEditable; + const { anchor } = selection; + const decorations: Decoration[] = []; + + if (!active) { + return; + } + + doc.descendants((node, pos) => { + const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize; + const isEmpty = !node.isLeaf && !node.childCount; + + if ((hasAnchor || !this.options.showOnlyCurrent) && isEmpty) { + const classes = [this.options.emptyNodeClass]; + + if (this.editor.isEmpty) { + classes.push(this.options.emptyEditorClass); + } + + const start = pos; + const end = pos + node.nodeSize; + const key = `${start}-${end}`; + + if (!this.editor.storage[this.name].has(key)) { + this.editor.storage[this.name].set( + key, + typeof this.options.placeholder === 'function' + ? this.options.placeholder({ + editor: this.editor, + node, + pos, + }) + : this.options.placeholder + ); + } + + const decoration = Decoration.node(start, end, { + 'class': classes.join(' '), + 'data-placeholder': this.editor.storage[this.name].get(key), + }); + + setTimeout(() => { + this.editor.storage[this.name].delete(key); + }, 500); + + decorations.push(decoration); + } + + return this.options.includeChildren; + }); + + return DecorationSet.create(doc, decorations); + }, + }, + }), + ]; + }, +}); diff --git a/packages/client/src/tiptap/editor/collaboration/kit.ts b/packages/client/src/tiptap/editor/collaboration/kit.ts index 1c236848..48f495e5 100644 --- a/packages/client/src/tiptap/editor/collaboration/kit.ts +++ b/packages/client/src/tiptap/editor/collaboration/kit.ts @@ -1,5 +1,4 @@ import { Toast } from '@douyinfe/semi-ui'; -import scrollIntoView from 'scroll-into-view-if-needed'; // 自定义节点扩展 import { Attachment } from 'tiptap/core/extensions/attachment'; import { BackgroundColor } from 'tiptap/core/extensions/background-color'; @@ -78,6 +77,14 @@ const DocumentWithTitle = Document.extend({ export { Document }; +const placeholders = [ + '输入 / 唤起更多', + '使用 markdown 语法进行输入', + '输入 @ 来提及他人', + '输入 : 来插入表情', + '你知道吗?输入 $katex 然后按一下空格就可以快速插入数学公式,其他节点操作类似哦', +]; + export const CollaborationKit = [ Paragraph, Placeholder.configure({ @@ -88,7 +95,7 @@ export const CollaborationKit = [ if (!editor.isEditable) return; - return '输入 / 唤起更多'; + return placeholders[~~(Math.random() * placeholders.length)]; }, showOnlyCurrent: false, showOnlyWhenEditable: false, From c502474e57b0e5adebefe3a3f75ba418a0c2c32d Mon Sep 17 00:00:00 2001 From: fantasticit Date: Fri, 19 Aug 2022 11:03:33 +0800 Subject: [PATCH 006/117] client: format code --- packages/client/src/tiptap/core/extensions/quick-insert.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/src/tiptap/core/extensions/quick-insert.ts b/packages/client/src/tiptap/core/extensions/quick-insert.ts index 5df5eb7a..95692f83 100644 --- a/packages/client/src/tiptap/core/extensions/quick-insert.ts +++ b/packages/client/src/tiptap/core/extensions/quick-insert.ts @@ -8,7 +8,7 @@ import { insertMenuLRUCache, QUICK_INSERT_COMMANDS, transformToCommands } from ' import { MenuList } from 'tiptap/core/wrappers/menu-list'; export const QuickInsertPluginKey = new PluginKey('quickInsert'); -const extensionName = 'quickInsert' +const extensionName = 'quickInsert'; export const QuickInsert = Node.create({ name: extensionName, @@ -63,8 +63,8 @@ export const QuickInsert = Node.create({ top: 0, right: 0, bottom: 0, - } - } + }, + }; }, }).configure({ suggestion: { From cb811ab21f5e2ca08d98445bfbf3858ed3dad1ef Mon Sep 17 00:00:00 2001 From: fantasticit Date: Fri, 19 Aug 2022 14:38:57 +0800 Subject: [PATCH 007/117] tiptap: fix status input blur --- .../src/tiptap/core/wrappers/status/index.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/client/src/tiptap/core/wrappers/status/index.tsx b/packages/client/src/tiptap/core/wrappers/status/index.tsx index a79081b9..98857773 100644 --- a/packages/client/src/tiptap/core/wrappers/status/index.tsx +++ b/packages/client/src/tiptap/core/wrappers/status/index.tsx @@ -4,7 +4,7 @@ import { NodeViewWrapper } from '@tiptap/react'; import cls from 'classnames'; import { useUser } from 'data/user'; import { useToggle } from 'hooks/use-toggle'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import styles from './index.module.scss'; @@ -24,14 +24,15 @@ export const StatusWrapper = ({ editor, node, updateAttributes }) => { const { user } = useUser(); const ref = useRef(); const [visible, toggleVisible] = useToggle(false); + const [currentText, setCurrentText] = useState(text); const content = useMemo( () => ( - {text || '点击设置状态'} + {currentText || '点击设置状态'} ), - [bgcolor, borderColor, currentTextColor, text] + [bgcolor, borderColor, currentTextColor, currentText] ); const onVisibleChange = useCallback( @@ -64,8 +65,10 @@ export const StatusWrapper = ({ editor, node, updateAttributes }) => { useEffect(() => { if (visible) { ref.current?.focus(); + } else { + updateAttributes({ text: currentText }); } - }, [visible]); + }, [visible, updateAttributes, currentText]); return ( @@ -78,7 +81,7 @@ export const StatusWrapper = ({ editor, node, updateAttributes }) => { content={
- updateAttributes({ text: v })} /> +
{STATUS_COLORS.map((color) => { From 2f97380e63581999ef1fddc872d0de0f3e669775 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Fri, 19 Aug 2022 16:14:00 +0800 Subject: [PATCH 008/117] tiptap: only use dynamic placeholder in edit --- .../src/tiptap/core/extensions/placeholder.ts | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/client/src/tiptap/core/extensions/placeholder.ts b/packages/client/src/tiptap/core/extensions/placeholder.ts index d4c8b56d..794b5f31 100644 --- a/packages/client/src/tiptap/core/extensions/placeholder.ts +++ b/packages/client/src/tiptap/core/extensions/placeholder.ts @@ -56,30 +56,44 @@ export const Placeholder = Extension.create({ const start = pos; const end = pos + node.nodeSize; - const key = `${start}-${end}`; + let placeholder = ''; - if (!this.editor.storage[this.name].has(key)) { - this.editor.storage[this.name].set( - key, + if (this.editor.isEditable) { + const key = `${start}-${end}`; + + if (!this.editor.storage[this.name].has(key)) { + this.editor.storage[this.name].set( + key, + typeof this.options.placeholder === 'function' + ? this.options.placeholder({ + editor: this.editor, + node, + pos, + }) + : this.options.placeholder + ); + } + placeholder = this.editor.storage[this.name].get(key); + + setTimeout(() => { + this.editor.storage[this.name].delete(key); + }, 500); + } else { + placeholder = typeof this.options.placeholder === 'function' ? this.options.placeholder({ editor: this.editor, node, pos, }) - : this.options.placeholder - ); + : this.options.placeholder; } const decoration = Decoration.node(start, end, { 'class': classes.join(' '), - 'data-placeholder': this.editor.storage[this.name].get(key), + 'data-placeholder': placeholder, }); - setTimeout(() => { - this.editor.storage[this.name].delete(key); - }, 500); - decorations.push(decoration); } From 3b68238249b072c2ee2029c0ed56f9a33d9d6624 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Fri, 19 Aug 2022 17:38:51 +0800 Subject: [PATCH 009/117] tiptap: improve mind toolbar --- .../kityminder/kity-core/module/priority.js | 2 +- .../src/tiptap/core/menus/mind/constant.ts | 33 ++ .../src/tiptap/core/menus/mind/modal.tsx | 7 + .../core/menus/mind/toolbar/bgcolor.tsx | 39 ++ .../core/menus/mind/toolbar/font-color.tsx | 39 ++ .../tiptap/core/menus/mind/toolbar/help.tsx | 33 ++ .../tiptap/core/menus/mind/toolbar/image.tsx | 4 +- .../tiptap/core/menus/mind/toolbar/index.tsx | 387 +++++++++--------- .../core/menus/mind/toolbar/priority.tsx | 29 ++ .../core/menus/mind/toolbar/progress.tsx | 29 ++ .../core/menus/mind/toolbar/template.tsx | 41 ++ .../tiptap/core/menus/mind/toolbar/theme.tsx | 42 ++ 12 files changed, 481 insertions(+), 204 deletions(-) create mode 100644 packages/client/src/tiptap/core/menus/mind/toolbar/bgcolor.tsx create mode 100644 packages/client/src/tiptap/core/menus/mind/toolbar/font-color.tsx create mode 100644 packages/client/src/tiptap/core/menus/mind/toolbar/help.tsx create mode 100644 packages/client/src/tiptap/core/menus/mind/toolbar/priority.tsx create mode 100644 packages/client/src/tiptap/core/menus/mind/toolbar/progress.tsx create mode 100644 packages/client/src/tiptap/core/menus/mind/toolbar/template.tsx create mode 100644 packages/client/src/tiptap/core/menus/mind/toolbar/theme.tsx diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/priority.js b/packages/client/src/thirtypart/kityminder/kity-core/module/priority.js index 7facd628..21e44fe6 100644 --- a/packages/client/src/thirtypart/kityminder/kity-core/module/priority.js +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/priority.js @@ -82,7 +82,7 @@ define(function (require, exports, module) { mask.fill(color[0]); } - number.setContent(value); + number.setContent('P' + value); }, }); diff --git a/packages/client/src/tiptap/core/menus/mind/constant.ts b/packages/client/src/tiptap/core/menus/mind/constant.ts index 9d8fbda7..25e3367c 100644 --- a/packages/client/src/tiptap/core/menus/mind/constant.ts +++ b/packages/client/src/tiptap/core/menus/mind/constant.ts @@ -1,3 +1,36 @@ +const getProgressTitle = (index) => { + switch (index) { + case 0: + return '移除进度'; + case 1: + return '未开始'; + case 9: + return '全部完成'; + default: + return '完成' + (index - 1) + '/8'; + } +}; + +export const PROGRESSES = Array.from({ length: 10 }, (_, i) => { + return { + text: getProgressTitle(i), + value: i, + }; +}); + +export const PRIORITIES = [ + { + text: '移除优先级', + value: 0, + }, + ...Array.from({ length: 9 }, (_, i) => { + return { + text: `P${i + 1}`, + value: i + 1, + }; + }), +]; + export const TEMPLATES = [ { label: '经典', diff --git a/packages/client/src/tiptap/core/menus/mind/modal.tsx b/packages/client/src/tiptap/core/menus/mind/modal.tsx index d8ecf19e..f9518dc8 100644 --- a/packages/client/src/tiptap/core/menus/mind/modal.tsx +++ b/packages/client/src/tiptap/core/menus/mind/modal.tsx @@ -65,6 +65,12 @@ export const MindSettingModal: React.FC = ({ editor }) => { }; }, [editor, toggleVisible]); + useEffect(() => { + if (!visible && mind) { + mind.destroy(); + } + }, [visible, mind]); + return ( = ({ editor }) => { onOk={save} okText="保存" cancelText="退出" + motion={false} >
{ + return ( + { + setBackgroundColor(color); + }} + > + +
+ } + /> + + + ); +}; diff --git a/packages/client/src/tiptap/core/menus/mind/toolbar/font-color.tsx b/packages/client/src/tiptap/core/menus/mind/toolbar/font-color.tsx new file mode 100644 index 00000000..f3b38948 --- /dev/null +++ b/packages/client/src/tiptap/core/menus/mind/toolbar/font-color.tsx @@ -0,0 +1,39 @@ +import { IconFont } from '@douyinfe/semi-icons'; +import { Button, Tooltip } from '@douyinfe/semi-ui'; +import { ColorPicker } from 'components/color-picker'; + +export const FontColor = ({ selectedNode, setFontColor, textColor }) => { + return ( + { + setFontColor(color); + }} + > + +
+ } + /> + + + ); +}; diff --git a/packages/client/src/tiptap/core/menus/mind/toolbar/help.tsx b/packages/client/src/tiptap/core/menus/mind/toolbar/help.tsx new file mode 100644 index 00000000..cc28a9c8 --- /dev/null +++ b/packages/client/src/tiptap/core/menus/mind/toolbar/help.tsx @@ -0,0 +1,33 @@ +import { IconHelpCircle } from '@douyinfe/semi-icons'; +import { Button, Descriptions, Popover } from '@douyinfe/semi-ui'; + +import styles from './index.module.scss'; + +const HELP_MESSAGE = [ + { key: '新增同级节点', value: 'Enter 键' }, + { key: '新增子节点', value: 'Tab 键' }, + { key: '编辑节点文字', value: '双击节点' }, + { key: '编辑节点菜单', value: '在节点右键' }, +]; + +const HELP_MESSAGE_STYLE = { + width: '200px', +}; + +export const Help = () => { + return ( + + + + } + > +