mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Copy and refactoring Mentions to Tags, Started
This commit is contained in:
parent
53ce72908b
commit
4e780f52dc
@ -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>
|
||||
|
||||
@ -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()
|
||||
},
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
39
webapp/components/Editor/nodes/Tag.js
Normal file
39
webapp/components/Editor/nodes/Tag.js
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user