mirror of https://github.com/fantasticit/think.git
feat: support selecton in banner
This commit is contained in:
parent
8022d937c2
commit
8b098285f9
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue