From 9ae7e466d85dbe5bbbb7dce3cd9e39571d3400a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Sat, 13 Oct 2018 23:41:54 +0200 Subject: [PATCH] add floating menu --- .../Components/Routes/FloatingMenu/index.vue | 125 ++++++++++++++++++ examples/Components/Subnavigation/index.vue | 3 + examples/main.js | 7 + packages/tiptap/src/components/editor.js | 13 ++ packages/tiptap/src/utils/floatingMenu.js | 72 ++++++++++ packages/tiptap/src/utils/index.js | 1 + 6 files changed, 221 insertions(+) create mode 100644 examples/Components/Routes/FloatingMenu/index.vue create mode 100644 packages/tiptap/src/utils/floatingMenu.js diff --git a/examples/Components/Routes/FloatingMenu/index.vue b/examples/Components/Routes/FloatingMenu/index.vue new file mode 100644 index 000000000..2297ee7b5 --- /dev/null +++ b/examples/Components/Routes/FloatingMenu/index.vue @@ -0,0 +1,125 @@ + + + + + \ No newline at end of file diff --git a/examples/Components/Subnavigation/index.vue b/examples/Components/Subnavigation/index.vue index 3812772a8..ba93707e9 100644 --- a/examples/Components/Subnavigation/index.vue +++ b/examples/Components/Subnavigation/index.vue @@ -6,6 +6,9 @@ Menu Bubble + + Floating Menu + Links diff --git a/examples/main.js b/examples/main.js index 5f93c0f30..c7b881f0c 100644 --- a/examples/main.js +++ b/examples/main.js @@ -26,6 +26,13 @@ const routes = [ githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/MenuBubble', }, }, + { + path: '/floating-menu', + component: () => import('Components/Routes/FloatingMenu'), + meta: { + githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/FloatingMenu', + }, + }, { path: '/links', component: () => import('Components/Routes/Links'), diff --git a/packages/tiptap/src/components/editor.js b/packages/tiptap/src/components/editor.js index c8062ff93..6465d1993 100644 --- a/packages/tiptap/src/components/editor.js +++ b/packages/tiptap/src/components/editor.js @@ -11,6 +11,7 @@ import { ExtensionManager, initNodeViews, menuBubble, + floatingMenu, builtInKeymap, } from '../utils' import builtInNodes from '../nodes' @@ -98,6 +99,14 @@ export default { focus: this.focus, }) slots.push(this.menububbleNode) + } else if (name === 'floatingMenu') { + this.floatingMenuNode = slot({ + nodes: this.menuActions ? this.menuActions.nodes : null, + marks: this.menuActions ? this.menuActions.marks : null, + focused: this.view ? this.view.focused : false, + focus: this.focus, + }) + slots.push(this.floatingMenuNode) } }) @@ -196,6 +205,10 @@ export default { plugins.push(menuBubble(this.menububbleNode)) } + if (this.floatingMenuNode) { + plugins.push(floatingMenu(this.floatingMenuNode)) + } + return plugins }, diff --git a/packages/tiptap/src/utils/floatingMenu.js b/packages/tiptap/src/utils/floatingMenu.js new file mode 100644 index 000000000..acec369cc --- /dev/null +++ b/packages/tiptap/src/utils/floatingMenu.js @@ -0,0 +1,72 @@ +import { Plugin } from 'prosemirror-state' + +class Toolbar { + + constructor({ node, editorView }) { + this.editorView = editorView + this.node = node + this.element = this.node.elm + this.element.style.visibility = 'hidden' + this.element.style.opacity = 0 + + this.editorView.dom.addEventListener('blur', this.hide.bind(this)) + } + + update(view, lastState) { + const { state } = view + + // Don't do anything if the document/selection didn't change + if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) { + return + } + + if (!state.selection.empty) { + this.hide() + return + } + + const currentDom = view.domAtPos(state.selection.$anchor.pos) + const isActive = currentDom.node.innerHTML === '
' + && currentDom.node.tagName === 'P' + && currentDom.node.parentNode === view.dom + + if (!isActive) { + this.hide() + return + } + + const editorBoundings = this.element.offsetParent.getBoundingClientRect() + const cursorBoundings = view.coordsAtPos(state.selection.$anchor.pos) + const top = cursorBoundings.top - editorBoundings.top + + this.element.style.top = `${top}px` + this.show() + } + + show() { + this.element.style.visibility = 'visible' + this.element.style.opacity = 1 + } + + hide(event) { + if (event && event.relatedTarget) { + return + } + + this.element.style.visibility = 'hidden' + this.element.style.opacity = 0 + } + + destroy() { + this.editorView.dom.removeEventListener('blur', this.hide) + } + +} + +export default function (node) { + return new Plugin({ + view(editorView) { + return new Toolbar({ editorView, node }) + }, + }) +} diff --git a/packages/tiptap/src/utils/index.js b/packages/tiptap/src/utils/index.js index d102f8800..d75dc8144 100644 --- a/packages/tiptap/src/utils/index.js +++ b/packages/tiptap/src/utils/index.js @@ -2,5 +2,6 @@ export { default as buildMenuActions } from './buildMenuActions' export { default as builtInKeymap } from './builtInKeymap' export { default as ComponentView } from './ComponentView' export { default as initNodeViews } from './initNodeViews' +export { default as floatingMenu } from './floatingMenu' export { default as menuBubble } from './menuBubble' export { default as ExtensionManager } from './ExtensionManager'