feat: support selecton in banner

This commit is contained in:
fantasticit 2022-04-01 12:08:16 +08:00
parent 8022d937c2
commit 8b098285f9
5 changed files with 54 additions and 37 deletions

View File

@ -24,7 +24,7 @@ export const Banner = Node.create({
type: { type: {
default: 'info', default: 'info',
rendered: false, rendered: false,
parseHTML: getDatasetAttribute('info'), parseHTML: getDatasetAttribute('type'),
renderHTML: (attributes) => { renderHTML: (attributes) => {
return { return {
'data-type': attributes.type, 'data-type': attributes.type,

View File

@ -1,7 +1,7 @@
import { Extension } from '@tiptap/core'; import { Extension } from '@tiptap/core';
import { Plugin, PluginKey, NodeSelection, TextSelection, Selection, AllSelection } from 'prosemirror-state'; import { Plugin, PluginKey, NodeSelection, TextSelection, Selection, AllSelection } from 'prosemirror-state';
import { Decoration, DecorationSet } from 'prosemirror-view'; import { Decoration, DecorationSet } from 'prosemirror-view';
import { getCurrentNode, isInCodeBlock } from '../services/node'; import { getCurrentNode, isInCodeBlock, isInBanner } from '../services/node';
import { EXTENSION_PRIORITY_HIGHEST } from '../constants'; import { EXTENSION_PRIORITY_HIGHEST } from '../constants';
export const selectionPluginKey = new PluginKey('selection'); export const selectionPluginKey = new PluginKey('selection');
@ -56,13 +56,30 @@ export const SelectionExtension = Extension.create({
*/ */
if ((event.ctrlKey || event.metaKey) && (event.keyCode == 65 || event.keyCode == 97)) { if ((event.ctrlKey || event.metaKey) && (event.keyCode == 65 || event.keyCode == 97)) {
const node = getCurrentNode(view.state); const node = getCurrentNode(view.state);
const $head = view.state.selection.$head;
let startPos = null;
let endPos = null;
// 代码块 // 代码块
if (isInCodeBlock(view.state)) { if (isInCodeBlock(view.state)) {
const { pos, parentOffset } = view.state.selection.$head; const { pos, parentOffset } = $head;
startPos = pos - parentOffset;
endPos = pos - parentOffset + node.nodeSize - 2;
}
// 信息框
if (isInBanner(view.state)) {
// @ts-ignore
const { path = [] } = $head;
startPos = path[2];
endPos = startPos + path[3].content.size;
}
if (startPos !== null && endPos !== null) {
const newState = view.state; const newState = view.state;
const next = new TextSelection( const next = new TextSelection(
newState.doc.resolve(pos - parentOffset + node.nodeSize - 2), //内容结束点 newState.doc.resolve(endPos), //内容结束点
newState.doc.resolve(pos - parentOffset) // 内容起始点 newState.doc.resolve(startPos) // 内容起始点
); );
view?.dispatch(newState.tr.setSelection(next)); view?.dispatch(newState.tr.setSelection(next));
return true; return true;
@ -71,19 +88,6 @@ export const SelectionExtension = Extension.create({
return false; 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) { decorations(state) {
return this.getState(state); return this.getState(state);
}, },
@ -93,10 +97,6 @@ export const SelectionExtension = Extension.create({
return DecorationSet.empty; return DecorationSet.empty;
}, },
apply(ctx) { 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 { doc, selection } = ctx;
const decorationSet = getDecorations(doc, selection); const decorationSet = getDecorations(doc, selection);
return decorationSet; return decorationSet;

View File

@ -7,6 +7,10 @@ export const isMarkdown = (text: string): boolean => {
const tables = text.match(/^\|(\S)*\|/gm); const tables = text.match(/^\|(\S)*\|/gm);
if (tables && tables.length) return true; if (tables && tables.length) return true;
// 自定义 container
const conatiner = text.match(/^:::/gm);
if (conatiner && conatiner.length > 1) return true;
// code-ish // code-ish
const fences = text.match(/^```/gm); const fences = text.match(/^```/gm);
if (fences && fences.length > 1) return true; if (fences && fences.length > 1) return true;

View File

@ -21,25 +21,36 @@ export function isListNode(node: Node): boolean {
return isBulletListNode(node) || isOrderedListNode(node) || isTodoListNode(node); return isBulletListNode(node) || isOrderedListNode(node) || isTodoListNode(node);
} }
export function isInTitle(state: EditorState): boolean { export function getCurrentNode(state: EditorState): Node {
const $head = state.selection.$head;
let node = null;
for (let d = $head.depth; d > 0; d--) {
node = $head.node(d);
}
return node;
}
export function isInCustomNode(state: EditorState, nodeName: string): boolean {
if (!state.schema.nodes[nodeName]) return false;
const $head = state.selection.$head; const $head = state.selection.$head;
for (let d = $head.depth; d > 0; d--) { for (let d = $head.depth; d > 0; d--) {
if ($head.node(d).type === state.schema.nodes.title) { if ($head.node(d).type === state.schema.nodes[nodeName]) {
return true; return true;
} }
} }
} }
export function getCurrentNode(state: EditorState): Node {
const $head = state.selection.$head;
return $head.node($head.depth);
}
export function isInCodeBlock(state: EditorState): boolean { export function isInCodeBlock(state: EditorState): boolean {
const $head = state.selection.$head; return isInCustomNode(state, 'codeBlock');
for (let d = $head.depth; d > 0; d--) { }
if ($head.node(d).type === state.schema.nodes.codeBlock) {
return true; export function isInTitle(state: EditorState): boolean {
} return isInCustomNode(state, 'title');
} }
export function isInBanner(state: EditorState): boolean {
return isInCustomNode(state, 'banner');
} }

View File

@ -141,9 +141,11 @@ export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
if (isEditable && !url) { if (isEditable && !url) {
return ( return (
<div className={cls(styles.wrap, 'render-wrapper')} onClick={selectFile}> <div className={cls(styles.wrap, 'render-wrapper')}>
<Spin spinning={loading}> <Spin spinning={loading}>
<Text style={{ cursor: 'pointer' }}>{loading ? '正在上传中' : '请选择文件'}</Text> <Text style={{ cursor: 'pointer' }} onClick={selectFile}>
{loading ? '正在上传中' : '请选择文件'}
</Text>
<input ref={$upload} type="file" hidden onChange={handleFile} /> <input ref={$upload} type="file" hidden onChange={handleFile} />
</Spin> </Spin>
</div> </div>