Avoid Hashtags in Comments in the Editor

Co-Authored-By: mattwr18 <mattwr18@gmail.com>
This commit is contained in:
Wolfgang Huß 2019-07-10 18:35:19 +02:00
parent e6e9b1b7d7
commit 1255a46b7a
6 changed files with 205 additions and 160 deletions

View File

@ -26,7 +26,10 @@ describe('ContributionForm.vue', () => {
const postTitle = 'this is a title for a post' const postTitle = 'this is a title for a post'
const postContent = 'this is a post' const postContent = 'this is a post'
const imageUpload = { const imageUpload = {
file: { filename: 'avataar.svg', previewElement: '' }, file: {
filename: 'avataar.svg',
previewElement: '',
},
url: 'someUrlToImage', url: 'someUrlToImage',
} }
const image = '/uploads/1562010976466-avataaars' const image = '/uploads/1562010976466-avataaars'
@ -217,7 +220,12 @@ describe('ContributionForm.vue', () => {
content: 'auf Deutsch geschrieben', content: 'auf Deutsch geschrieben',
language: 'de', language: 'de',
image, image,
categories: [{ id: 'cat12', name: 'Democracy & Politics' }], categories: [
{
id: 'cat12',
name: 'Democracy & Politics',
},
],
}, },
} }
wrapper = Wrapper() wrapper = Wrapper()

View File

@ -224,6 +224,170 @@ export default {
doc: { type: Object, default: () => {} }, doc: { type: Object, default: () => {} },
}, },
data() { data() {
let mentionExtension
if (!this.users) {
mentionExtension = []
} else {
mentionExtension = [
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.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.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.renderPopup(virtualNode)
},
// 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.destroyPopup()
},
// 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)
},
}),
]
}
let hashtagExtension
if (!this.hashtags) {
hashtagExtension = []
} else {
hashtagExtension = [
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.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.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.renderPopup(virtualNode)
},
// 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.destroyPopup()
},
// 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()),
)
},
}),
]
}
return { return {
lastValueHash: null, lastValueHash: null,
editor: new Editor({ editor: new Editor({
@ -249,154 +413,8 @@ export default {
emptyNodeText: this.placeholder || this.$t('editor.placeholder'), emptyNodeText: this.placeholder || this.$t('editor.placeholder'),
}), }),
new History(), new History(),
new Mention({ ...mentionExtension,
// a list of all suggested items ...hashtagExtension,
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.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.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.renderPopup(virtualNode)
},
// 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.destroyPopup()
},
// 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)
},
}),
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.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.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.renderPopup(virtualNode)
},
// 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.destroyPopup()
},
// 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()),
)
},
}),
], ],
onUpdate: e => { onUpdate: e => {
clearTimeout(throttleInputEvent) clearTimeout(throttleInputEvent)

View File

@ -1,5 +1,5 @@
import { mount, createLocalVue } from '@vue/test-utils' import { mount, createLocalVue } from '@vue/test-utils'
import CommentForm from './index.vue' import CommentForm from './CommentForm.vue'
import Styleguide from '@human-connection/styleguide' import Styleguide from '@human-connection/styleguide'
import Vuex from 'vuex' import Vuex from 'vuex'
@ -21,9 +21,15 @@ describe('CommentForm.vue', () => {
mutate: jest mutate: jest
.fn() .fn()
.mockResolvedValueOnce({ .mockResolvedValueOnce({
data: { CreateComment: { contentExcerpt: 'this is a comment' } }, data: {
CreateComment: {
contentExcerpt: 'this is a comment',
},
},
}) })
.mockRejectedValue({ message: 'Ouch!' }), .mockRejectedValue({
message: 'Ouch!',
}),
}, },
$toast: { $toast: {
error: jest.fn(), error: jest.fn(),
@ -31,7 +37,9 @@ describe('CommentForm.vue', () => {
}, },
} }
propsData = { propsData = {
post: { id: 1 }, post: {
id: 1,
},
} }
}) })
@ -45,7 +53,12 @@ describe('CommentForm.vue', () => {
getters, getters,
}) })
const Wrapper = () => { const Wrapper = () => {
return mount(CommentForm, { mocks, localVue, propsData, store }) return mount(CommentForm, {
mocks,
localVue,
propsData,
store,
})
} }
beforeEach(() => { beforeEach(() => {

View File

@ -2,7 +2,13 @@
<ds-form v-model="form" @submit="handleSubmit"> <ds-form v-model="form" @submit="handleSubmit">
<template slot-scope="{ errors }"> <template slot-scope="{ errors }">
<ds-card> <ds-card>
<hc-editor ref="editor" :users="users" :value="form.content" @input="updateEditorContent" /> <hc-editor
ref="editor"
:users="users"
:hashtags="null"
:value="form.content"
@input="updateEditorContent"
/>
<ds-space /> <ds-space />
<ds-flex :gutter="{ base: 'small', md: 'small', sm: 'x-large', xs: 'x-large' }"> <ds-flex :gutter="{ base: 'small', md: 'small', sm: 'x-large', xs: 'x-large' }">
<ds-flex-item :width="{ base: '0%', md: '50%', sm: '0%', xs: '0%' }" /> <ds-flex-item :width="{ base: '0%', md: '50%', sm: '0%', xs: '0%' }" />

View File

@ -49,8 +49,8 @@
"email-exists": "Es gibt schon ein Benutzerkonto mit dieser E-Mail Adresse!", "email-exists": "Es gibt schon ein Benutzerkonto mit dieser E-Mail Adresse!",
"invalid-invitation-token": "Es sieht so aus, als ob der Einladungscode schon eingelöst wurde. Jeder Code kann nur einmalig benutzt werden." "invalid-invitation-token": "Es sieht so aus, als ob der Einladungscode schon eingelöst wurde. Jeder Code kann nur einmalig benutzt werden."
}, },
"submit": "Konto erstellen", "submit": "Konto erstellen",
"success": "Eine Mail mit einem Bestätigungslink für die Registrierung wurde an <b>{email}</b> geschickt" "success": "Eine Mail mit einem Bestätigungslink für die Registrierung wurde an <b>{email}</b> geschickt"
} }
}, },
"create-user-account": { "create-user-account": {
@ -229,7 +229,7 @@
}, },
"comment": { "comment": {
"submit": "Kommentiere", "submit": "Kommentiere",
"submitted": "Kommentar Gesendet" "submitted": "Kommentar gesendet"
} }
}, },
"comment": { "comment": {

View File

@ -67,7 +67,7 @@ import HcTag from '~/components/Tag'
import ContentMenu from '~/components/ContentMenu' import ContentMenu from '~/components/ContentMenu'
import HcUser from '~/components/User' import HcUser from '~/components/User'
import HcShoutButton from '~/components/ShoutButton.vue' import HcShoutButton from '~/components/ShoutButton.vue'
import HcCommentForm from '~/components/comments/CommentForm' import HcCommentForm from '~/components/comments/CommentForm/CommentForm'
import HcCommentList from '~/components/comments/CommentList' import HcCommentList from '~/components/comments/CommentList'
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers' import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
import PostQuery from '~/graphql/PostQuery.js' import PostQuery from '~/graphql/PostQuery.js'