diff --git a/backend/src/middleware/hashtags/extractHashtags.js b/backend/src/middleware/hashtags/extractHashtags.js index c7782e59d..9a903e4fa 100644 --- a/backend/src/middleware/hashtags/extractHashtags.js +++ b/backend/src/middleware/hashtags/extractHashtags.js @@ -4,23 +4,23 @@ import { exec, build } from 'xregexp/xregexp-all.js' // https://en.wikipedia.org/w/index.php?title=Hashtag&oldid=905141980#Style // here: // 0. Search for whole string. -// 1. Hashtag has only all unicode characters and '0-9'. -// 2. If it starts with a digit '0-9' than a unicode character has to follow. -const regX = build('^/search/hashtag/((\\pL+[\\pL0-9]*)|([0-9]+\\pL+[\\pL0-9]*))$') +// 1. Hashtag has only all unicode letters and '0-9'. +// 2. If it starts with a digit '0-9' than a unicode letter has to follow. +const regX = build('^((\\pL+[\\pL0-9]*)|([0-9]+\\pL+[\\pL0-9]*))$') export default function(content) { if (!content) return [] const $ = cheerio.load(content) // We can not search for class '.hashtag', because the classes are removed at the 'xss' middleware. // But we have to know, which Hashtags are removed from the content as well, so we search for the 'a' html-tag. - const urls = $('a') + const ids = $('a[data-hashtag-id]') .map((_, el) => { - return $(el).attr('href') + return $(el).attr('data-hashtag-id') }) .get() const hashtags = [] - urls.forEach(url => { - const match = exec(url, regX) + ids.forEach(id => { + const match = exec(id, regX) if (match != null) { hashtags.push(match[1]) } diff --git a/backend/src/middleware/hashtags/extractHashtags.spec.js b/backend/src/middleware/hashtags/extractHashtags.spec.js index 2e1761718..739c7de54 100644 --- a/backend/src/middleware/hashtags/extractHashtags.spec.js +++ b/backend/src/middleware/hashtags/extractHashtags.spec.js @@ -8,9 +8,24 @@ describe('extractHashtags', () => { }) describe('searches through links', () => { - it('finds links with and without ".hashtag" class and extracts Hashtag names', () => { - const content = - '

#Elections#Democracy

' + it('without `class="hashtag"` but `data-hashtag-id="something"`, and extracts the Hashtag to make a Hashtag link', () => { + const content = ` +

+ + #Elections + + + #Democracy + +

+ ` expect(extractHashtags(content)).toEqual(['Elections', 'Democracy']) }) @@ -20,23 +35,57 @@ describe('extractHashtags', () => { expect(extractHashtags(content)).toEqual([]) }) - describe('handles links', () => { - it('ignores links with domains', () => { - const content = - '

#Elections#Democracy

' - expect(extractHashtags(content)).toEqual(['Democracy']) - }) - - it('ignores Hashtag links with not allowed character combinations', () => { - // Allowed are all unicode letters '\pL' and all digits '0-9'. There haveto be at least one letter in it. - const content = - '

Something inspirational about #AbcDefXyz0123456789!*(),2, #0123456789, #0123456789a, #AbcDefXyz0123456789, and #λαπ.

' - expect(extractHashtags(content).sort()).toEqual([ - '0123456789a', - 'AbcDefXyz0123456789', - 'λαπ', - ]) - }) + it('ignores hashtag links with unsupported character combinations', () => { + // Allowed are all unicode letters '\pL' and all digits '0-9'. There haveto be at least one letter in it. + const content = ` +

+ Something inspirational about + + #AbcDefXyz0123456789!*(),2 + , + + #0123456789 + , + + #0123456789a + , + + #AbcDefXyz0123456789 + , and + + #ħπαλ + . +

+ ` + expect(extractHashtags(content).sort()).toEqual([ + '0123456789a', + 'AbcDefXyz0123456789', + 'ħπαλ', + ]) }) describe('does not crash if', () => { diff --git a/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js b/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js index 3f101f778..89f1bdd86 100644 --- a/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js +++ b/backend/src/middleware/hashtags/hashtagsMiddleware.spec.js @@ -69,8 +69,27 @@ afterEach(async () => { describe('hashtags', () => { const id = 'p135' const title = 'Two Hashtags' - const postContent = - '

Hey Dude, #Democracy should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone.

' + const postContent = ` +

+ Hey Dude, + + #Democracy + + should work equal for everybody!? That seems to be the only way to have + equal + + #Liberty + + for everyone. +

+ ` const postWithHastagsQuery = gql` query($id: ID) { Post(id: $id) { @@ -129,10 +148,29 @@ describe('hashtags', () => { ) }) - describe('afterwards update the Post by removing a Hashtag, leaving a Hashtag and add a Hashtag', () => { - // The already existing Hashtag has no class at this point. - const postContent = - '

Hey Dude, #Elections should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone.

' + describe('updates the Post by removing, keeping and adding one hashtag respectively', () => { + // The already existing hashtag has no class at this point. + const postContent = ` +

+ Hey Dude, + + #Elections + + should work equal for everybody!? That seems to be the only way to + have equal + + #Liberty + + for everyone. +

+ ` it('only one previous Hashtag and the new Hashtag exists', async () => { await mutate({ diff --git a/backend/src/seed/seed-db.js b/backend/src/seed/seed-db.js index 48a739529..8c87a3214 100644 --- a/backend/src/seed/seed-db.js +++ b/backend/src/seed/seed-db.js @@ -413,9 +413,9 @@ import { gql } from '../jest/helpers' const mention2 = 'Hey @jenny-rostock, here is another notification for you!' const hashtag1 = - 'See #NaturphilosophieYoga can really help you!' + 'See #NaturphilosophieYoga, it can really help you!' const hashtagAndMention1 = - 'The new physics of #QuantenFlussTheorie can explain #QuantumGravity! @peter-lustig got that already. ;-)' + 'The new physics of #QuantenFlussTheorie can explain #QuantumGravity! @peter-lustig got that already. ;-)' const createPostMutation = gql` mutation($id: ID, $title: String!, $content: String!, $categoryIds: [ID]) { CreatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) { @@ -470,9 +470,9 @@ import { gql } from '../jest/helpers' authenticatedUser = await dewey.toJson() const mentionInComment1 = - 'I heard @jenny-rostock, practice it since 3 years now.' + 'I heard @jenny-rostock has practiced it for 3 years now.' const mentionInComment2 = - 'Did @peter-lustig told you?' + 'Did @peter-lustig tell you?' const createCommentMutation = gql` mutation($id: ID, $postId: ID!, $content: String!) { CreateComment(id: $id, postId: $postId, content: $content) { @@ -661,21 +661,21 @@ import { gql } from '../jest/helpers' mutate({ mutation: reportMutation, variables: { - description: "I don't like this comment", + description: 'This comment is bigoted', id: 'c1', }, }), mutate({ mutation: reportMutation, variables: { - description: "I don't like this post", + description: 'This post is bigoted', id: 'p1', }, }), mutate({ mutation: reportMutation, variables: { - description: "I don't like this user", + description: 'This user is harassing me with bigoted remarks', id: 'u1', }, }), diff --git a/webapp/components/Editor/ContentViewer.vue b/webapp/components/Editor/ContentViewer.vue index 993cf32b9..ab07d11d6 100644 --- a/webapp/components/Editor/ContentViewer.vue +++ b/webapp/components/Editor/ContentViewer.vue @@ -4,6 +4,7 @@