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) { query($id: ID) {
Post(id: $id) { Post(id: $id) {
tags { tags {
id
name 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 = [ const expected = [
{ {
id: 'Democracy',
name: 'Democracy', name: 'Democracy',
}, },
{ {
id: 'Liberty',
name: 'Liberty', name: 'Liberty',
}, },
] ]
@ -259,9 +262,11 @@ describe('Hashtags', () => {
const expected = [ const expected = [
{ {
id: 'Elections',
name: 'Elections', name: 'Elections',
}, },
{ {
id: 'Liberty',
name: 'Liberty', name: 'Liberty',
}, },
] ]

View File

@ -1,5 +1,11 @@
import cheerio from 'cheerio' 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) { export default function(content) {
if (!content) return [] if (!content) return []

View File

@ -21,16 +21,16 @@ describe('extractHashtags', () => {
}) })
describe('handles links', () => { describe('handles links', () => {
it('with domains', () => { it('ignores links with domains', () => {
const content = 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>' '<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(['Elections', 'Democracy']) expect(extractHashtags(content)).toEqual(['Democracy'])
}) })
it('special characters', () => { it('ignores Hashtag links with not allowed character combinations', () => {
const content = 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>' '<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(['u!*(),2', 'u.~-3']) expect(extractHashtags(content)).toEqual(['0123456789a', 'AbcDefXyz0123456789'])
}) })
}) })
@ -41,6 +41,12 @@ describe('extractHashtags', () => {
expect(extractHashtags(content)).toEqual([]) 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', () => { it('`href` is empty or invalid', () => {
const content = 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>' '<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', role: 'user',
email: 'user@example.org', email: 'user@example.org',
}), }),
f.create('User', { id: 'u4', name: 'Tick', role: 'user', email: 'tick@example.org' }), f.create('User', {
f.create('User', { id: 'u5', name: 'Trick', role: 'user', email: 'trick@example.org' }), id: 'u4',
f.create('User', { id: 'u6', name: 'Track', role: 'user', email: 'track@example.org' }), name: 'Tick',
f.create('User', { id: 'u7', name: 'Dagobert', role: 'user', email: 'dagobert@example.org' }), 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([ const [asAdmin, asModerator, asUser, asTick, asTrick, asTrack] = await Promise.all([
Factory().authenticateAs({ email: 'admin@example.org', password: '1234' }), Factory().authenticateAs({
Factory().authenticateAs({ email: 'moderator@example.org', password: '1234' }), email: 'admin@example.org',
Factory().authenticateAs({ email: 'user@example.org', password: '1234' }), password: '1234',
Factory().authenticateAs({ email: 'tick@example.org', password: '1234' }), }),
Factory().authenticateAs({ email: 'trick@example.org', password: '1234' }), Factory().authenticateAs({
Factory().authenticateAs({ email: 'track@example.org', password: '1234' }), 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([ await Promise.all([
f.relate('User', 'Badges', { from: 'b6', to: 'u1' }), f.relate('User', 'Badges', {
f.relate('User', 'Badges', { from: 'b5', to: 'u2' }), from: 'b6',
f.relate('User', 'Badges', { from: 'b4', to: 'u3' }), to: 'u1',
f.relate('User', 'Badges', { from: 'b3', to: 'u4' }), }),
f.relate('User', 'Badges', { from: 'b2', to: 'u5' }), f.relate('User', 'Badges', {
f.relate('User', 'Badges', { from: 'b1', to: 'u6' }), from: 'b5',
f.relate('User', 'Friends', { from: 'u1', to: 'u2' }), to: 'u2',
f.relate('User', 'Friends', { from: 'u1', to: 'u3' }), }),
f.relate('User', 'Friends', { from: 'u2', to: 'u3' }), f.relate('User', 'Badges', {
f.relate('User', 'Blacklisted', { from: 'u7', to: 'u4' }), from: 'b4',
f.relate('User', 'Blacklisted', { from: 'u7', to: 'u5' }), to: 'u3',
f.relate('User', 'Blacklisted', { from: 'u7', to: 'u6' }), }),
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([ await Promise.all([
asAdmin.follow({ id: 'u3', type: 'User' }), asAdmin.follow({
asModerator.follow({ id: 'u4', type: 'User' }), id: 'u3',
asUser.follow({ id: 'u4', type: 'User' }), type: 'User',
asTick.follow({ id: 'u6', type: 'User' }), }),
asTrick.follow({ id: 'u4', type: 'User' }), asModerator.follow({
asTrack.follow({ id: 'u3', type: 'User' }), 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([ 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', { f.create('Category', {
id: 'cat2', id: 'cat2',
name: 'Happyness & Values', name: 'Happyness & Values',
@ -203,10 +300,22 @@ import Factory from './factories'
]) ])
await Promise.all([ await Promise.all([
f.create('Tag', { id: 't1', name: 'Umwelt' }), f.create('Tag', {
f.create('Tag', { id: 't2', name: 'Naturschutz' }), id: 'Umwelt',
f.create('Tag', { id: 't3', name: 'Demokratie' }), name: 'Umwelt',
f.create('Tag', { id: 't4', name: 'Freiheit' }), }),
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?' 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!' 'Hey <a class="mention" href="/profile/u3">@jenny-rostock</a>, here is another notification for you!'
await Promise.all([ await Promise.all([
asAdmin.create('Post', { id: 'p0', image: faker.image.unsplash.food() }), asAdmin.create('Post', {
asModerator.create('Post', { id: 'p1', image: faker.image.unsplash.technology() }), id: 'p0',
asUser.create('Post', { id: 'p2' }), image: faker.image.unsplash.food(),
asTick.create('Post', { id: 'p3' }), }),
asTrick.create('Post', { id: 'p4' }), asModerator.create('Post', {
asTrack.create('Post', { id: 'p5' }), id: 'p1',
asAdmin.create('Post', { id: 'p6', image: faker.image.unsplash.buildings() }), image: faker.image.unsplash.technology(),
asModerator.create('Post', { id: 'p7', content: `${mention1} ${faker.lorem.paragraph()}` }), }),
asUser.create('Post', { id: 'p8', image: faker.image.unsplash.nature() }), asUser.create('Post', {
asTick.create('Post', { id: 'p9' }), id: 'p2',
asTrick.create('Post', { id: 'p10' }), }),
asTrack.create('Post', { id: 'p11', image: faker.image.unsplash.people() }), asTick.create('Post', {
asAdmin.create('Post', { id: 'p12', content: `${mention2} ${faker.lorem.paragraph()}` }), id: 'p3',
asModerator.create('Post', { id: 'p13' }), }),
asUser.create('Post', { id: 'p14', image: faker.image.unsplash.objects() }), asTrick.create('Post', {
asTick.create('Post', { id: 'p15' }), 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([ await Promise.all([
f.relate('Post', 'Categories', { from: 'p0', to: 'cat16' }), f.relate('Post', 'Categories', {
f.relate('Post', 'Categories', { from: 'p1', to: 'cat1' }), from: 'p0',
f.relate('Post', 'Categories', { from: 'p2', to: 'cat2' }), to: 'cat16',
f.relate('Post', 'Categories', { from: 'p3', to: 'cat3' }), }),
f.relate('Post', 'Categories', { from: 'p4', to: 'cat4' }), f.relate('Post', 'Categories', {
f.relate('Post', 'Categories', { from: 'p5', to: 'cat5' }), from: 'p1',
f.relate('Post', 'Categories', { from: 'p6', to: 'cat6' }), to: 'cat1',
f.relate('Post', 'Categories', { from: 'p7', to: 'cat7' }), }),
f.relate('Post', 'Categories', { from: 'p8', to: 'cat8' }), f.relate('Post', 'Categories', {
f.relate('Post', 'Categories', { from: 'p9', to: 'cat9' }), from: 'p2',
f.relate('Post', 'Categories', { from: 'p10', to: 'cat10' }), to: 'cat2',
f.relate('Post', 'Categories', { from: 'p11', to: 'cat11' }), }),
f.relate('Post', 'Categories', { from: 'p12', to: 'cat12' }), f.relate('Post', 'Categories', {
f.relate('Post', 'Categories', { from: 'p13', to: 'cat13' }), from: 'p3',
f.relate('Post', 'Categories', { from: 'p14', to: 'cat14' }), to: 'cat3',
f.relate('Post', 'Categories', { from: 'p15', to: 'cat15' }), }),
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', {
f.relate('Post', 'Tags', { from: 'p1', to: 't1' }), from: 'p0',
f.relate('Post', 'Tags', { from: 'p2', to: 't2' }), to: 'Freiheit',
f.relate('Post', 'Tags', { from: 'p3', to: 't3' }), }),
f.relate('Post', 'Tags', { from: 'p4', to: 't4' }), f.relate('Post', 'Tags', {
f.relate('Post', 'Tags', { from: 'p5', to: 't1' }), from: 'p1',
f.relate('Post', 'Tags', { from: 'p6', to: 't2' }), to: 'Umwelt',
f.relate('Post', 'Tags', { from: 'p7', to: 't3' }), }),
f.relate('Post', 'Tags', { from: 'p8', to: 't4' }), f.relate('Post', 'Tags', {
f.relate('Post', 'Tags', { from: 'p9', to: 't1' }), from: 'p2',
f.relate('Post', 'Tags', { from: 'p10', to: 't2' }), to: 'Naturschutz',
f.relate('Post', 'Tags', { from: 'p11', to: 't3' }), }),
f.relate('Post', 'Tags', { from: 'p12', to: 't4' }), f.relate('Post', 'Tags', {
f.relate('Post', 'Tags', { from: 'p13', to: 't1' }), from: 'p3',
f.relate('Post', 'Tags', { from: 'p14', to: 't2' }), to: 'Demokratie',
f.relate('Post', 'Tags', { from: 'p15', to: 't3' }), }),
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([ await Promise.all([
asAdmin.shout({ id: 'p2', type: 'Post' }), asAdmin.shout({
asAdmin.shout({ id: 'p6', type: 'Post' }), id: 'p2',
asModerator.shout({ id: 'p0', type: 'Post' }), type: 'Post',
asModerator.shout({ id: 'p6', type: 'Post' }), }),
asUser.shout({ id: 'p6', type: 'Post' }), asAdmin.shout({
asUser.shout({ id: 'p7', type: 'Post' }), id: 'p6',
asTick.shout({ id: 'p8', type: 'Post' }), type: 'Post',
asTick.shout({ id: 'p9', type: 'Post' }), }),
asTrack.shout({ id: 'p10', 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([ await Promise.all([
asAdmin.shout({ id: 'p2', type: 'Post' }), asAdmin.shout({
asAdmin.shout({ id: 'p6', type: 'Post' }), id: 'p2',
asModerator.shout({ id: 'p0', type: 'Post' }), type: 'Post',
asModerator.shout({ id: 'p6', type: 'Post' }), }),
asUser.shout({ id: 'p6', type: 'Post' }), asAdmin.shout({
asUser.shout({ id: 'p7', type: 'Post' }), id: 'p6',
asTick.shout({ id: 'p8', type: 'Post' }), type: 'Post',
asTick.shout({ id: 'p9', type: 'Post' }), }),
asTrack.shout({ id: 'p10', 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([ await Promise.all([
asUser.create('Comment', { id: 'c1', postId: 'p1' }), asUser.create('Comment', {
asTick.create('Comment', { id: 'c2', postId: 'p1' }), id: 'c1',
asTrack.create('Comment', { id: 'c3', postId: 'p3' }), postId: 'p1',
asTrick.create('Comment', { id: 'c4', postId: 'p2' }), }),
asModerator.create('Comment', { id: 'c5', postId: 'p3' }), asTick.create('Comment', {
asAdmin.create('Comment', { id: 'c6', postId: 'p4' }), id: 'c2',
asUser.create('Comment', { id: 'c7', postId: 'p2' }), postId: 'p1',
asTick.create('Comment', { id: 'c8', postId: 'p15' }), }),
asTrick.create('Comment', { id: 'c9', postId: 'p15' }), asTrack.create('Comment', {
asTrack.create('Comment', { id: 'c10', postId: 'p15' }), id: 'c3',
asUser.create('Comment', { id: 'c11', postId: 'p15' }), postId: 'p3',
asUser.create('Comment', { id: 'c12', postId: 'p15' }), }),
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) }' const disableMutation = 'mutation($id: ID!) { disable(id: $id) }'
await Promise.all([ await Promise.all([
asModerator.mutate(disableMutation, { id: 'p11' }), asModerator.mutate(disableMutation, {
asModerator.mutate(disableMutation, { id: 'c5' }), id: 'p11',
}),
asModerator.mutate(disableMutation, {
id: 'c5',
}),
]) ])
await Promise.all([ await Promise.all([
asTick.create('Report', { description: "I don't like this comment", id: 'c1' }), asTick.create('Report', {
asTrick.create('Report', { description: "I don't like this post", id: 'p1' }), description: "I don't like this comment",
asTrack.create('Report', { description: "I don't like this user", id: 'u1' }), 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([ await Promise.all([
@ -342,10 +690,22 @@ import Factory from './factories'
]) ])
await Promise.all([ await Promise.all([
f.relate('Organization', 'CreatedBy', { from: 'u1', to: 'o1' }), f.relate('Organization', 'CreatedBy', {
f.relate('Organization', 'CreatedBy', { from: 'u1', to: 'o2' }), from: 'u1',
f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o2' }), to: 'o1',
f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o3' }), }),
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 */ /* eslint-disable-next-line no-console */
console.log('Seeded Data...') console.log('Seeded Data...')

View File

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

View File

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

View File

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

View File

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