From 7728ca1de04fcba890145b5ce536b614cf3528d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Sat, 29 Sep 2018 12:33:18 +0200 Subject: [PATCH] refactoring --- .../Components/Routes/Suggestions/index.vue | 55 +++++++++++++++---- .../tiptap-extensions/src/nodes/Mention.js | 4 +- .../src/plugins/Suggestions.js | 4 +- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/examples/Components/Routes/Suggestions/index.vue b/examples/Components/Routes/Suggestions/index.vue index 6cca5e7f..ed4abb34 100644 --- a/examples/Components/Routes/Suggestions/index.vue +++ b/examples/Components/Routes/Suggestions/index.vue @@ -1,7 +1,7 @@ @@ -50,43 +50,54 @@ import { } from 'tiptap-extensions' export default { + components: { Editor, Icon, }, + data() { return { extensions: [ new HardBreakNode(), new HeadingNode({ maxLevel: 3 }), new MentionNode({ + // a list of all suggested items items: [ { id: 1, name: 'Philipp Kühn' }, { id: 2, name: 'Hans Pagel' }, { id: 3, name: 'Kris Siepert' }, { id: 4, name: 'Justin Schueler' }, ], + // is called when a suggestion starts onEnter: ({ items, query, range, command, virtualNode }) => { this.query = query this.filteredUsers = items - this.mentionPosition = range - this.insertMention = command + this.suggestionRange = range this.renderPopup(virtualNode) + // we save the command for inserting a selected mention + // this allows us to call it inside of our custom popup + // via keyboard navigation and on click + this.insertMention = command }, + // is called when a suggestion has changed onChange: ({ items, query, range, virtualNode }) => { this.query = query this.filteredUsers = items - this.mentionPosition = range + this.suggestionRange = range this.navigatedUserIndex = 0 this.renderPopup(virtualNode) }, + // is called when a suggestion is cancelled onExit: () => { + // reset all saved values this.query = null this.filteredUsers = [] - this.mentionPosition = null + this.suggestionRange = null this.navigatedUserIndex = 0 this.destroyPopup() }, + // is called on every keyDown event while a suggestion is active onKeyDown: ({ event }) => { // pressing up arrow if (event.keyCode === 38) { @@ -106,6 +117,10 @@ export default { return false }, + // is called when a suggestion has changed + // this function is optional because there is basic filtering built-in + // you can overwrite it if you prefer your own filtering + // in this example we use fuse.js with support for fuzzy search onFilter: (items, query) => { if (!query) { return items @@ -113,9 +128,7 @@ export default { const fuse = new Fuse(items, { threshold: 0.2, - keys: [ - 'name', - ], + keys: ['name'], }) return fuse.search(query) @@ -126,27 +139,39 @@ export default { new ItalicMark(), ], query: null, - mentionPosition: null, + suggestionRange: null, filteredUsers: [], navigatedUserIndex: 0, insertMention: () => {}, } }, + computed: { + hasResults() { return this.filteredUsers.length }, + showSuggestions() { return this.query || this.hasResults }, + }, + methods: { + + // navigate to the previous item + // if it's the first item, navigate to the last one upHandler() { this.navigatedUserIndex = ((this.navigatedUserIndex + this.filteredUsers.length) - 1) % this.filteredUsers.length }, + + // navigate to the next item + // if it's the last item, navigate to the first one downHandler() { this.navigatedUserIndex = (this.navigatedUserIndex + 1) % this.filteredUsers.length }, + enterHandler() { const user = this.filteredUsers[this.navigatedUserIndex] @@ -154,15 +179,21 @@ export default { this.selectUser(user) } }, + + // we have to replace our suggestion text with a mention + // so it's important to pass also the position of your suggestion text selectUser(user) { this.insertMention({ - position: this.mentionPosition, + replaceRange: this.suggestionRange, attrs: { id: user.id, label: user.name, }, }) }, + + // renders a popup with suggestions + // tiptap provides a virtualNode object for using popper.js (or tippy.js) for popups renderPopup(node) { if (this.popup) { return @@ -182,12 +213,14 @@ export default { arrowType: 'round', }) }, + destroyPopup() { if (this.popup) { this.popup.destroyAll() this.popup = null } }, + }, } diff --git a/packages/tiptap-extensions/src/nodes/Mention.js b/packages/tiptap-extensions/src/nodes/Mention.js index 828e0b5e..8f6d95f7 100644 --- a/packages/tiptap-extensions/src/nodes/Mention.js +++ b/packages/tiptap-extensions/src/nodes/Mention.js @@ -48,8 +48,8 @@ export default class MentionNode extends Node { allowSpaces: false, startOfLine: false, }, - command: ({ position, attrs, schema }) => { - return replaceText(position, schema.nodes.mention, attrs) + command: ({ range, attrs, schema }) => { + return replaceText(range, schema.nodes.mention, attrs) }, items: this.options.items, onEnter: this.options.onEnter, diff --git a/packages/tiptap-extensions/src/plugins/Suggestions.js b/packages/tiptap-extensions/src/plugins/Suggestions.js index 5d3ba446..876b94c6 100644 --- a/packages/tiptap-extensions/src/plugins/Suggestions.js +++ b/packages/tiptap-extensions/src/plugins/Suggestions.js @@ -146,9 +146,9 @@ export default function SuggestionsPlugin({ decorationNode, virtualNode, items: onFilter(items, next.text), - command: ({ position, attrs }) => { + command: ({ replaceRange, attrs }) => { command({ - position, + range: replaceRange, attrs, schema: view.state.schema, })(view.state, view.dispatch, view)