From 0e54e4af65f0fa32a0b03146d0457932ad443886 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Tue, 22 Mar 2022 12:43:03 +0800 Subject: [PATCH] feat: improve tiptap --- .../client/src/components/tiptap/basekit.tsx | 2 + .../tiptap/components/status/index.tsx | 8 +- .../tiptap/extensions/evokeMenu.tsx | 7 +- .../src/components/tiptap/extensions/focus.ts | 90 +++++++++++++++++++ .../components/tiptap/extensions/status.ts | 1 - packages/client/src/styles/prosemirror.scss | 38 ++++---- 6 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 packages/client/src/components/tiptap/extensions/focus.ts diff --git a/packages/client/src/components/tiptap/basekit.tsx b/packages/client/src/components/tiptap/basekit.tsx index aefd3c6d..f8436940 100644 --- a/packages/client/src/components/tiptap/basekit.tsx +++ b/packages/client/src/components/tiptap/basekit.tsx @@ -13,6 +13,7 @@ import { DocumentReference } from './extensions/documentReference'; import { Dropcursor } from './extensions/dropCursor'; import { Emoji } from './extensions/emoji'; import { EvokeMenu } from './extensions/evokeMenu'; +import { Focus } from './extensions/focus'; import { FontSize } from './extensions/fontSize'; import { FootnoteDefinition } from './extensions/footnoteDefinition'; import { FootnoteReference } from './extensions/footnoteReference'; @@ -67,6 +68,7 @@ export const BaseKit = [ Dropcursor, Emoji, EvokeMenu, + Focus, FontSize, FootnoteDefinition, FootnoteReference, diff --git a/packages/client/src/components/tiptap/components/status/index.tsx b/packages/client/src/components/tiptap/components/status/index.tsx index 7d14f0eb..c2d1bb95 100644 --- a/packages/client/src/components/tiptap/components/status/index.tsx +++ b/packages/client/src/components/tiptap/components/status/index.tsx @@ -15,11 +15,7 @@ export const StatusWrapper = ({ editor, node, updateAttributes }) => { content={ <>
- updateAttributes({ text: v })} - /> + updateAttributes({ text: v })} />
{['grey', 'red', 'green', 'orange', 'purple', 'teal'].map((color) => { @@ -44,7 +40,7 @@ export const StatusWrapper = ({ editor, node, updateAttributes }) => { ) : ( content )} - + {/* */} ); }; diff --git a/packages/client/src/components/tiptap/extensions/evokeMenu.tsx b/packages/client/src/components/tiptap/extensions/evokeMenu.tsx index 6959543b..80716eb7 100644 --- a/packages/client/src/components/tiptap/extensions/evokeMenu.tsx +++ b/packages/client/src/components/tiptap/extensions/evokeMenu.tsx @@ -54,21 +54,22 @@ export const EvokeMenu = Node.create({ const isEmpty = parent && parent.node.content.size === 0; const isSlash = parent && parent.node.textContent === '/'; const isTopLevel = state.selection.$from.depth === 1; + const hasOtherChildren = parent && parent.node.content.childCount > 1; if (isTopLevel) { if (isEmpty) { decorations.push( Decoration.node(parent.pos, parent.pos + parent.node.nodeSize, { - 'class': 'placeholder', + 'class': 'is-empty', 'data-placeholder': '输入 / 唤起更多', }) ); } - if (isSlash) { + if (isSlash && !hasOtherChildren) { decorations.push( Decoration.node(parent.pos, parent.pos + parent.node.nodeSize, { - 'class': 'placeholder', + 'class': 'is-empty', 'data-placeholder': ` 继续输入进行过滤`, }) ); diff --git a/packages/client/src/components/tiptap/extensions/focus.ts b/packages/client/src/components/tiptap/extensions/focus.ts new file mode 100644 index 00000000..e32fdaa4 --- /dev/null +++ b/packages/client/src/components/tiptap/extensions/focus.ts @@ -0,0 +1,90 @@ +import { Extension } from '@tiptap/core'; +import { Plugin, PluginKey } from 'prosemirror-state'; +import { DecorationSet, Decoration } from 'prosemirror-view'; + +export interface FocusOptions { + className: string; + mode: 'all' | 'deepest' | 'shallowest'; +} + +export const Focus = Extension.create({ + name: 'focus', + + addOptions() { + return { + className: 'has-focus', + mode: 'all', + }; + }, + + addProseMirrorPlugins() { + return [ + new Plugin({ + key: new PluginKey('focus'), + props: { + decorations: ({ doc, selection }) => { + const { isEditable, isFocused } = this.editor; + const { anchor } = selection; + const decorations: Decoration[] = []; + + if (!isEditable || !isFocused) { + return DecorationSet.create(doc, []); + } + + // Maximum Levels + let maxLevels = 0; + + if (this.options.mode === 'deepest') { + doc.descendants((node, pos) => { + if (node.isText) { + return; + } + + const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1; + + if (!isCurrent) { + return false; + } + + maxLevels += 1; + }); + } + + // Loop through current + let currentLevel = 0; + + doc.descendants((node, pos) => { + if (node.isText) { + return false; + } + + const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1; + + if (!isCurrent) { + return false; + } + + currentLevel += 1; + + const outOfScope = + (this.options.mode === 'deepest' && maxLevels - currentLevel > 0) || + (this.options.mode === 'shallowest' && currentLevel > 1); + + if (outOfScope) { + return this.options.mode === 'deepest'; + } + + decorations.push( + Decoration.node(pos, pos + node.nodeSize, { + class: this.options.className, + }) + ); + }); + + return DecorationSet.create(doc, decorations); + }, + }, + }), + ]; + }, +}); diff --git a/packages/client/src/components/tiptap/extensions/status.ts b/packages/client/src/components/tiptap/extensions/status.ts index 43d87098..50676708 100644 --- a/packages/client/src/components/tiptap/extensions/status.ts +++ b/packages/client/src/components/tiptap/extensions/status.ts @@ -12,7 +12,6 @@ declare module '@tiptap/core' { export const Status = Node.create({ name: 'status', - content: 'text*', group: 'inline', inline: true, atom: true, diff --git a/packages/client/src/styles/prosemirror.scss b/packages/client/src/styles/prosemirror.scss index 2e1943d9..83e4a11e 100644 --- a/packages/client/src/styles/prosemirror.scss +++ b/packages/client/src/styles/prosemirror.scss @@ -38,24 +38,20 @@ outline: none; } - .is-empty::before { - content: attr(data-placeholder); - float: left; - color: #aaa; - pointer-events: none; - height: 0; - } + .is-empty { + &.has-focus { + &::before { + content: attr(data-placeholder); + float: left; + color: #aaa; + pointer-events: none; + height: 0; + } + } - .is-empty.node-codeBlock::before { - transform: translate(10px, 10px); - } - - .placeholder::before { - content: attr(data-placeholder); - float: left; - color: #aaa; - pointer-events: none; - height: 0; + &.node-codeBlock::before { + transform: translate(10px, 10px); + } } .hr-line { @@ -108,6 +104,14 @@ color: var(--semi-color-text-0); margin: 10px 0 22px; border-bottom: 1px solid var(--semi-color-border); + + &.is-empty::before { + content: attr(data-placeholder); + float: left; + color: #aaa; + pointer-events: none; + height: 0; + } } h1 {