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 {