mirror of https://github.com/fantasticit/think.git
tiptap: add clipboard extension
This commit is contained in:
parent
c6fc458ae3
commit
6c1c29ee1a
|
@ -0,0 +1,97 @@
|
||||||
|
import { Extension } from '@tiptap/core';
|
||||||
|
import { Fragment } from 'prosemirror-model';
|
||||||
|
import { Plugin, PluginKey } from 'prosemirror-state';
|
||||||
|
import { EXTENSION_PRIORITY_HIGHEST } from 'tiptap/core/constants';
|
||||||
|
import { copyNode } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
|
const isPureText = (content): boolean => {
|
||||||
|
if (!content) return false;
|
||||||
|
|
||||||
|
if (Array.isArray(content)) {
|
||||||
|
if (content.length > 1) return false;
|
||||||
|
return isPureText(content[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const child = content['content'];
|
||||||
|
if (child) {
|
||||||
|
return isPureText(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content['type'] === 'text';
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IClipboardOptions {
|
||||||
|
/**
|
||||||
|
* 将 prosemirror 转换为 markdown
|
||||||
|
*/
|
||||||
|
prosemirrorToMarkdown: (arg: { content: Fragment }) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Clipboard = Extension.create<IClipboardOptions>({
|
||||||
|
name: 'clipboard',
|
||||||
|
priority: EXTENSION_PRIORITY_HIGHEST,
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
prosemirrorToMarkdown: (arg) => String(arg.content),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
const extensionThis = this;
|
||||||
|
|
||||||
|
return [
|
||||||
|
new Plugin({
|
||||||
|
key: new PluginKey('clipboard'),
|
||||||
|
props: {
|
||||||
|
handleKeyDown(view, event) {
|
||||||
|
/**
|
||||||
|
* Command + C
|
||||||
|
* Ctrl + C
|
||||||
|
*/
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.keyCode == 67) {
|
||||||
|
const { state } = view;
|
||||||
|
// @ts-ignore
|
||||||
|
const currentNode = state.selection.node;
|
||||||
|
|
||||||
|
if (currentNode) {
|
||||||
|
event.preventDefault();
|
||||||
|
copyNode(currentNode);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
clipboardTextSerializer: (slice) => {
|
||||||
|
const json = slice.content.toJSON();
|
||||||
|
const isSelectAll = slice.openStart === slice.openEnd && slice.openEnd === 0;
|
||||||
|
|
||||||
|
if (typeof json === 'object' || isSelectAll) {
|
||||||
|
return extensionThis.options.prosemirrorToMarkdown({
|
||||||
|
content: slice.content,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const isText = isPureText(json) && !isSelectAll;
|
||||||
|
|
||||||
|
if (isText) {
|
||||||
|
return slice.content.textBetween(0, slice.content.size, '\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = slice.content;
|
||||||
|
|
||||||
|
if (!doc) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = extensionThis.options.prosemirrorToMarkdown({
|
||||||
|
content: doc,
|
||||||
|
});
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
|
@ -5,7 +5,6 @@ import { Fragment, Schema } from 'prosemirror-model';
|
||||||
import { Plugin, PluginKey } from 'prosemirror-state';
|
import { Plugin, PluginKey } from 'prosemirror-state';
|
||||||
import { EXTENSION_PRIORITY_HIGHEST } from 'tiptap/core/constants';
|
import { EXTENSION_PRIORITY_HIGHEST } from 'tiptap/core/constants';
|
||||||
import {
|
import {
|
||||||
copyNode,
|
|
||||||
handleFileEvent,
|
handleFileEvent,
|
||||||
isInCode,
|
isInCode,
|
||||||
isMarkdown,
|
isMarkdown,
|
||||||
|
@ -36,22 +35,6 @@ interface IPasteOptions {
|
||||||
prosemirrorToMarkdown: (arg: { content: Fragment }) => string;
|
prosemirrorToMarkdown: (arg: { content: Fragment }) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPureText = (content): boolean => {
|
|
||||||
if (!content) return false;
|
|
||||||
|
|
||||||
if (Array.isArray(content)) {
|
|
||||||
if (content.length > 1) return false;
|
|
||||||
return isPureText(content[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const child = content['content'];
|
|
||||||
if (child) {
|
|
||||||
return isPureText(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
return content['type'] === 'text';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Paste = Extension.create<IPasteOptions>({
|
export const Paste = Extension.create<IPasteOptions>({
|
||||||
name: 'paste',
|
name: 'paste',
|
||||||
priority: EXTENSION_PRIORITY_HIGHEST,
|
priority: EXTENSION_PRIORITY_HIGHEST,
|
||||||
|
@ -102,13 +85,6 @@ export const Paste = Extension.create<IPasteOptions>({
|
||||||
|
|
||||||
const { markdownToProsemirror } = extensionThis.options;
|
const { markdownToProsemirror } = extensionThis.options;
|
||||||
|
|
||||||
console.log('p', {
|
|
||||||
text,
|
|
||||||
html,
|
|
||||||
node,
|
|
||||||
markdownText,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 直接复制节点
|
// 直接复制节点
|
||||||
if (node) {
|
if (node) {
|
||||||
const json = safeJSONParse(node);
|
const json = safeJSONParse(node);
|
||||||
|
@ -178,7 +154,6 @@ export const Paste = Extension.create<IPasteOptions>({
|
||||||
content: normalizeMarkdown(markdownText || text),
|
content: normalizeMarkdown(markdownText || text),
|
||||||
needTitle: hasTitleExtension && !hasTitle,
|
needTitle: hasTitleExtension && !hasTitle,
|
||||||
});
|
});
|
||||||
console.log('p', markdownText, text);
|
|
||||||
|
|
||||||
let tr = view.state.tr;
|
let tr = view.state.tr;
|
||||||
const selection = tr.selection;
|
const selection = tr.selection;
|
||||||
|
@ -220,50 +195,6 @@ export const Paste = Extension.create<IPasteOptions>({
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
handleKeyDown(view, event) {
|
|
||||||
/**
|
|
||||||
* Command + C
|
|
||||||
* Ctrl + C
|
|
||||||
*/
|
|
||||||
if ((event.ctrlKey || event.metaKey) && event.keyCode == 67) {
|
|
||||||
const { state } = view;
|
|
||||||
// @ts-ignore
|
|
||||||
const currentNode = state.selection.node;
|
|
||||||
|
|
||||||
if (currentNode) {
|
|
||||||
event.preventDefault();
|
|
||||||
copyNode(currentNode);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
clipboardTextSerializer: (slice) => {
|
|
||||||
const json = slice.content.toJSON();
|
|
||||||
const isSelectAll = slice.openStart === slice.openEnd && slice.openEnd === 0;
|
|
||||||
|
|
||||||
if (typeof json === 'object' || isSelectAll) {
|
|
||||||
return extensionThis.options.prosemirrorToMarkdown({
|
|
||||||
content: slice.content,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const isText = isPureText(json) && !isSelectAll;
|
|
||||||
|
|
||||||
if (isText) {
|
|
||||||
return slice.content.textBetween(0, slice.content.size, '\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
const doc = slice.content;
|
|
||||||
if (!doc) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const content = extensionThis.options.prosemirrorToMarkdown({
|
|
||||||
content: doc,
|
|
||||||
});
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
@ -103,6 +103,20 @@ export const SelectionExtension = Extension.create({
|
||||||
return decorationSet;
|
return decorationSet;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
filterTransaction(tr, state) {
|
||||||
|
// Prevent prosemirror's mutation observer overriding a node selection with a text selection
|
||||||
|
// for exact same range - this was cause of being unable to change dates in collab:
|
||||||
|
// https://product-fabric.atlassian.net/browse/ED-10645
|
||||||
|
if (
|
||||||
|
state.selection instanceof NodeSelection &&
|
||||||
|
tr.selection instanceof TextSelection &&
|
||||||
|
state.selection.from === tr.selection.from &&
|
||||||
|
state.selection.to === tr.selection.to
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
new Plugin({
|
new Plugin({
|
||||||
key: new PluginKey('preventSelection'),
|
key: new PluginKey('preventSelection'),
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Blockquote } from 'tiptap/core/extensions/blockquote';
|
||||||
import { Bold } from 'tiptap/core/extensions/bold';
|
import { Bold } from 'tiptap/core/extensions/bold';
|
||||||
import { BulletList } from 'tiptap/core/extensions/bullet-list';
|
import { BulletList } from 'tiptap/core/extensions/bullet-list';
|
||||||
import { Callout } from 'tiptap/core/extensions/callout';
|
import { Callout } from 'tiptap/core/extensions/callout';
|
||||||
|
import { Clipboard } from 'tiptap/core/extensions/clipboard';
|
||||||
import { Code, CodeMarkPlugin } from 'tiptap/core/extensions/code';
|
import { Code, CodeMarkPlugin } from 'tiptap/core/extensions/code';
|
||||||
import { CodeBlock } from 'tiptap/core/extensions/code-block';
|
import { CodeBlock } from 'tiptap/core/extensions/code-block';
|
||||||
import { Color } from 'tiptap/core/extensions/color';
|
import { Color } from 'tiptap/core/extensions/color';
|
||||||
|
@ -88,6 +89,9 @@ export const CollaborationKit = [
|
||||||
Blockquote,
|
Blockquote,
|
||||||
Bold,
|
Bold,
|
||||||
BulletList,
|
BulletList,
|
||||||
|
Clipboard.configure({
|
||||||
|
prosemirrorToMarkdown,
|
||||||
|
}),
|
||||||
Code,
|
Code,
|
||||||
CodeMarkPlugin,
|
CodeMarkPlugin,
|
||||||
CodeBlock,
|
CodeBlock,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { BackgroundColor } from 'tiptap/core/extensions/background-color';
|
||||||
import { Blockquote } from 'tiptap/core/extensions/blockquote';
|
import { Blockquote } from 'tiptap/core/extensions/blockquote';
|
||||||
import { Bold } from 'tiptap/core/extensions/bold';
|
import { Bold } from 'tiptap/core/extensions/bold';
|
||||||
import { BulletList } from 'tiptap/core/extensions/bullet-list';
|
import { BulletList } from 'tiptap/core/extensions/bullet-list';
|
||||||
|
import { Clipboard } from 'tiptap/core/extensions/clipboard';
|
||||||
import { Code } from 'tiptap/core/extensions/code';
|
import { Code } from 'tiptap/core/extensions/code';
|
||||||
import { CodeBlock } from 'tiptap/core/extensions/code-block';
|
import { CodeBlock } from 'tiptap/core/extensions/code-block';
|
||||||
import { Color } from 'tiptap/core/extensions/color';
|
import { Color } from 'tiptap/core/extensions/color';
|
||||||
|
@ -18,6 +19,7 @@ import { HardBreak } from 'tiptap/core/extensions/hard-break';
|
||||||
import { Heading } from 'tiptap/core/extensions/heading';
|
import { Heading } from 'tiptap/core/extensions/heading';
|
||||||
import { HorizontalRule } from 'tiptap/core/extensions/horizontal-rule';
|
import { HorizontalRule } from 'tiptap/core/extensions/horizontal-rule';
|
||||||
import { HTMLMarks } from 'tiptap/core/extensions/html-marks';
|
import { HTMLMarks } from 'tiptap/core/extensions/html-marks';
|
||||||
|
import { Image } from 'tiptap/core/extensions/image';
|
||||||
import { Indent } from 'tiptap/core/extensions/indent';
|
import { Indent } from 'tiptap/core/extensions/indent';
|
||||||
import { Italic } from 'tiptap/core/extensions/italic';
|
import { Italic } from 'tiptap/core/extensions/italic';
|
||||||
import { Katex } from 'tiptap/core/extensions/katex';
|
import { Katex } from 'tiptap/core/extensions/katex';
|
||||||
|
@ -53,6 +55,9 @@ export const CommentKit = [
|
||||||
Blockquote,
|
Blockquote,
|
||||||
Bold,
|
Bold,
|
||||||
BulletList,
|
BulletList,
|
||||||
|
Clipboard.configure({
|
||||||
|
prosemirrorToMarkdown,
|
||||||
|
}),
|
||||||
Code,
|
Code,
|
||||||
CodeBlock,
|
CodeBlock,
|
||||||
Color,
|
Color,
|
||||||
|
@ -70,6 +75,7 @@ export const CommentKit = [
|
||||||
HorizontalRule,
|
HorizontalRule,
|
||||||
...HTMLMarks,
|
...HTMLMarks,
|
||||||
Indent,
|
Indent,
|
||||||
|
Image,
|
||||||
Italic,
|
Italic,
|
||||||
Katex,
|
Katex,
|
||||||
Link,
|
Link,
|
||||||
|
|
Loading…
Reference in New Issue