Copy and refactoring Mentions to Tags, Started

This commit is contained in:
Wolfgang Huß 2019-06-17 18:44:17 +02:00
parent 53ce72908b
commit 4e780f52dc
4 changed files with 185 additions and 33 deletions

View File

@ -4,7 +4,12 @@
<ds-card>
<ds-input model="title" class="post-title" placeholder="Title" name="title" autofocus />
<no-ssr>
<hc-editor :users="users" :value="form.content" @input="updateEditorContent" />
<hc-editor
:users="users"
:tags="tags"
:value="form.content"
@input="updateEditorContent"
/>
</no-ssr>
<ds-space margin-bottom="xxx-large" />
<ds-flex class="contribution-form-footer">
@ -75,6 +80,7 @@ export default {
disabled: false,
slug: null,
users: [],
tags: [],
}
},
watch: {
@ -148,17 +154,34 @@ export default {
apollo: {
User: {
query() {
return gql(`{
User(orderBy: slug_asc) {
id
slug
return gql`
{
User(orderBy: slug_asc) {
id
slug
}
}
}`)
`
},
result(result) {
this.users = result.data.User
},
},
Tag: {
query() {
return gql`
{
Tag(orderBy: name_asc) {
id
name
}
}
`
},
result(result) {
this.tags = result.data.Tag
},
},
},
}
</script>

View File

@ -1,20 +1,30 @@
<template>
<div class="editor">
<div v-show="showSuggestions" ref="suggestions" class="suggestion-list">
<template v-if="hasResults">
<template v-if="usersFilterHasResults">
<div
v-for="(user, index) in filteredUsers"
:key="user.id"
class="suggestion-list__item"
:class="{ 'is-selected': navigatedUserIndex === index }"
@click="selectUser(user)"
@click="selectItem(user, 'mention')"
>
@{{ user.slug }}
</div>
</template>
<div v-else class="suggestion-list__item is-empty">
No users found
</div>
<div v-else class="suggestion-list__item is-empty">No users found</div>
<template v-if="tagsFilterHasResults">
<div
v-for="(tag, index) in filteredTags"
:key="tag.id"
class="suggestion-list__item"
:class="{ 'is-selected': navigatedUserIndex === index }"
@click="selectItem(tag, 'tag')"
>
#{{ tag.name }}
</div>
</template>
<div v-else class="suggestion-list__item is-empty">No users found</div>
</div>
<editor-menu-bubble :editor="editor">
@ -175,6 +185,7 @@ import {
History,
} from 'tiptap-extensions'
import Mention from './nodes/Mention.js'
import Tag from './nodes/Tag.js'
let throttleInputEvent
@ -186,6 +197,7 @@ export default {
},
props: {
users: { type: Array, default: () => [] },
tags: { type: Array, default: () => [] },
value: { type: String, default: '' },
doc: { type: Object, default: () => {} },
},
@ -250,17 +262,82 @@ export default {
onKeyDown: ({ event }) => {
// pressing up arrow
if (event.keyCode === 38) {
this.upHandler()
this.upHandler(this.filteredUsers)
return true
}
// pressing down arrow
if (event.keyCode === 40) {
this.downHandler()
this.downHandler(this.filteredUsers)
return true
}
// pressing enter
if (event.keyCode === 13) {
this.enterHandler()
this.enterHandler(this.filteredUsers, 'mention')
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)
},
}),
new Tag({
items: () => {
return this.tags
},
onEnter: ({ items, query, range, command, virtualNode }) => {
this.query = query
this.filteredTags = items
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.filteredTags = items
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.filteredTags = []
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) {
this.upHandler(this.filteredTags)
return true
}
// pressing down arrow
if (event.keyCode === 40) {
this.downHandler(this.filteredTags)
return true
}
// pressing enter
if (event.keyCode === 13) {
this.enterHandler(this.filteredTags, 'tag')
return true
}
return false
@ -291,17 +368,21 @@ export default {
query: null,
suggestionRange: null,
filteredUsers: [],
filteredTags: [],
navigatedUserIndex: 0,
insertMention: () => {},
observer: null,
}
},
computed: {
hasResults() {
usersFilterHasResults() {
return this.filteredUsers.length
},
tagsFilterHasResults() {
return this.filteredTags.length
},
showSuggestions() {
return this.query || this.hasResults
return this.query || this.usersFilterHasResults || this.tagsFilterHasResults
},
},
watch: {
@ -332,31 +413,40 @@ export default {
},
// navigate to the previous item
// if it's the first item, navigate to the last one
upHandler() {
upHandler(filteredArray) {
this.navigatedUserIndex =
(this.navigatedUserIndex + this.filteredUsers.length - 1) % this.filteredUsers.length
(this.navigatedUserIndex + this.filteredArray.length - 1) % this.filteredArray.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
downHandler(filteredArray) {
this.navigatedUserIndex = (this.navigatedUserIndex + 1) % this.filteredArray.length
},
enterHandler() {
const user = this.filteredUsers[this.navigatedUserIndex]
if (user) {
this.selectUser(user)
enterHandler(filteredArray, type) {
const item = this.filteredArray[this.navigatedUserIndex]
if (item) {
this.selectItem(item, type)
}
},
// 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) {
selectItem(item, type) {
const typeAttrs = {
mention: {
// TODO: use router here
url: `/profile/${item.id}`,
label: item.slug,
},
tag: {
// TODO: Fill up with input tag in search field
url: ``,
// callBack: () => {},
label: item.name,
},
}
this.insertMention({
range: this.suggestionRange,
attrs: {
// TODO: use router here
url: `/profile/${user.id}`,
label: user.slug,
},
attrs: typeAttrs[type],
})
this.editor.focus()
},

View File

@ -19,9 +19,9 @@ export default class Mention extends TipTapMention {
`${this.options.matcher.char}${node.attrs.label}`,
]
}
patchedSchema.parseDOM = [
// this is not implemented
]
// patchedSchema.parseDOM = [
// // this is not implemented
// ]
return patchedSchema
}
}

View File

@ -0,0 +1,39 @@
import { Mention as TipTapMention } from 'tiptap-extensions'
export default class Tag extends TipTapMention {
get defaultOptions() {
return {
matcher: {
char: '#',
allowSpaces: false,
startOfLine: false,
},
mentionClass: 'tag',
suggestionClass: 'tag-suggestion',
}
}
get schema() {
const patchedSchema = super.schema
patchedSchema.attrs = {
url: {},
label: {},
}
patchedSchema.toDOM = node => {
return [
'a',
{
class: this.options.mentionClass,
href: node.attrs.url,
target: '_blank',
},
`${this.options.matcher.char}${node.attrs.label}`,
]
}
// patchedSchema.parseDOM = [
// // this is not implemented
// ]
return patchedSchema
}
}