fix copying mentions as plain text

This commit is contained in:
Philipp Kühn 2021-01-19 20:27:51 +01:00
parent 80ec657053
commit 563f37d74b
11 changed files with 116 additions and 15 deletions

View File

@ -1,5 +1,5 @@
import { Node, mergeAttributes } from '@tiptap/core'
import { VueRenderer } from '@tiptap/vue'
import { VueNodeViewRenderer } from '@tiptap/vue'
import Component from './Component.vue'
export default Node.create({
@ -30,6 +30,6 @@ export default Node.create({
},
addNodeView() {
return VueRenderer(Component)
return VueNodeViewRenderer(Component)
},
})

View File

@ -1,5 +1,5 @@
import { Node, mergeAttributes } from '@tiptap/core'
import { VueRenderer } from '@tiptap/vue'
import { VueNodeViewRenderer } from '@tiptap/vue'
import Component from './Component.vue'
export default Node.create({
@ -24,6 +24,6 @@ export default Node.create({
},
addNodeView() {
return VueRenderer(Component)
return VueNodeViewRenderer(Component)
},
})

View File

@ -1,5 +1,5 @@
import { Node, mergeAttributes } from '@tiptap/core'
import { VueRenderer } from '@tiptap/vue'
import { VueNodeViewRenderer } from '@tiptap/vue'
import Component from './Component.vue'
export default Node.create({
@ -22,6 +22,6 @@ export default Node.create({
},
addNodeView() {
return VueRenderer(Component)
return VueNodeViewRenderer(Component)
},
})

View File

@ -7,7 +7,7 @@ Node views are the best thing since sliced bread, at least if youre a fan of
<!-- ```js
import { Node } from '@tiptap/core'
import { VueRenderer } from '@tiptap/vue'
import { VueNodeViewRenderer } from '@tiptap/vue'
import Component from './Component.vue'
export default Node.create({
@ -83,12 +83,12 @@ https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-task-item
```js
import { Node } from '@tiptap/core'
import { VueRenderer } from '@tiptap/vue'
import { VueNodeViewRenderer } from '@tiptap/vue'
import Component from './Component.vue'
export default Node.create({
addNodeView() {
return VueRenderer(Component)
return VueNodeViewRenderer(Component)
},
})
```

View File

@ -29,7 +29,7 @@ export class Editor extends EventEmitter {
private commandManager!: CommandManager
private extensionManager!: ExtensionManager
public extensionManager!: ExtensionManager
private css!: HTMLStyleElement

View File

@ -6,6 +6,7 @@ import { Editor } from './Editor'
import { Extensions, NodeViewRenderer } from './types'
import getSchema from './helpers/getSchema'
import getSchemaTypeByName from './helpers/getSchemaTypeByName'
import getNodeType from './helpers/getNodeType'
import splitExtensions from './helpers/splitExtensions'
import getAttributesFromExtensions from './helpers/getAttributesFromExtensions'
import getRenderedAttributes from './helpers/getRenderedAttributes'
@ -145,11 +146,9 @@ export default class ExtensionManager {
const context = {
options: extension.options,
editor,
type: getSchemaTypeByName(extension.config.name, this.schema),
type: getNodeType(extension.config.name, this.schema),
}
// @ts-ignore
const renderer = extension.config.addNodeView?.bind(context)?.() as NodeViewRenderer
const renderer = extension.config.addNodeView?.call(context) as NodeViewRenderer
const nodeview = (
node: ProsemirrorNode,
@ -173,4 +172,23 @@ export default class ExtensionManager {
}))
}
get textSerializers() {
const { editor } = this
const { nodeExtensions } = splitExtensions(this.extensions)
return Object.fromEntries(nodeExtensions
.filter(extension => !!extension.config.renderText)
.map(extension => {
const context = {
options: extension.options,
editor,
type: getNodeType(extension.config.name, this.schema),
}
const textSerializer = (props: { node: ProsemirrorNode }) => extension.config.renderText?.call(context, props)
return [extension.config.name, textSerializer]
}))
}
}

View File

@ -88,6 +88,20 @@ export interface NodeConfig<Options = any, Commands = {}> extends Overwrite<Exte
}
) => DOMOutputSpec) | null,
/**
* Render Text
*/
renderText?: ((
this: {
options: Options,
editor: Editor,
type: NodeType,
},
props: {
node: ProseMirrorNode,
}
) => string) | null,
/**
* Add Attributes
*/
@ -257,6 +271,7 @@ export class Node<Options = any, Commands = {}> {
isolating: null,
parseHTML: () => null,
renderHTML: null,
renderText: null,
addAttributes: () => ({}),
addNodeView: null,
onCreate: null,

View File

@ -0,0 +1,60 @@
import { Editor } from '@tiptap/core'
import { Plugin, PluginKey } from 'prosemirror-state'
import { Extension } from '../Extension'
const textBetween = (
editor: Editor,
from: number,
to: number,
blockSeparator?: string,
leafText?: string,
): string => {
let text = ''
let separated = true
editor.state.doc.nodesBetween(from, to, (node, pos) => {
const textSerializer = editor.extensionManager.textSerializers[node.type.name]
if (textSerializer) {
text += textSerializer({ node })
separated = !blockSeparator
} else if (node.isText) {
text += node?.text?.slice(Math.max(from, pos) - pos, to - pos)
separated = !blockSeparator
} else if (node.isLeaf && leafText) {
text += leafText
separated = !blockSeparator
} else if (!separated && node.isBlock) {
text += blockSeparator
separated = true
}
}, 0)
return text
}
export const ClipboardTextSerializer = Extension.create({
name: 'editable',
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey('clipboardTextSerializer'),
props: {
clipboardTextSerializer: () => {
const { editor } = this
const { from, to } = editor.state.selection
return textBetween(editor, from, to, '\n')
},
},
}),
]
},
})
declare module '@tiptap/core' {
interface AllExtensions {
ClipboardTextSerializer: typeof ClipboardTextSerializer,
}
}

View File

@ -1,3 +1,4 @@
export { ClipboardTextSerializer } from './clipboardTextSerializer'
export { Commands } from './commands'
export { Editable } from './editable'
export { FocusEvents } from './focusEvents'

View File

@ -60,6 +60,10 @@ export const Mention = Node.create({
return ['span', HTMLAttributes, `@${node.attrs.id}`]
},
renderText({ node }) {
return `@${node.attrs.id}`
},
addProseMirrorPlugins() {
return [
Suggestion({

View File

@ -16,7 +16,10 @@ export type SuggestionMatch = {
export function findSuggestionMatch(config: Trigger): SuggestionMatch {
const {
char, allowSpaces, startOfLine, $position,
char,
allowSpaces,
startOfLine,
$position,
} = config
// cancel if top level node