Hashtags restricted in the chars, in Editor Suggestions and Backend

Refactor Seeding with Tag ids as Tag names.
In Frontend: Done as a suggestion with all the forbidden chars removed.
In Backend by Regex only the fitting links.
Rewrite backend test to make sure Hashtag „id“ is the name of the Hashtag.
Test for „https://www.example.org/#anchor“

Co-Authored-By: mattwr18 <mattwr18@gmail.com>
This commit is contained in:
Wolfgang Huß 2019-07-08 19:25:57 +02:00
parent 8d648deda4
commit e69f292bd8
8 changed files with 553 additions and 147 deletions

View File

@ -177,6 +177,7 @@ describe('Hashtags', () => {
query($id: ID) {
Post(id: $id) {
tags {
id
name
}
}
@ -216,12 +217,14 @@ describe('Hashtags', () => {
})
})
it('both Hashtags are created', async () => {
it('both Hashtags are created with the "id" set to thier "name"', async () => {
const expected = [
{
id: 'Democracy',
name: 'Democracy',
},
{
id: 'Liberty',
name: 'Liberty',
},
]
@ -259,9 +262,11 @@ describe('Hashtags', () => {
const expected = [
{
id: 'Elections',
name: 'Elections',
},
{
id: 'Liberty',
name: 'Liberty',
},
]

View File

@ -1,5 +1,11 @@
import cheerio from 'cheerio'
const ID_REGEX = /\/search\/hashtag\/([\w\-.!~*'"(),]+)/g
// formats of a Hashtag:
// https://en.wikipedia.org/w/index.php?title=Hashtag&oldid=905141980#Style
// here:
// 0. Search for whole string.
// 1. Hashtag has only 'a-z', 'A-Z', and '0-9'.
// 2. If it starts with a digit '0-9' than 'a-z', or 'A-Z' has to follow.
const ID_REGEX = /^\/search\/hashtag\/(([a-zA-Z]+[a-zA-Z0-9]*)|([0-9]+[a-zA-Z]+[a-zA-Z0-9]*))$/g
export default function(content) {
if (!content) return []

View File

@ -21,16 +21,16 @@ describe('extractHashtags', () => {
})
describe('handles links', () => {
it('with domains', () => {
it('ignores links with domains', () => {
const content =
'<p><a class="hashtag" href="http://localhost:3000/search/hashtag/Elections">#Elections</a><a href="http://localhost:3000/search/hashtag/Democracy">#Democracy</a></p>'
expect(extractHashtags(content)).toEqual(['Elections', 'Democracy'])
'<p><a class="hashtag" href="http://localhost:3000/search/hashtag/Elections">#Elections</a><a href="/search/hashtag/Democracy">#Democracy</a></p>'
expect(extractHashtags(content)).toEqual(['Democracy'])
})
it('special characters', () => {
it('ignores Hashtag links with not allowed character combinations', () => {
const content =
'<p>Something inspirational about <a href="/search/hashtag/u!*(),2" class="hashtag" target="_blank">#u!*(),2</a> and <a href="/search/hashtag/u.~-3" target="_blank">#u.~-3</a>.</p>'
expect(extractHashtags(content)).toEqual(['u!*(),2', 'u.~-3'])
'<p>Something inspirational about <a href="/search/hashtag/AbcDefXyz0123456789!*(),2" class="hashtag" target="_blank">#AbcDefXyz0123456789!*(),2</a>, <a href="/search/hashtag/0123456789" class="hashtag" target="_blank">#0123456789</a>, <a href="/search/hashtag/0123456789a" class="hashtag" target="_blank">#0123456789a</a> and <a href="/search/hashtag/AbcDefXyz0123456789" target="_blank">#AbcDefXyz0123456789</a>.</p>'
expect(extractHashtags(content)).toEqual(['0123456789a', 'AbcDefXyz0123456789'])
})
})
@ -41,6 +41,12 @@ describe('extractHashtags', () => {
expect(extractHashtags(content)).toEqual([])
})
it('`href` contains Hashtag as page anchor', () => {
const content =
'<p>Something inspirational about <a href="https://www.example.org/#anchor" target="_blank">#anchor</a>.</p>'
expect(extractHashtags(content)).toEqual([])
})
it('`href` is empty or invalid', () => {
const content =
'<p>Something inspirational about <a href="" class="hashtag" target="_blank">@bob-der-baumeister</a> and <a href="not-a-url" target="_blank">@jenny-rostock</a>.</p>'

View File

@ -69,47 +69,144 @@ import Factory from './factories'
role: 'user',
email: 'user@example.org',
}),
f.create('User', { id: 'u4', name: 'Tick', role: 'user', email: 'tick@example.org' }),
f.create('User', { id: 'u5', name: 'Trick', role: 'user', email: 'trick@example.org' }),
f.create('User', { id: 'u6', name: 'Track', role: 'user', email: 'track@example.org' }),
f.create('User', { id: 'u7', name: 'Dagobert', role: 'user', email: 'dagobert@example.org' }),
f.create('User', {
id: 'u4',
name: 'Tick',
role: 'user',
email: 'tick@example.org',
}),
f.create('User', {
id: 'u5',
name: 'Trick',
role: 'user',
email: 'trick@example.org',
}),
f.create('User', {
id: 'u6',
name: 'Track',
role: 'user',
email: 'track@example.org',
}),
f.create('User', {
id: 'u7',
name: 'Dagobert',
role: 'user',
email: 'dagobert@example.org',
}),
])
const [asAdmin, asModerator, asUser, asTick, asTrick, asTrack] = await Promise.all([
Factory().authenticateAs({ email: 'admin@example.org', password: '1234' }),
Factory().authenticateAs({ email: 'moderator@example.org', password: '1234' }),
Factory().authenticateAs({ email: 'user@example.org', password: '1234' }),
Factory().authenticateAs({ email: 'tick@example.org', password: '1234' }),
Factory().authenticateAs({ email: 'trick@example.org', password: '1234' }),
Factory().authenticateAs({ email: 'track@example.org', password: '1234' }),
Factory().authenticateAs({
email: 'admin@example.org',
password: '1234',
}),
Factory().authenticateAs({
email: 'moderator@example.org',
password: '1234',
}),
Factory().authenticateAs({
email: 'user@example.org',
password: '1234',
}),
Factory().authenticateAs({
email: 'tick@example.org',
password: '1234',
}),
Factory().authenticateAs({
email: 'trick@example.org',
password: '1234',
}),
Factory().authenticateAs({
email: 'track@example.org',
password: '1234',
}),
])
await Promise.all([
f.relate('User', 'Badges', { from: 'b6', to: 'u1' }),
f.relate('User', 'Badges', { from: 'b5', to: 'u2' }),
f.relate('User', 'Badges', { from: 'b4', to: 'u3' }),
f.relate('User', 'Badges', { from: 'b3', to: 'u4' }),
f.relate('User', 'Badges', { from: 'b2', to: 'u5' }),
f.relate('User', 'Badges', { from: 'b1', to: 'u6' }),
f.relate('User', 'Friends', { from: 'u1', to: 'u2' }),
f.relate('User', 'Friends', { from: 'u1', to: 'u3' }),
f.relate('User', 'Friends', { from: 'u2', to: 'u3' }),
f.relate('User', 'Blacklisted', { from: 'u7', to: 'u4' }),
f.relate('User', 'Blacklisted', { from: 'u7', to: 'u5' }),
f.relate('User', 'Blacklisted', { from: 'u7', to: 'u6' }),
f.relate('User', 'Badges', {
from: 'b6',
to: 'u1',
}),
f.relate('User', 'Badges', {
from: 'b5',
to: 'u2',
}),
f.relate('User', 'Badges', {
from: 'b4',
to: 'u3',
}),
f.relate('User', 'Badges', {
from: 'b3',
to: 'u4',
}),
f.relate('User', 'Badges', {
from: 'b2',
to: 'u5',
}),
f.relate('User', 'Badges', {
from: 'b1',
to: 'u6',
}),
f.relate('User', 'Friends', {
from: 'u1',
to: 'u2',
}),
f.relate('User', 'Friends', {
from: 'u1',
to: 'u3',
}),
f.relate('User', 'Friends', {
from: 'u2',
to: 'u3',
}),
f.relate('User', 'Blacklisted', {
from: 'u7',
to: 'u4',
}),
f.relate('User', 'Blacklisted', {
from: 'u7',
to: 'u5',
}),
f.relate('User', 'Blacklisted', {
from: 'u7',
to: 'u6',
}),
])
await Promise.all([
asAdmin.follow({ id: 'u3', type: 'User' }),
asModerator.follow({ id: 'u4', type: 'User' }),
asUser.follow({ id: 'u4', type: 'User' }),
asTick.follow({ id: 'u6', type: 'User' }),
asTrick.follow({ id: 'u4', type: 'User' }),
asTrack.follow({ id: 'u3', type: 'User' }),
asAdmin.follow({
id: 'u3',
type: 'User',
}),
asModerator.follow({
id: 'u4',
type: 'User',
}),
asUser.follow({
id: 'u4',
type: 'User',
}),
asTick.follow({
id: 'u6',
type: 'User',
}),
asTrick.follow({
id: 'u4',
type: 'User',
}),
asTrack.follow({
id: 'u3',
type: 'User',
}),
])
await Promise.all([
f.create('Category', { id: 'cat1', name: 'Just For Fun', slug: 'justforfun', icon: 'smile' }),
f.create('Category', {
id: 'cat1',
name: 'Just For Fun',
slug: 'justforfun',
icon: 'smile',
}),
f.create('Category', {
id: 'cat2',
name: 'Happyness & Values',
@ -203,10 +300,22 @@ import Factory from './factories'
])
await Promise.all([
f.create('Tag', { id: 't1', name: 'Umwelt' }),
f.create('Tag', { id: 't2', name: 'Naturschutz' }),
f.create('Tag', { id: 't3', name: 'Demokratie' }),
f.create('Tag', { id: 't4', name: 'Freiheit' }),
f.create('Tag', {
id: 'Umwelt',
name: 'Umwelt',
}),
f.create('Tag', {
id: 'Naturschutz',
name: 'Naturschutz',
}),
f.create('Tag', {
id: 'Demokratie',
name: 'Demokratie',
}),
f.create('Tag', {
id: 'Freiheit',
name: 'Freiheit',
}),
])
const mention1 = 'Hey <a class="mention" href="/profile/u3">@jenny-rostock</a>, what\'s up?'
@ -214,108 +323,347 @@ import Factory from './factories'
'Hey <a class="mention" href="/profile/u3">@jenny-rostock</a>, here is another notification for you!'
await Promise.all([
asAdmin.create('Post', { id: 'p0', image: faker.image.unsplash.food() }),
asModerator.create('Post', { id: 'p1', image: faker.image.unsplash.technology() }),
asUser.create('Post', { id: 'p2' }),
asTick.create('Post', { id: 'p3' }),
asTrick.create('Post', { id: 'p4' }),
asTrack.create('Post', { id: 'p5' }),
asAdmin.create('Post', { id: 'p6', image: faker.image.unsplash.buildings() }),
asModerator.create('Post', { id: 'p7', content: `${mention1} ${faker.lorem.paragraph()}` }),
asUser.create('Post', { id: 'p8', image: faker.image.unsplash.nature() }),
asTick.create('Post', { id: 'p9' }),
asTrick.create('Post', { id: 'p10' }),
asTrack.create('Post', { id: 'p11', image: faker.image.unsplash.people() }),
asAdmin.create('Post', { id: 'p12', content: `${mention2} ${faker.lorem.paragraph()}` }),
asModerator.create('Post', { id: 'p13' }),
asUser.create('Post', { id: 'p14', image: faker.image.unsplash.objects() }),
asTick.create('Post', { id: 'p15' }),
asAdmin.create('Post', {
id: 'p0',
image: faker.image.unsplash.food(),
}),
asModerator.create('Post', {
id: 'p1',
image: faker.image.unsplash.technology(),
}),
asUser.create('Post', {
id: 'p2',
}),
asTick.create('Post', {
id: 'p3',
}),
asTrick.create('Post', {
id: 'p4',
}),
asTrack.create('Post', {
id: 'p5',
}),
asAdmin.create('Post', {
id: 'p6',
image: faker.image.unsplash.buildings(),
}),
asModerator.create('Post', {
id: 'p7',
content: `${mention1} ${faker.lorem.paragraph()}`,
}),
asUser.create('Post', {
id: 'p8',
image: faker.image.unsplash.nature(),
}),
asTick.create('Post', {
id: 'p9',
}),
asTrick.create('Post', {
id: 'p10',
}),
asTrack.create('Post', {
id: 'p11',
image: faker.image.unsplash.people(),
}),
asAdmin.create('Post', {
id: 'p12',
content: `${mention2} ${faker.lorem.paragraph()}`,
}),
asModerator.create('Post', {
id: 'p13',
}),
asUser.create('Post', {
id: 'p14',
image: faker.image.unsplash.objects(),
}),
asTick.create('Post', {
id: 'p15',
}),
])
await Promise.all([
f.relate('Post', 'Categories', { from: 'p0', to: 'cat16' }),
f.relate('Post', 'Categories', { from: 'p1', to: 'cat1' }),
f.relate('Post', 'Categories', { from: 'p2', to: 'cat2' }),
f.relate('Post', 'Categories', { from: 'p3', to: 'cat3' }),
f.relate('Post', 'Categories', { from: 'p4', to: 'cat4' }),
f.relate('Post', 'Categories', { from: 'p5', to: 'cat5' }),
f.relate('Post', 'Categories', { from: 'p6', to: 'cat6' }),
f.relate('Post', 'Categories', { from: 'p7', to: 'cat7' }),
f.relate('Post', 'Categories', { from: 'p8', to: 'cat8' }),
f.relate('Post', 'Categories', { from: 'p9', to: 'cat9' }),
f.relate('Post', 'Categories', { from: 'p10', to: 'cat10' }),
f.relate('Post', 'Categories', { from: 'p11', to: 'cat11' }),
f.relate('Post', 'Categories', { from: 'p12', to: 'cat12' }),
f.relate('Post', 'Categories', { from: 'p13', to: 'cat13' }),
f.relate('Post', 'Categories', { from: 'p14', to: 'cat14' }),
f.relate('Post', 'Categories', { from: 'p15', to: 'cat15' }),
f.relate('Post', 'Categories', {
from: 'p0',
to: 'cat16',
}),
f.relate('Post', 'Categories', {
from: 'p1',
to: 'cat1',
}),
f.relate('Post', 'Categories', {
from: 'p2',
to: 'cat2',
}),
f.relate('Post', 'Categories', {
from: 'p3',
to: 'cat3',
}),
f.relate('Post', 'Categories', {
from: 'p4',
to: 'cat4',
}),
f.relate('Post', 'Categories', {
from: 'p5',
to: 'cat5',
}),
f.relate('Post', 'Categories', {
from: 'p6',
to: 'cat6',
}),
f.relate('Post', 'Categories', {
from: 'p7',
to: 'cat7',
}),
f.relate('Post', 'Categories', {
from: 'p8',
to: 'cat8',
}),
f.relate('Post', 'Categories', {
from: 'p9',
to: 'cat9',
}),
f.relate('Post', 'Categories', {
from: 'p10',
to: 'cat10',
}),
f.relate('Post', 'Categories', {
from: 'p11',
to: 'cat11',
}),
f.relate('Post', 'Categories', {
from: 'p12',
to: 'cat12',
}),
f.relate('Post', 'Categories', {
from: 'p13',
to: 'cat13',
}),
f.relate('Post', 'Categories', {
from: 'p14',
to: 'cat14',
}),
f.relate('Post', 'Categories', {
from: 'p15',
to: 'cat15',
}),
f.relate('Post', 'Tags', { from: 'p0', to: 't4' }),
f.relate('Post', 'Tags', { from: 'p1', to: 't1' }),
f.relate('Post', 'Tags', { from: 'p2', to: 't2' }),
f.relate('Post', 'Tags', { from: 'p3', to: 't3' }),
f.relate('Post', 'Tags', { from: 'p4', to: 't4' }),
f.relate('Post', 'Tags', { from: 'p5', to: 't1' }),
f.relate('Post', 'Tags', { from: 'p6', to: 't2' }),
f.relate('Post', 'Tags', { from: 'p7', to: 't3' }),
f.relate('Post', 'Tags', { from: 'p8', to: 't4' }),
f.relate('Post', 'Tags', { from: 'p9', to: 't1' }),
f.relate('Post', 'Tags', { from: 'p10', to: 't2' }),
f.relate('Post', 'Tags', { from: 'p11', to: 't3' }),
f.relate('Post', 'Tags', { from: 'p12', to: 't4' }),
f.relate('Post', 'Tags', { from: 'p13', to: 't1' }),
f.relate('Post', 'Tags', { from: 'p14', to: 't2' }),
f.relate('Post', 'Tags', { from: 'p15', to: 't3' }),
f.relate('Post', 'Tags', {
from: 'p0',
to: 'Freiheit',
}),
f.relate('Post', 'Tags', {
from: 'p1',
to: 'Umwelt',
}),
f.relate('Post', 'Tags', {
from: 'p2',
to: 'Naturschutz',
}),
f.relate('Post', 'Tags', {
from: 'p3',
to: 'Demokratie',
}),
f.relate('Post', 'Tags', {
from: 'p4',
to: 'Freiheit',
}),
f.relate('Post', 'Tags', {
from: 'p5',
to: 'Umwelt',
}),
f.relate('Post', 'Tags', {
from: 'p6',
to: 'Naturschutz',
}),
f.relate('Post', 'Tags', {
from: 'p7',
to: 'Demokratie',
}),
f.relate('Post', 'Tags', {
from: 'p8',
to: 'Freiheit',
}),
f.relate('Post', 'Tags', {
from: 'p9',
to: 'Umwelt',
}),
f.relate('Post', 'Tags', {
from: 'p10',
to: 'Naturschutz',
}),
f.relate('Post', 'Tags', {
from: 'p11',
to: 'Demokratie',
}),
f.relate('Post', 'Tags', {
from: 'p12',
to: 'Freiheit',
}),
f.relate('Post', 'Tags', {
from: 'p13',
to: 'Umwelt',
}),
f.relate('Post', 'Tags', {
from: 'p14',
to: 'Naturschutz',
}),
f.relate('Post', 'Tags', {
from: 'p15',
to: 'Demokratie',
}),
])
await Promise.all([
asAdmin.shout({ id: 'p2', type: 'Post' }),
asAdmin.shout({ id: 'p6', type: 'Post' }),
asModerator.shout({ id: 'p0', type: 'Post' }),
asModerator.shout({ id: 'p6', type: 'Post' }),
asUser.shout({ id: 'p6', type: 'Post' }),
asUser.shout({ id: 'p7', type: 'Post' }),
asTick.shout({ id: 'p8', type: 'Post' }),
asTick.shout({ id: 'p9', type: 'Post' }),
asTrack.shout({ id: 'p10', type: 'Post' }),
asAdmin.shout({
id: 'p2',
type: 'Post',
}),
asAdmin.shout({
id: 'p6',
type: 'Post',
}),
asModerator.shout({
id: 'p0',
type: 'Post',
}),
asModerator.shout({
id: 'p6',
type: 'Post',
}),
asUser.shout({
id: 'p6',
type: 'Post',
}),
asUser.shout({
id: 'p7',
type: 'Post',
}),
asTick.shout({
id: 'p8',
type: 'Post',
}),
asTick.shout({
id: 'p9',
type: 'Post',
}),
asTrack.shout({
id: 'p10',
type: 'Post',
}),
])
await Promise.all([
asAdmin.shout({ id: 'p2', type: 'Post' }),
asAdmin.shout({ id: 'p6', type: 'Post' }),
asModerator.shout({ id: 'p0', type: 'Post' }),
asModerator.shout({ id: 'p6', type: 'Post' }),
asUser.shout({ id: 'p6', type: 'Post' }),
asUser.shout({ id: 'p7', type: 'Post' }),
asTick.shout({ id: 'p8', type: 'Post' }),
asTick.shout({ id: 'p9', type: 'Post' }),
asTrack.shout({ id: 'p10', type: 'Post' }),
asAdmin.shout({
id: 'p2',
type: 'Post',
}),
asAdmin.shout({
id: 'p6',
type: 'Post',
}),
asModerator.shout({
id: 'p0',
type: 'Post',
}),
asModerator.shout({
id: 'p6',
type: 'Post',
}),
asUser.shout({
id: 'p6',
type: 'Post',
}),
asUser.shout({
id: 'p7',
type: 'Post',
}),
asTick.shout({
id: 'p8',
type: 'Post',
}),
asTick.shout({
id: 'p9',
type: 'Post',
}),
asTrack.shout({
id: 'p10',
type: 'Post',
}),
])
await Promise.all([
asUser.create('Comment', { id: 'c1', postId: 'p1' }),
asTick.create('Comment', { id: 'c2', postId: 'p1' }),
asTrack.create('Comment', { id: 'c3', postId: 'p3' }),
asTrick.create('Comment', { id: 'c4', postId: 'p2' }),
asModerator.create('Comment', { id: 'c5', postId: 'p3' }),
asAdmin.create('Comment', { id: 'c6', postId: 'p4' }),
asUser.create('Comment', { id: 'c7', postId: 'p2' }),
asTick.create('Comment', { id: 'c8', postId: 'p15' }),
asTrick.create('Comment', { id: 'c9', postId: 'p15' }),
asTrack.create('Comment', { id: 'c10', postId: 'p15' }),
asUser.create('Comment', { id: 'c11', postId: 'p15' }),
asUser.create('Comment', { id: 'c12', postId: 'p15' }),
asUser.create('Comment', {
id: 'c1',
postId: 'p1',
}),
asTick.create('Comment', {
id: 'c2',
postId: 'p1',
}),
asTrack.create('Comment', {
id: 'c3',
postId: 'p3',
}),
asTrick.create('Comment', {
id: 'c4',
postId: 'p2',
}),
asModerator.create('Comment', {
id: 'c5',
postId: 'p3',
}),
asAdmin.create('Comment', {
id: 'c6',
postId: 'p4',
}),
asUser.create('Comment', {
id: 'c7',
postId: 'p2',
}),
asTick.create('Comment', {
id: 'c8',
postId: 'p15',
}),
asTrick.create('Comment', {
id: 'c9',
postId: 'p15',
}),
asTrack.create('Comment', {
id: 'c10',
postId: 'p15',
}),
asUser.create('Comment', {
id: 'c11',
postId: 'p15',
}),
asUser.create('Comment', {
id: 'c12',
postId: 'p15',
}),
])
const disableMutation = 'mutation($id: ID!) { disable(id: $id) }'
await Promise.all([
asModerator.mutate(disableMutation, { id: 'p11' }),
asModerator.mutate(disableMutation, { id: 'c5' }),
asModerator.mutate(disableMutation, {
id: 'p11',
}),
asModerator.mutate(disableMutation, {
id: 'c5',
}),
])
await Promise.all([
asTick.create('Report', { description: "I don't like this comment", id: 'c1' }),
asTrick.create('Report', { description: "I don't like this post", id: 'p1' }),
asTrack.create('Report', { description: "I don't like this user", id: 'u1' }),
asTick.create('Report', {
description: "I don't like this comment",
id: 'c1',
}),
asTrick.create('Report', {
description: "I don't like this post",
id: 'p1',
}),
asTrack.create('Report', {
description: "I don't like this user",
id: 'u1',
}),
])
await Promise.all([
@ -342,10 +690,22 @@ import Factory from './factories'
])
await Promise.all([
f.relate('Organization', 'CreatedBy', { from: 'u1', to: 'o1' }),
f.relate('Organization', 'CreatedBy', { from: 'u1', to: 'o2' }),
f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o2' }),
f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o3' }),
f.relate('Organization', 'CreatedBy', {
from: 'u1',
to: 'o1',
}),
f.relate('Organization', 'CreatedBy', {
from: 'u1',
to: 'o2',
}),
f.relate('Organization', 'OwnedBy', {
from: 'u2',
to: 'o2',
}),
f.relate('Organization', 'OwnedBy', {
from: 'u2',
to: 'o3',
}),
])
/* eslint-disable-next-line no-console */
console.log('Seeded Data...')

View File

@ -1,6 +1,8 @@
<template>
<div class="editor">
<!-- Mention and Hashtag Suggestions Menu -->
<div v-show="showSuggestions" ref="suggestions" class="suggestion-list">
<!-- "filteredItems" array is not empty -->
<template v-if="hasResults">
<div
v-for="(item, index) in filteredItems"
@ -12,18 +14,36 @@
<div v-if="isMention">@{{ item.slug }}</div>
<div v-if="isHashtag">#{{ item.name }}</div>
</div>
<div
v-if="isHashtag && query && !filteredItems.find(el => el.name === query)"
class="suggestion-list__item is-empty"
>
{{ $t('editor.hashtag.addHashtag') }}
<div v-if="isHashtag">
<!-- if query is not empty and is find fully in the suggestions array ... -->
<div v-if="query && !filteredItems.find(el => el.name === query)">
<div class="suggestion-list__item is-empty">{{ $t('editor.hashtag.addHashtag') }}</div>
<div class="suggestion-list__item" @click="selectItem({ name: query })">
#{{ query }}
</div>
</div>
<!-- otherwise if sanitized query is empty advice the user to add a char -->
<div v-else-if="!query">
<div class="suggestion-list__item is-empty">{{ $t('editor.hashtag.addLetter') }}</div>
</div>
</div>
</template>
<div v-else class="suggestion-list__item is-empty">
<div v-if="isMention">{{ $t('editor.mention.noUsersFound') }}</div>
<!-- if "!hasResults" -->
<div v-else>
<div v-if="isMention" class="suggestion-list__item is-empty">
{{ $t('editor.mention.noUsersFound') }}
</div>
<div v-if="isHashtag">
<div v-if="query === ''">{{ $t('editor.hashtag.noHashtagsFound') }}</div>
<div v-else>{{ $t('editor.hashtag.addHashtag') }}</div>
<div v-if="query === ''" class="suggestion-list__item is-empty">
{{ $t('editor.hashtag.noHashtagsFound') }}
</div>
<!-- if "query" is not empty -->
<div v-else>
<div class="suggestion-list__item is-empty">{{ $t('editor.hashtag.addHashtag') }}</div>
<div class="suggestion-list__item" @click="selectItem({ name: query })">
#{{ query }}
</div>
</div>
</div>
</div>
</div>
@ -309,7 +329,7 @@ export default {
onEnter: ({ items, query, range, command, virtualNode }) => {
this.suggestionType = this.hashtagSuggestionType
this.query = query
this.query = this.sanitizedQuery(query)
this.filteredItems = items
this.suggestionRange = range
this.renderPopup(virtualNode)
@ -320,7 +340,7 @@ export default {
},
// is called when a suggestion has changed
onChange: ({ items, query, range, virtualNode }) => {
this.query = query
this.query = this.sanitizedQuery(query)
this.filteredItems = items
this.suggestionRange = range
this.navigatedItemIndex = 0
@ -366,14 +386,15 @@ export default {
// 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
}
const fuse = new Fuse(items, {
threshold: 0.2,
keys: ['name'],
})
return fuse.search(query)
return items.filter(item =>
JSON.stringify(item)
.toLowerCase()
.includes(query.toLowerCase()),
)
},
}),
],
@ -437,6 +458,12 @@ 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
},
// navigate to the previous item
// if it's the first item, navigate to the last one
upHandler() {

View File

@ -5,7 +5,7 @@
<div>
<ds-flex>
<ds-flex-item :width="{ base: '49px', md: '150px' }">
<a v-router-link style="display: inline-flex" href="/">
<a @click="$router.push('/').go('/')">
<ds-logo />
</a>
</ds-flex-item>

View File

@ -43,7 +43,8 @@
},
"hashtag": {
"noHashtagsFound": "Keine Hashtags gefunden",
"addHashtag": "Neuer Hashtag"
"addHashtag": "Neuer Hashtag",
"addLetter": "Tippe einen Buchstaben"
}
},
"profile": {

View File

@ -43,7 +43,8 @@
},
"hashtag": {
"noHashtagsFound": "No hashtags found",
"addHashtag": "New hashtag"
"addHashtag": "New hashtag",
"addLetter": "Type a letter"
}
},
"profile": {