From aea3f86a8f973c79e6c0205aa3f9547f981dac24 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 21 Oct 2023 11:33:49 +0200 Subject: [PATCH] feat(editor): add command list example copied from https://github.com/ueberdosis/tiptap/tree/252acb32d27a0f9af14813eeed83d8a50059a43a/demos/src/Experiments/Commands/Vue --- src/components/input/editor/CommandsList.vue | 118 +++++++++++++++++++ src/components/input/editor/commands.js | 26 ++++ src/components/input/editor/suggestion.js | 113 ++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 src/components/input/editor/CommandsList.vue create mode 100644 src/components/input/editor/commands.js create mode 100644 src/components/input/editor/suggestion.js diff --git a/src/components/input/editor/CommandsList.vue b/src/components/input/editor/CommandsList.vue new file mode 100644 index 000000000..a5f5c8048 --- /dev/null +++ b/src/components/input/editor/CommandsList.vue @@ -0,0 +1,118 @@ + + + + + \ No newline at end of file diff --git a/src/components/input/editor/commands.js b/src/components/input/editor/commands.js new file mode 100644 index 000000000..a16f097c9 --- /dev/null +++ b/src/components/input/editor/commands.js @@ -0,0 +1,26 @@ +import { Extension } from '@tiptap/core' +import Suggestion from '@tiptap/suggestion' + +export default Extension.create({ + name: 'commands', + + addOptions() { + return { + suggestion: { + char: '/', + command: ({ editor, range, props }) => { + props.command({ editor, range }) + }, + }, + } + }, + + addProseMirrorPlugins() { + return [ + Suggestion({ + editor: this.editor, + ...this.options.suggestion, + }), + ] + }, +}) \ No newline at end of file diff --git a/src/components/input/editor/suggestion.js b/src/components/input/editor/suggestion.js new file mode 100644 index 000000000..58c501023 --- /dev/null +++ b/src/components/input/editor/suggestion.js @@ -0,0 +1,113 @@ +import { VueRenderer } from '@tiptap/vue-3' +import tippy from 'tippy.js' + +import CommandsList from './CommandsList.vue' + +export default { + items: ({ query }) => { + return [ + { + title: 'H1', + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setNode('heading', { level: 1 }) + .run() + }, + }, + { + title: 'H2', + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setNode('heading', { level: 2 }) + .run() + }, + }, + { + title: 'bold', + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setMark('bold') + .run() + }, + }, + { + title: 'italic', + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setMark('italic') + .run() + }, + }, + ].filter(item => item.title.toLowerCase().startsWith(query.toLowerCase())).slice(0, 10) + }, + + render: () => { + let component + let popup + + return { + onStart: props => { + component = new VueRenderer(CommandsList, { + // using vue 2: + // parent: this, + // propsData: props, + props, + editor: props.editor, + }) + + if (!props.clientRect) { + return + } + + popup = tippy('body', { + getReferenceClientRect: props.clientRect, + appendTo: () => document.body, + content: component.element, + showOnCreate: true, + interactive: true, + trigger: 'manual', + placement: 'bottom-start', + }) + }, + + onUpdate(props) { + component.updateProps(props) + + if (!props.clientRect) { + return + } + + popup[0].setProps({ + getReferenceClientRect: props.clientRect, + }) + }, + + onKeyDown(props) { + if (props.event.key === 'Escape') { + popup[0].hide() + + return true + } + + return component.ref?.onKeyDown(props) + }, + + onExit() { + popup[0].destroy() + component.destroy() + }, + } + }, +} \ No newline at end of file