diff --git a/packages/client/src/tiptap/extensions/selection.ts b/packages/client/src/tiptap/extensions/selection.ts index 19a2ef2..52a247a 100644 --- a/packages/client/src/tiptap/extensions/selection.ts +++ b/packages/client/src/tiptap/extensions/selection.ts @@ -1,6 +1,7 @@ import { Extension } from '@tiptap/core'; import { Plugin, PluginKey, NodeSelection, TextSelection, Selection, AllSelection } from 'prosemirror-state'; import { Decoration, DecorationSet } from 'prosemirror-view'; +import { getCurrentNode, isInCodeBlock } from '../services/node'; import { EXTENSION_PRIORITY_HIGHEST } from '../constants'; export const selectionPluginKey = new PluginKey('selection'); @@ -48,8 +49,55 @@ export const SelectionExtension = Extension.create({ new Plugin({ key: selectionPluginKey, props: { + handleKeyDown(view, event) { + /** + * Command + A + * Ctrl + A + */ + if ((event.ctrlKey || event.metaKey) && (event.keyCode == 65 || event.keyCode == 97)) { + const node = getCurrentNode(view.state); + // 代码块 + if (isInCodeBlock(view.state)) { + const { pos, parentOffset } = view.state.selection.$head; + const newState = view.state; + const next = new TextSelection( + newState.doc.resolve(pos - parentOffset + node.nodeSize - 2), //内容结束点 + newState.doc.resolve(pos - parentOffset) // 内容起始点 + ); + view?.dispatch(newState.tr.setSelection(next)); + return true; + } + } + + return false; + }, + handleDoubleClickOn(view, pos, node, nodePos, event) { + if (node.type.name === 'codeBlock') { + event.preventDefault(); + const transaction = view.state.tr.setMeta('selectNode', { + fromPos: nodePos, + toPos: nodePos + node.nodeSize, + attrs: { class: 'selected-node' }, + }); + view?.dispatch(transaction); + return false; + } + return true; + }, decorations(state) { - const { doc, selection } = state; + return this.getState(state); + }, + }, + state: { + init() { + return DecorationSet.empty; + }, + apply(ctx) { + if (ctx.getMeta('selectNode')) { + const { fromPos, toPos, attrs } = ctx.getMeta('selectNode'); + return DecorationSet.create(ctx.doc, [Decoration.node(fromPos, toPos, attrs)]); + } + const { doc, selection } = ctx; const decorationSet = getDecorations(doc, selection); return decorationSet; }, diff --git a/packages/client/src/tiptap/services/node.ts b/packages/client/src/tiptap/services/node.ts index 949fddc..b41fc7e 100644 --- a/packages/client/src/tiptap/services/node.ts +++ b/packages/client/src/tiptap/services/node.ts @@ -29,3 +29,17 @@ export function isInTitle(state: EditorState): boolean { } } } + +export function getCurrentNode(state: EditorState): Node { + const $head = state.selection.$head; + return $head.node($head.depth); +} + +export function isInCodeBlock(state: EditorState): boolean { + const $head = state.selection.$head; + for (let d = $head.depth; d > 0; d--) { + if ($head.node(d).type === state.schema.nodes.codeBlock) { + return true; + } + } +} diff --git a/packages/client/src/tiptap/styles/selection.scss b/packages/client/src/tiptap/styles/selection.scss index 81f22ba..52e0ba6 100644 --- a/packages/client/src/tiptap/styles/selection.scss +++ b/packages/client/src/tiptap/styles/selection.scss @@ -50,7 +50,7 @@ .node-iframe, .node-mind, .node-banner { - margin-top: .75em; + margin-top: 0.75em; } .node-attachment, @@ -72,6 +72,7 @@ } &.selected-node { + position: relative; .render-wrapper { border: 1px solid var(--node-selected-border-color) !important; @@ -80,6 +81,15 @@ box-shadow: none; } } + + &::after { + position: absolute; + content: ''; + inset: 0px; + opacity: 0.3; + pointer-events: none; + background-color: rgb(179, 212, 255); + } } &:not(.has-focus) {