From ee00a3b3dfa78df86959081418ea7e373880cc48 Mon Sep 17 00:00:00 2001 From: Alina Beck Date: Fri, 23 Aug 2019 15:49:20 +0100 Subject: [PATCH 01/31] replace floating and bubble menu with menu bar --- webapp/components/Editor/Editor.vue | 138 +-------------------- webapp/components/Editor/MenuBar.vue | 78 ++++++++++++ webapp/components/Editor/MenuBarButton.vue | 16 +++ 3 files changed, 98 insertions(+), 134 deletions(-) create mode 100644 webapp/components/Editor/MenuBar.vue create mode 100644 webapp/components/Editor/MenuBarButton.vue diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index 7791facb6..c0bf3da43 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -43,137 +43,7 @@ - - - @@ -184,20 +54,20 @@ import linkify from 'linkify-it' import stringHash from 'string-hash' import Fuse from 'fuse.js' import tippy from 'tippy.js' -import { Editor, EditorContent, EditorFloatingMenu, EditorMenuBubble } from 'tiptap' +import { Editor, EditorContent } from 'tiptap' import EventHandler from './plugins/eventHandler.js' import { History } from 'tiptap-extensions' import Hashtag from './nodes/Hashtag.js' import Mention from './nodes/Mention.js' import { mapGetters } from 'vuex' +import HcMenuBar from './MenuBar' let throttleInputEvent export default { components: { EditorContent, - EditorFloatingMenu, - EditorMenuBubble, + HcMenuBar, }, props: { users: { type: Array, default: () => null }, // If 'null', than the Mention extention is not assigned. diff --git a/webapp/components/Editor/MenuBar.vue b/webapp/components/Editor/MenuBar.vue new file mode 100644 index 000000000..8156eee9e --- /dev/null +++ b/webapp/components/Editor/MenuBar.vue @@ -0,0 +1,78 @@ + + + diff --git a/webapp/components/Editor/MenuBarButton.vue b/webapp/components/Editor/MenuBarButton.vue new file mode 100644 index 000000000..28eae6583 --- /dev/null +++ b/webapp/components/Editor/MenuBarButton.vue @@ -0,0 +1,16 @@ + + + From cbb0d7933111579ae50c1706c55cec976fd2832e Mon Sep 17 00:00:00 2001 From: Alina Beck Date: Fri, 23 Aug 2019 17:37:17 +0100 Subject: [PATCH 02/31] put link bubble back in (wip) --- webapp/components/Editor/Editor.vue | 44 +++++++++++++++++++++++++--- webapp/components/Editor/MenuBar.vue | 35 ++++++++++------------ 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index c0bf3da43..72b95771d 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -43,7 +43,42 @@ - + + @@ -54,20 +89,21 @@ import linkify from 'linkify-it' import stringHash from 'string-hash' import Fuse from 'fuse.js' import tippy from 'tippy.js' -import { Editor, EditorContent } from 'tiptap' +import { Editor, EditorContent, EditorMenuBubble } from 'tiptap' import EventHandler from './plugins/eventHandler.js' import { History } from 'tiptap-extensions' import Hashtag from './nodes/Hashtag.js' import Mention from './nodes/Mention.js' import { mapGetters } from 'vuex' -import HcMenuBar from './MenuBar' +import MenuBar from './MenuBar' let throttleInputEvent export default { components: { EditorContent, - HcMenuBar, + EditorMenuBubble, + MenuBar, }, props: { users: { type: Array, default: () => null }, // If 'null', than the Mention extention is not assigned. diff --git a/webapp/components/Editor/MenuBar.vue b/webapp/components/Editor/MenuBar.vue index 8156eee9e..d4ff1a673 100644 --- a/webapp/components/Editor/MenuBar.vue +++ b/webapp/components/Editor/MenuBar.vue @@ -1,65 +1,57 @@ @@ -73,6 +65,9 @@ export default { EditorMenuBar, MenuBarButton, }, - props: ['editor'], + props: { + editor: Object, + showLinkMenu: Function, + }, } From fbe4de347bf8a4f0ac6115096773113fb2e0daee Mon Sep 17 00:00:00 2001 From: Alina Beck Date: Fri, 23 Aug 2019 22:30:05 +0100 Subject: [PATCH 03/31] extract suggestions menu into separate component --- webapp/components/Editor/Editor.vue | 62 +++++--------------- webapp/components/Editor/SuggestionsMenu.vue | 59 +++++++++++++++++++ 2 files changed, 75 insertions(+), 46 deletions(-) create mode 100644 webapp/components/Editor/SuggestionsMenu.vue diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index 72b95771d..d660e9d15 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -1,48 +1,16 @@ @@ -96,6 +64,7 @@ import Hashtag from './nodes/Hashtag.js' import Mention from './nodes/Mention.js' import { mapGetters } from 'vuex' import MenuBar from './MenuBar' +import SuggestionsMenu from './SuggestionsMenu' let throttleInputEvent @@ -104,6 +73,7 @@ export default { EditorContent, EditorMenuBubble, MenuBar, + SuggestionsMenu, }, props: { users: { type: Array, default: () => null }, // If 'null', than the Mention extention is not assigned. @@ -400,7 +370,7 @@ export default { return } this.popup = tippy(node, { - content: this.$refs.suggestions, + content: this.$refs.suggestions.$el, trigger: 'mouseenter', interactive: true, theme: 'dark', @@ -416,7 +386,7 @@ export default { this.observer = new MutationObserver(() => { this.popup.popperInstance.scheduleUpdate() }) - this.observer.observe(this.$refs.suggestions, { + this.observer.observe(this.$refs.suggestions.$el, { childList: true, subtree: true, characterData: true, diff --git a/webapp/components/Editor/SuggestionsMenu.vue b/webapp/components/Editor/SuggestionsMenu.vue new file mode 100644 index 000000000..54877acc3 --- /dev/null +++ b/webapp/components/Editor/SuggestionsMenu.vue @@ -0,0 +1,59 @@ + + + From 2c0b2ed482643be5855df851569bac58758913a0 Mon Sep 17 00:00:00 2001 From: Alina Beck Date: Fri, 23 Aug 2019 23:21:24 +0100 Subject: [PATCH 04/31] place tippy popup in renderless component --- webapp/components/Editor/ContextMenu.vue | 52 ++++++++++++++++++++++ webapp/components/Editor/Editor.vue | 55 ++++-------------------- 2 files changed, 61 insertions(+), 46 deletions(-) create mode 100644 webapp/components/Editor/ContextMenu.vue diff --git a/webapp/components/Editor/ContextMenu.vue b/webapp/components/Editor/ContextMenu.vue new file mode 100644 index 000000000..4297cf971 --- /dev/null +++ b/webapp/components/Editor/ContextMenu.vue @@ -0,0 +1,52 @@ + diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index d660e9d15..9e1787862 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -48,6 +48,7 @@ + @@ -56,7 +57,6 @@ import defaultExtensions from './defaultExtensions.js' import linkify from 'linkify-it' import stringHash from 'string-hash' import Fuse from 'fuse.js' -import tippy from 'tippy.js' import { Editor, EditorContent, EditorMenuBubble } from 'tiptap' import EventHandler from './plugins/eventHandler.js' import { History } from 'tiptap-extensions' @@ -65,6 +65,7 @@ import Mention from './nodes/Mention.js' import { mapGetters } from 'vuex' import MenuBar from './MenuBar' import SuggestionsMenu from './SuggestionsMenu' +import ContextMenu from './ContextMenu' let throttleInputEvent @@ -74,6 +75,7 @@ export default { EditorMenuBubble, MenuBar, SuggestionsMenu, + ContextMenu, }, props: { users: { type: Array, default: () => null }, // If 'null', than the Mention extention is not assigned. @@ -99,7 +101,7 @@ export default { this.query = query this.filteredItems = items this.suggestionRange = range - this.renderPopup(virtualNode) + this.$refs.contextMenu.displayContextMenu(virtualNode, this.$refs.suggestions.$el) // 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 @@ -111,7 +113,7 @@ export default { this.filteredItems = items this.suggestionRange = range this.navigatedItemIndex = 0 - this.renderPopup(virtualNode) + this.$refs.contextMenu.displayContextMenu(virtualNode, this.$refs.suggestions.$el) }, // is called when a suggestion is cancelled onExit: () => { @@ -122,7 +124,7 @@ export default { this.filteredItems = [] this.suggestionRange = null this.navigatedItemIndex = 0 - this.destroyPopup() + this.$refs.contextMenu.hideContextMenu() }, // is called on every keyDown event while a suggestion is active onKeyDown: ({ event }) => { @@ -175,7 +177,7 @@ export default { this.query = this.sanitizedQuery(query) this.filteredItems = items this.suggestionRange = range - this.renderPopup(virtualNode) + this.$refs.contextMenu.displayContextMenu(virtualNode, this.$refs.suggestions.$el) // 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 @@ -187,7 +189,7 @@ export default { this.filteredItems = items this.suggestionRange = range this.navigatedItemIndex = 0 - this.renderPopup(virtualNode) + this.$refs.contextMenu.displayContextMenu(virtualNode, this.$refs.suggestions.$el) }, // is called when a suggestion is cancelled onExit: () => { @@ -198,7 +200,7 @@ export default { this.filteredItems = [] this.suggestionRange = null this.navigatedItemIndex = 0 - this.destroyPopup() + this.$refs.contextMenu.hideContextMenu() }, // is called on every keyDown event while a suggestion is active onKeyDown: ({ event }) => { @@ -363,45 +365,6 @@ export default { }) this.editor.focus() }, - // 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 - } - this.popup = tippy(node, { - content: this.$refs.suggestions.$el, - trigger: 'mouseenter', - interactive: true, - theme: 'dark', - placement: 'top-start', - inertia: true, - duration: [400, 200], - showOnInit: true, - arrow: true, - arrowType: 'round', - }) - // we have to update tippy whenever the DOM is updated - if (MutationObserver) { - this.observer = new MutationObserver(() => { - this.popup.popperInstance.scheduleUpdate() - }) - this.observer.observe(this.$refs.suggestions.$el, { - childList: true, - subtree: true, - characterData: true, - }) - } - }, - destroyPopup() { - if (this.popup) { - this.popup.destroy() - this.popup = null - } - if (this.observer) { - this.observer.disconnect() - } - }, onUpdate(e) { const content = e.getHTML() const contentHash = stringHash(content) From 62775a93d6183879d800cb3ffe0ceb2d9b5fb76d Mon Sep 17 00:00:00 2001 From: Alina Beck Date: Sat, 24 Aug 2019 10:42:24 +0100 Subject: [PATCH 05/31] put link input in separate component --- webapp/components/Editor/ContextMenu.vue | 4 +- webapp/components/Editor/Editor.vue | 71 +++++------------------- webapp/components/Editor/LinkInput.vue | 23 ++++++++ webapp/components/Editor/MenuBar.vue | 3 +- 4 files changed, 42 insertions(+), 59 deletions(-) create mode 100644 webapp/components/Editor/LinkInput.vue diff --git a/webapp/components/Editor/ContextMenu.vue b/webapp/components/Editor/ContextMenu.vue index 4297cf971..d0cf4c087 100644 --- a/webapp/components/Editor/ContextMenu.vue +++ b/webapp/components/Editor/ContextMenu.vue @@ -7,13 +7,13 @@ export default { node: Object, }, methods: { - displayContextMenu(target, content) { + displayContextMenu(target, content, trigger) { if (this.menu) { return } this.menu = tippy(target, { content: content, - trigger: 'mouseenter', + trigger: trigger || 'mouseenter', interactive: true, theme: 'dark', placement: 'top-start', diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index 9e1787862..63f948e7e 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -1,5 +1,8 @@ @@ -57,25 +23,26 @@ import defaultExtensions from './defaultExtensions.js' import linkify from 'linkify-it' import stringHash from 'string-hash' import Fuse from 'fuse.js' -import { Editor, EditorContent, EditorMenuBubble } from 'tiptap' +import { Editor, EditorContent } from 'tiptap' import EventHandler from './plugins/eventHandler.js' import { History } from 'tiptap-extensions' import Hashtag from './nodes/Hashtag.js' import Mention from './nodes/Mention.js' import { mapGetters } from 'vuex' import MenuBar from './MenuBar' -import SuggestionsMenu from './SuggestionsMenu' import ContextMenu from './ContextMenu' +import SuggestionsMenu from './SuggestionsMenu' +import LinkInput from './LinkInput' let throttleInputEvent export default { components: { + ContextMenu, EditorContent, - EditorMenuBubble, + LinkInput, MenuBar, SuggestionsMenu, - ContextMenu, }, props: { users: { type: Array, default: () => null }, // If 'null', than the Mention extention is not assigned. @@ -373,26 +340,24 @@ export default { this.$emit('input', content) } }, - showLinkMenu(attrs) { + showLinkMenu(attrs, element) { this.linkUrl = attrs.href this.linkMenuIsActive = true + this.$refs.contextMenu.displayContextMenu(element, this.$refs.linkMenu.$el, 'click') this.$nextTick(() => { try { - const $el = this.$refs.linkInput.$el.getElementsByTagName('input')[0] - $el.focus() - $el.select() + const input = this.$refs.linkMenu.$el.querySelector('input') + input.focus() + input.select() } catch (err) {} }) }, hideLinkMenu() { + this.$refs.contextMenu.hideContextMenu() this.linkUrl = null this.linkMenuIsActive = false this.editor.focus() }, - hideMenu(isActive) { - isActive = false - this.hideLinkMenu() - }, setLinkUrl(command, url) { const links = linkify().match(url) if (links) { @@ -469,12 +434,6 @@ export default { } } -.ProseMirror { - padding: $space-base; - margin: -$space-base; - min-height: $space-large; -} - .ProseMirror:focus { outline: none; } diff --git a/webapp/components/Editor/LinkInput.vue b/webapp/components/Editor/LinkInput.vue new file mode 100644 index 000000000..d870065ef --- /dev/null +++ b/webapp/components/Editor/LinkInput.vue @@ -0,0 +1,23 @@ + + + diff --git a/webapp/components/Editor/MenuBar.vue b/webapp/components/Editor/MenuBar.vue index d4ff1a673..38f278978 100644 --- a/webapp/components/Editor/MenuBar.vue +++ b/webapp/components/Editor/MenuBar.vue @@ -6,8 +6,9 @@ From af985ba95cafda65078f85b5eb43f37d524a280c Mon Sep 17 00:00:00 2001 From: Alina Beck Date: Sat, 24 Aug 2019 10:44:37 +0100 Subject: [PATCH 06/31] rename suggestions menu to suggestion list --- webapp/components/Editor/Editor.vue | 6 +++--- .../Editor/{SuggestionsMenu.vue => SuggestionList.vue} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename webapp/components/Editor/{SuggestionsMenu.vue => SuggestionList.vue} (100%) diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index 63f948e7e..285a1dc33 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -3,7 +3,7 @@ - null }, // If 'null', than the Mention extention is not assigned. diff --git a/webapp/components/Editor/SuggestionsMenu.vue b/webapp/components/Editor/SuggestionList.vue similarity index 100% rename from webapp/components/Editor/SuggestionsMenu.vue rename to webapp/components/Editor/SuggestionList.vue From 6b452a2217220c7be62937568bca8ee1ffc59efa Mon Sep 17 00:00:00 2001 From: Alina Beck Date: Sat, 24 Aug 2019 11:59:59 +0100 Subject: [PATCH 07/31] refactor duplicate code --- webapp/components/Editor/Editor.vue | 232 ++++++++++------------------ 1 file changed, 78 insertions(+), 154 deletions(-) diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index 285a1dc33..48ebe30c4 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -51,81 +51,19 @@ export default { doc: { type: Object, default: () => {} }, }, data() { - // Set array of optional extensions by analysing the props. let optionalExtensions = [] // Don't change the following line. The functionallity is in danger! if (this.users) { optionalExtensions.push( new Mention({ - // a list of all suggested items items: () => { return this.users }, - // is called when a suggestion starts - onEnter: ({ items, query, range, command, virtualNode }) => { - this.suggestionType = this.mentionSuggestionType - - this.query = query - this.filteredItems = items - this.suggestionRange = range - this.$refs.contextMenu.displayContextMenu(virtualNode, this.$refs.suggestions.$el) - // 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.insertMentionOrHashtag = command - }, - // is called when a suggestion has changed - onChange: ({ items, query, range, virtualNode }) => { - this.query = query - this.filteredItems = items - this.suggestionRange = range - this.navigatedItemIndex = 0 - this.$refs.contextMenu.displayContextMenu(virtualNode, this.$refs.suggestions.$el) - }, - // is called when a suggestion is cancelled - onExit: () => { - this.suggestionType = this.nullSuggestionType - - // reset all saved values - this.query = null - this.filteredItems = [] - this.suggestionRange = null - this.navigatedItemIndex = 0 - this.$refs.contextMenu.hideContextMenu() - }, - // is called on every keyDown event while a suggestion is active - onKeyDown: ({ event }) => { - // pressing up arrow - if (event.keyCode === 38) { - this.upHandler() - return true - } - // pressing down arrow - if (event.keyCode === 40) { - this.downHandler() - return true - } - // pressing enter - if (event.keyCode === 13) { - this.enterHandler() - return true - } - 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 - } - const fuse = new Fuse(items, { - threshold: 0.2, - keys: ['slug'], - }) - return fuse.search(query) - }, + onEnter: props => this.openSuggestionList(props, this.mentionSuggestionType), + onChange: this.updateSuggestionList, + onExit: this.closeSuggestionList, + onKeyDown: this.navigateSuggestionList, + onFilter: this.filterSuggestionList, }), ) } @@ -133,81 +71,14 @@ export default { if (this.hashtags) { optionalExtensions.push( new Hashtag({ - // a list of all suggested items items: () => { return this.hashtags }, - // is called when a suggestion starts - onEnter: ({ items, query, range, command, virtualNode }) => { - this.suggestionType = this.hashtagSuggestionType - - this.query = this.sanitizedQuery(query) - this.filteredItems = items - this.suggestionRange = range - this.$refs.contextMenu.displayContextMenu(virtualNode, this.$refs.suggestions.$el) - // 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.insertMentionOrHashtag = command - }, - // is called when a suggestion has changed - onChange: ({ items, query, range, virtualNode }) => { - this.query = this.sanitizedQuery(query) - this.filteredItems = items - this.suggestionRange = range - this.navigatedItemIndex = 0 - this.$refs.contextMenu.displayContextMenu(virtualNode, this.$refs.suggestions.$el) - }, - // is called when a suggestion is cancelled - onExit: () => { - this.suggestionType = this.nullSuggestionType - - // reset all saved values - this.query = null - this.filteredItems = [] - this.suggestionRange = null - this.navigatedItemIndex = 0 - this.$refs.contextMenu.hideContextMenu() - }, - // is called on every keyDown event while a suggestion is active - onKeyDown: ({ event }) => { - // pressing up arrow - if (event.keyCode === 38) { - this.upHandler() - return true - } - // pressing down arrow - if (event.keyCode === 40) { - this.downHandler() - return true - } - // pressing enter - if (event.keyCode === 13) { - this.enterHandler() - return true - } - // pressing space - if (event.keyCode === 32) { - this.spaceHandler() - return true - } - 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) => { - query = this.sanitizedQuery(query) - if (!query) { - return items - } - return items.filter(item => - JSON.stringify(item) - .toLowerCase() - .includes(query.toLowerCase()), - ) - }, + onEnter: props => this.openSuggestionList(props, this.hashtagSuggestionType), + onChange: this.updateSuggestionList, + onExit: this.closeSuggestionList, + onKeyDown: this.navigateSuggestionList, + onFilter: this.filterSuggestionList, }), ) } @@ -283,32 +154,85 @@ export default { this.editor.destroy() }, methods: { - sanitizedQuery(query) { - // remove all not allowed chars - query = query.replace(/[^a-zA-Z0-9]/gm, '') - // if the query is only made of digits, make it empty - return query.replace(/[0-9]/gm, '') === '' ? '' : query + openSuggestionList({ items, query, range, command, virtualNode }, suggestionType) { + this.suggestionType = suggestionType + + this.query = this.sanitizeQuery(query) + this.filteredItems = items + this.suggestionRange = range + this.$refs.contextMenu.displayContextMenu(virtualNode, this.$refs.suggestions.$el) + this.insertMentionOrHashtag = command }, - // navigate to the previous item - // if it's the first item, navigate to the last one - upHandler() { + updateSuggestionList({ items, query, range, virtualNode }) { + this.query = this.sanitizeQuery(query) + this.filteredItems = items + this.suggestionRange = range + this.navigatedItemIndex = 0 + this.$refs.contextMenu.displayContextMenu(virtualNode, this.$refs.suggestions.$el) + }, + closeSuggestionList() { + this.suggestionType = this.nullSuggestionType + this.query = null + this.filteredItems = [] + this.suggestionRange = null + this.navigatedItemIndex = 0 + this.$refs.contextMenu.hideContextMenu() + }, + navigateSuggestionList({ event }) { + switch (event.keyCode) { + case 38: + this.handleUpArrow() + return true + + case 40: + this.handleDownArrow() + return true + + case 13: + this.handleEnter() + return true + + case 32: + this.handleSpace() + return true + + default: + return false + } + }, + filterSuggestionList(items, query) { + query = this.sanitizeQuery(query) + if (!query) { + return items + } + return items.filter(item => { + const itemString = item.slug || item.id + return itemString.toLowerCase().includes(query.toLowerCase()) + }) + }, + sanitizeQuery(query) { + if (this.suggestionType === this.hashtagSuggestionType) { + // remove all not allowed chars + query = query.replace(/[^a-zA-Z0-9]/gm, '') + // if the query is only made of digits, make it empty + return query.replace(/[0-9]/gm, '') === '' ? '' : query + } + return query + }, + handleUpArrow() { this.navigatedItemIndex = (this.navigatedItemIndex + this.filteredItems.length - 1) % this.filteredItems.length }, - // navigate to the next item - // if it's the last item, navigate to the first one - downHandler() { + handleDownArrow() { this.navigatedItemIndex = (this.navigatedItemIndex + 1) % this.filteredItems.length }, - // Handles pressing of enter. - enterHandler() { + handleEnter() { const item = this.filteredItems[this.navigatedItemIndex] if (item) { this.selectItem(item) } }, - // For hashtags handles pressing of space. - spaceHandler() { + handleSpace() { if (this.suggestionType === this.hashtagSuggestionType && this.query !== '') { this.selectItem({ id: this.query }) } From dc70da88264ef3bddf89774eba98fb0842f6b985 Mon Sep 17 00:00:00 2001 From: Alina Beck Date: Sat, 24 Aug 2019 12:42:34 +0100 Subject: [PATCH 08/31] make optional extensions a computed property --- webapp/components/Editor/Editor.vue | 123 ++++++++++++++-------------- 1 file changed, 63 insertions(+), 60 deletions(-) diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index 48ebe30c4..1d601da42 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -19,22 +19,23 @@ diff --git a/webapp/constants/editor.js b/webapp/constants/editor.js new file mode 100644 index 000000000..ac4e53641 --- /dev/null +++ b/webapp/constants/editor.js @@ -0,0 +1,2 @@ +export const HASHTAG = 'hashtag' +export const MENTION = 'mention' diff --git a/webapp/constants/keycodes.js b/webapp/constants/keycodes.js new file mode 100644 index 000000000..b34eb8dbe --- /dev/null +++ b/webapp/constants/keycodes.js @@ -0,0 +1,4 @@ +export const ARROW_UP = 38 +export const ARROW_DOWN = 40 +export const RETURN = 13 +export const SPACE = 32 From 62e34bf5b0ed8aa807c8f557a1047d8c2636faf9 Mon Sep 17 00:00:00 2001 From: Alina Beck Date: Mon, 26 Aug 2019 12:46:46 +0100 Subject: [PATCH 10/31] refactor suggestion list --- webapp/components/Editor/Editor.vue | 34 ------ webapp/components/Editor/SuggestionList.vue | 114 ++++++++++++-------- 2 files changed, 70 insertions(+), 78 deletions(-) diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index d08257347..c511eae8e 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -4,14 +4,12 @@ @@ -68,12 +66,6 @@ export default { }, computed: { ...mapGetters({ placeholder: 'editor/placeholder' }), - hasResults() { - return this.filteredItems.length - }, - showSuggestions() { - return this.query || this.hasResults - }, optionalExtensions() { const extensions = [] // Don't change the following line. The functionallity is in danger! @@ -290,32 +282,6 @@ export default { From 1df3cb1ed13f5a58321cce1ae7b2bbdc6ddb132c Mon Sep 17 00:00:00 2001 From: Alina Beck Date: Mon, 26 Aug 2019 16:08:46 +0100 Subject: [PATCH 11/31] get link input to work --- webapp/components/Editor/ContextMenu.vue | 25 ++++++++----- webapp/components/Editor/Editor.vue | 43 +++++++++++----------- webapp/components/Editor/LinkInput.vue | 16 ++++---- webapp/components/Editor/MenuBar.vue | 4 +- webapp/components/Editor/MenuBarButton.vue | 2 +- 5 files changed, 50 insertions(+), 40 deletions(-) diff --git a/webapp/components/Editor/ContextMenu.vue b/webapp/components/Editor/ContextMenu.vue index d0cf4c087..487920d2b 100644 --- a/webapp/components/Editor/ContextMenu.vue +++ b/webapp/components/Editor/ContextMenu.vue @@ -7,21 +7,28 @@ export default { node: Object, }, methods: { - displayContextMenu(target, content, trigger) { + displayContextMenu(target, content, type) { + const trigger = type === 'link' ? 'click' : 'mouseenter' + const showOnInit = type !== 'link' + if (this.menu) { return } + this.menu = tippy(target, { - content: content, - trigger: trigger || 'mouseenter', - interactive: true, - theme: 'dark', - placement: 'top-start', - inertia: true, - duration: [400, 200], - showOnInit: true, arrow: true, arrowType: 'round', + content: content, + // duration: [400, 200], + inertia: true, + interactive: true, + placement: 'top-start', + showOnInit, + theme: 'dark', + trigger, + onMount(instance) { + instance.popper.querySelector('input').focus() + }, }) // we have to update tippy whenever the DOM is updated if (MutationObserver) { diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index c511eae8e..168e2e732 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -1,6 +1,6 @@ @@ -54,7 +61,7 @@ export default { lastValueHash: null, editor: null, linkUrl: null, - linkMenuIsActive: false, + isLinkInputActive: false, suggestionType: '', query: null, suggestionRange: null, @@ -242,23 +249,17 @@ export default { this.$emit('input', content) } }, - showLinkMenu(attrs, element) { - this.linkUrl = attrs.href - this.linkMenuIsActive = true - this.$refs.contextMenu.displayContextMenu(element, this.$refs.linkMenu.$el, 'click') - this.$nextTick(() => { - try { - const input = this.$refs.linkMenu.$el.querySelector('input') - input.focus() - input.select() - } catch (err) {} - }) - }, - hideLinkMenu() { - this.$refs.contextMenu.hideContextMenu() - this.linkUrl = null - this.linkMenuIsActive = false - this.editor.focus() + toggleLinkInput(attrs, element) { + if (!this.isLinkInputActive && attrs && element) { + this.linkUrl = attrs.href + this.isLinkInputActive = true + this.$refs.contextMenu.displayContextMenu(element, this.$refs.linkInput.$el, 'link') + } else { + this.$refs.contextMenu.hideContextMenu() + this.linkUrl = null + this.isLinkInputActive = false + this.editor.focus() + } }, setLinkUrl(command, url) { const links = linkify().match(url) @@ -267,7 +268,7 @@ export default { command({ href: links.pop().url, }) - this.hideLinkMenu() + this.toggleLinkInput() this.editor.focus() } else if (!url) { // remove link diff --git a/webapp/components/Editor/LinkInput.vue b/webapp/components/Editor/LinkInput.vue index d870065ef..7478b43b3 100644 --- a/webapp/components/Editor/LinkInput.vue +++ b/webapp/components/Editor/LinkInput.vue @@ -1,11 +1,13 @@ @@ -16,10 +15,19 @@ From ba1e62da214f11e6cb9750f0e69efec4c74a98b6 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Mon, 26 Aug 2019 18:54:21 +0200 Subject: [PATCH 13/31] Update the apollo cache --- webapp/components/Comment.vue | 44 ++++++++++++++++++------------ webapp/graphql/CommentMutations.js | 7 +++++ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/webapp/components/Comment.vue b/webapp/components/Comment.vue index 6d3b05eff..9b3f2861b 100644 --- a/webapp/components/Comment.vue +++ b/webapp/components/Comment.vue @@ -62,12 +62,13 @@ + + diff --git a/webapp/components/Editor/Editor.vue b/webapp/components/Editor/Editor.vue index 98b42809c..fb75914d3 100644 --- a/webapp/components/Editor/Editor.vue +++ b/webapp/components/Editor/Editor.vue @@ -1,7 +1,7 @@