mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' of https://github.com/Human-Connection/Human-Connection into links_to_imprint_and_privacy_policy_changed_to_human-connection.org
This commit is contained in:
commit
c23212b306
@ -4,6 +4,7 @@
|
||||
[](https://codecov.io/gh/Human-Connection/Human-Connection/)
|
||||
[](https://github.com/Human-Connection/Nitro-Backend/blob/backend/LICENSE.md)
|
||||
[](https://discordapp.com/invite/DFSjPaX)
|
||||
[](https://www.codetriage.com/human-connection/human-connection)
|
||||
|
||||
Human Connection is a nonprofit social, action and knowledge network that connects information to action and promotes positive local and global change in all areas of life.
|
||||
|
||||
|
||||
@ -55,12 +55,12 @@
|
||||
"cheerio": "~1.0.0-rc.3",
|
||||
"cors": "~2.8.5",
|
||||
"cross-env": "~5.2.1",
|
||||
"date-fns": "2.1.0",
|
||||
"date-fns": "2.2.1",
|
||||
"debug": "~4.1.1",
|
||||
"dotenv": "~8.1.0",
|
||||
"express": "^4.17.1",
|
||||
"faker": "Marak/faker.js#master",
|
||||
"graphql": "^14.5.4",
|
||||
"graphql": "^14.5.6",
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~3.0.5",
|
||||
@ -73,7 +73,7 @@
|
||||
"lodash": "~4.17.14",
|
||||
"merge-graphql-schemas": "^1.7.0",
|
||||
"metascraper": "^4.10.3",
|
||||
"metascraper-audio": "^5.6.5",
|
||||
"metascraper-audio": "^5.7.4",
|
||||
"metascraper-author": "^5.6.5",
|
||||
"metascraper-clearbit-logo": "^5.3.0",
|
||||
"metascraper-date": "^5.7.4",
|
||||
@ -82,9 +82,9 @@
|
||||
"metascraper-lang": "^5.7.4",
|
||||
"metascraper-lang-detector": "^4.8.5",
|
||||
"metascraper-logo": "^5.7.4",
|
||||
"metascraper-publisher": "^5.6.5",
|
||||
"metascraper-publisher": "^5.7.4",
|
||||
"metascraper-soundcloud": "^5.7.4",
|
||||
"metascraper-title": "^5.7.0",
|
||||
"metascraper-title": "^5.7.4",
|
||||
"metascraper-url": "^5.7.4",
|
||||
"metascraper-video": "^5.7.4",
|
||||
"metascraper-youtube": "^5.7.4",
|
||||
@ -116,7 +116,7 @@
|
||||
"babel-jest": "~24.9.0",
|
||||
"chai": "~4.2.0",
|
||||
"cucumber": "~5.1.0",
|
||||
"eslint": "~6.3.0",
|
||||
"eslint": "~6.4.0",
|
||||
"eslint-config-prettier": "~6.3.0",
|
||||
"eslint-config-standard": "~14.1.0",
|
||||
"eslint-plugin-import": "~2.18.2",
|
||||
|
||||
@ -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])
|
||||
}
|
||||
|
||||
@ -8,9 +8,24 @@ describe('extractHashtags', () => {
|
||||
})
|
||||
|
||||
describe('searches through links', () => {
|
||||
it('finds links with and without ".hashtag" class and extracts Hashtag names', () => {
|
||||
const content =
|
||||
'<p><a class="hashtag" href="/search/hashtag/Elections">#Elections</a><a href="/search/hashtag/Democracy">#Democracy</a></p>'
|
||||
it('without `class="hashtag"` but `data-hashtag-id="something"`, and extracts the Hashtag to make a Hashtag link', () => {
|
||||
const content = `
|
||||
<p>
|
||||
<a
|
||||
class="hashtag"
|
||||
data-hashtag-id="Elections"
|
||||
href="/?hashtag=Elections"
|
||||
>
|
||||
#Elections
|
||||
</a>
|
||||
<a
|
||||
data-hashtag-id="Democracy"
|
||||
href="/?hashtag=Democracy"
|
||||
>
|
||||
#Democracy
|
||||
</a>
|
||||
</p>
|
||||
`
|
||||
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 =
|
||||
'<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('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 =
|
||||
'<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>, <a href="/search/hashtag/AbcDefXyz0123456789" target="_blank">#AbcDefXyz0123456789</a>, and <a href="/search/hashtag/λαπ" target="_blank">#λαπ</a>.</p>'
|
||||
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 = `
|
||||
<p>
|
||||
Something inspirational about
|
||||
<a
|
||||
href="/?hashtag=AbcDefXyz0123456789!*(),2"
|
||||
data-hashtag-id="AbcDefXyz0123456789!*(),2"
|
||||
class="hashtag"
|
||||
target="_blank"
|
||||
>
|
||||
#AbcDefXyz0123456789!*(),2
|
||||
</a>,
|
||||
<a
|
||||
href="/?hashtag=0123456789"
|
||||
data-hashtag-id="0123456789"
|
||||
class="hashtag"
|
||||
target="_blank"
|
||||
>
|
||||
#0123456789
|
||||
</a>,
|
||||
<a href="?hashtag=0123456789a"
|
||||
data-hashtag-id="0123456789a"
|
||||
class="hashtag"
|
||||
target="_blank"
|
||||
>
|
||||
#0123456789a
|
||||
</a>,
|
||||
<a
|
||||
href="/?hashtag=AbcDefXyz0123456789"
|
||||
data-hashtag-id="AbcDefXyz0123456789"
|
||||
class="hashtag"
|
||||
target="_blank"
|
||||
>
|
||||
#AbcDefXyz0123456789
|
||||
</a>, and
|
||||
<a
|
||||
href="/?hashtag=%C4%A7%CF%80%CE%B1%CE%BB"
|
||||
data-hashtag-id="ħπαλ"
|
||||
class="hashtag"
|
||||
target="_blank"
|
||||
>
|
||||
#ħπαλ
|
||||
</a>.
|
||||
</p>
|
||||
`
|
||||
expect(extractHashtags(content).sort()).toEqual([
|
||||
'0123456789a',
|
||||
'AbcDefXyz0123456789',
|
||||
'ħπαλ',
|
||||
])
|
||||
})
|
||||
|
||||
describe('does not crash if', () => {
|
||||
|
||||
@ -69,8 +69,27 @@ afterEach(async () => {
|
||||
describe('hashtags', () => {
|
||||
const id = 'p135'
|
||||
const title = 'Two Hashtags'
|
||||
const postContent =
|
||||
'<p>Hey Dude, <a class="hashtag" href="/search/hashtag/Democracy">#Democracy</a> should work equal for everybody!? That seems to be the only way to have equal <a class="hashtag" href="/search/hashtag/Liberty">#Liberty</a> for everyone.</p>'
|
||||
const postContent = `
|
||||
<p>
|
||||
Hey Dude,
|
||||
<a
|
||||
class="hashtag"
|
||||
data-hashtag-id="Democracy"
|
||||
href="/?hashtag=Democracy">
|
||||
#Democracy
|
||||
</a>
|
||||
should work equal for everybody!? That seems to be the only way to have
|
||||
equal
|
||||
<a
|
||||
class="hashtag"
|
||||
data-hashtag-id="Liberty"
|
||||
href="/?hashtag=Liberty"
|
||||
>
|
||||
#Liberty
|
||||
</a>
|
||||
for everyone.
|
||||
</p>
|
||||
`
|
||||
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 =
|
||||
'<p>Hey Dude, <a class="hashtag" href="/search/hashtag/Elections">#Elections</a> should work equal for everybody!? That seems to be the only way to have equal <a href="/search/hashtag/Liberty">#Liberty</a> for everyone.</p>'
|
||||
describe('updates the Post by removing, keeping and adding one hashtag respectively', () => {
|
||||
// The already existing hashtag has no class at this point.
|
||||
const postContent = `
|
||||
<p>
|
||||
Hey Dude,
|
||||
<a
|
||||
class="hashtag"
|
||||
data-hashtag-id="Elections"
|
||||
href="?hashtag=Elections"
|
||||
>
|
||||
#Elections
|
||||
</a>
|
||||
should work equal for everybody!? That seems to be the only way to
|
||||
have equal
|
||||
<a
|
||||
data-hashtag-id="Liberty"
|
||||
href="?hashtag=Liberty"
|
||||
>
|
||||
#Liberty
|
||||
</a>
|
||||
for everyone.
|
||||
</p>
|
||||
`
|
||||
|
||||
it('only one previous Hashtag and the new Hashtag exists', async () => {
|
||||
await mutate({
|
||||
|
||||
@ -6,14 +6,30 @@ import encryptPassword from '../../helpers/encryptPassword'
|
||||
|
||||
const instance = neode()
|
||||
|
||||
/*
|
||||
* TODO: remove this function as soon type `User` has no `email` property
|
||||
* anymore
|
||||
*/
|
||||
const checkEmailDoesNotExist = async ({ email }) => {
|
||||
const alreadyExistingMail = async (_parent, args, context) => {
|
||||
let { email } = args
|
||||
email = email.toLowerCase()
|
||||
const emails = await instance.all('EmailAddress', { email })
|
||||
if (emails.length > 0) throw new UserInputError('User account with this email already exists.')
|
||||
const cypher = `
|
||||
MATCH (email:EmailAddress {email: $email})
|
||||
OPTIONAL MATCH (email)-[:PRIMARY_EMAIL]-(user)
|
||||
RETURN email, user
|
||||
`
|
||||
let transactionRes
|
||||
const session = context.driver.session()
|
||||
try {
|
||||
transactionRes = await session.run(cypher, { email })
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
const [result] = transactionRes.records.map(record => {
|
||||
return {
|
||||
alreadyExistingEmail: record.get('email').properties,
|
||||
user: record.get('user') && record.get('user').properties,
|
||||
}
|
||||
})
|
||||
const { alreadyExistingEmail, user } = result || {}
|
||||
if (user) throw new UserInputError('User account with this email already exists.')
|
||||
return alreadyExistingEmail
|
||||
}
|
||||
|
||||
export default {
|
||||
@ -37,22 +53,24 @@ export default {
|
||||
}
|
||||
return response
|
||||
},
|
||||
Signup: async (_parent, args, _context, _resolveInfo) => {
|
||||
Signup: async (_parent, args, context) => {
|
||||
const nonce = uuid().substring(0, 6)
|
||||
args.nonce = nonce
|
||||
await checkEmailDoesNotExist({ email: args.email })
|
||||
let emailAddress = await alreadyExistingMail(_parent, args, context)
|
||||
if (emailAddress) return emailAddress
|
||||
try {
|
||||
const emailAddress = await instance.create('EmailAddress', args)
|
||||
emailAddress = await instance.create('EmailAddress', args)
|
||||
return emailAddress.toJson()
|
||||
} catch (e) {
|
||||
throw new UserInputError(e.message)
|
||||
}
|
||||
},
|
||||
SignupByInvitation: async (_parent, args, _context, _resolveInfo) => {
|
||||
SignupByInvitation: async (_parent, args, context) => {
|
||||
const { token } = args
|
||||
const nonce = uuid().substring(0, 6)
|
||||
args.nonce = nonce
|
||||
await checkEmailDoesNotExist({ email: args.email })
|
||||
let emailAddress = await alreadyExistingMail(_parent, args, context)
|
||||
if (emailAddress) return emailAddress
|
||||
try {
|
||||
const result = await instance.cypher(
|
||||
`
|
||||
@ -69,14 +87,14 @@ export default {
|
||||
)
|
||||
if (!validInvitationCode)
|
||||
throw new UserInputError('Invitation code already used or does not exist.')
|
||||
const emailAddress = await instance.create('EmailAddress', args)
|
||||
emailAddress = await instance.create('EmailAddress', args)
|
||||
await validInvitationCode.relateTo(emailAddress, 'activated')
|
||||
return emailAddress.toJson()
|
||||
} catch (e) {
|
||||
throw new UserInputError(e)
|
||||
}
|
||||
},
|
||||
SignupVerification: async (_parent, args, _context, _resolveInfo) => {
|
||||
SignupVerification: async (_parent, args) => {
|
||||
const { termsAndConditionsAgreedVersion } = args
|
||||
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
|
||||
if (!regEx.test(termsAndConditionsAgreedVersion)) {
|
||||
|
||||
@ -310,11 +310,60 @@ describe('Signup', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('creates a Signup with a cryptographic `nonce`', async () => {
|
||||
await mutate({ mutation, variables })
|
||||
let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' })
|
||||
emailAddress = await emailAddress.toJson()
|
||||
expect(emailAddress.nonce).toEqual(expect.any(String))
|
||||
describe('creates a EmailAddress node', () => {
|
||||
it('with `createdAt` attribute', async () => {
|
||||
await mutate({ mutation, variables })
|
||||
let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' })
|
||||
emailAddress = await emailAddress.toJson()
|
||||
expect(emailAddress.createdAt).toBeTruthy()
|
||||
expect(Date.parse(emailAddress.createdAt)).toEqual(expect.any(Number))
|
||||
})
|
||||
|
||||
it('with a cryptographic `nonce`', async () => {
|
||||
await mutate({ mutation, variables })
|
||||
let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' })
|
||||
emailAddress = await emailAddress.toJson()
|
||||
expect(emailAddress.nonce).toEqual(expect.any(String))
|
||||
})
|
||||
|
||||
describe('if the email already exists', () => {
|
||||
let email
|
||||
beforeEach(async () => {
|
||||
email = await factory.create('EmailAddress', {
|
||||
email: 'someuser@example.org',
|
||||
verifiedAt: null,
|
||||
})
|
||||
})
|
||||
|
||||
describe('and the user has registered already', () => {
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', { email })
|
||||
})
|
||||
|
||||
it('throws UserInputError error because of unique constraint violation', async () => {
|
||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||
errors: [{ message: 'User account with this email already exists.' }],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('but the user has not yet registered', () => {
|
||||
it('resolves with the already existing email', async () => {
|
||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||
data: { Signup: { email: 'someuser@example.org' } },
|
||||
})
|
||||
})
|
||||
|
||||
it('creates no additional `EmailAddress` node', async () => {
|
||||
// admin account and the already existing user
|
||||
await expect(neode.all('EmailAddress')).resolves.toHaveLength(2)
|
||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||
data: { Signup: { email: 'someuser@example.org' } },
|
||||
})
|
||||
await expect(neode.all('EmailAddress')).resolves.toHaveLength(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -24,7 +24,15 @@ export default function create() {
|
||||
}
|
||||
args = await encryptPassword(args)
|
||||
const user = await neodeInstance.create('User', args)
|
||||
const email = await factoryInstance.create('EmailAddress', { email: args.email })
|
||||
|
||||
let email
|
||||
if (typeof args.email === 'object') {
|
||||
// probably a neode node
|
||||
email = args.email
|
||||
} else {
|
||||
email = await factoryInstance.create('EmailAddress', { email: args.email })
|
||||
}
|
||||
|
||||
await user.relateTo(email, 'primaryEmail')
|
||||
await email.relateTo(user, 'belongsTo')
|
||||
return user
|
||||
|
||||
@ -413,9 +413,9 @@ import { gql } from '../jest/helpers'
|
||||
const mention2 =
|
||||
'Hey <a class="mention" data-mention-id="u3" href="/profile/u3">@jenny-rostock</a>, here is another notification for you!'
|
||||
const hashtag1 =
|
||||
'See <a class="hashtag" href="/search/hashtag/NaturphilosophieYoga">#NaturphilosophieYoga</a> can really help you!'
|
||||
'See <a class="hashtag" data-hashtag-id="NaturphilosophieYoga" href="/?hashtag=NaturphilosophieYoga">#NaturphilosophieYoga</a>, it can really help you!'
|
||||
const hashtagAndMention1 =
|
||||
'The new physics of <a class="hashtag" href="/search/hashtag/QuantenFlussTheorie">#QuantenFlussTheorie</a> can explain <a class="hashtag" href="/search/hashtag/QuantumGravity">#QuantumGravity</a>! <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> got that already. ;-)'
|
||||
'The new physics of <a class="hashtag" data-hashtag-id="QuantenFlussTheorie" href="/?hashtag=QuantenFlussTheorie">#QuantenFlussTheorie</a> can explain <a class="hashtag" data-hashtag-id="QuantumGravity" href="/?hashtag=QuantumGravity">#QuantumGravity</a>! <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> 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 <a class="mention" data-mention-id="u3" href="/profile/u3">@jenny-rostock</a>, practice it since 3 years now.'
|
||||
'I heard <a class="mention" data-mention-id="u3" href="/profile/u3">@jenny-rostock</a> has practiced it for 3 years now.'
|
||||
const mentionInComment2 =
|
||||
'Did <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> told you?'
|
||||
'Did <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> 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',
|
||||
},
|
||||
}),
|
||||
|
||||
@ -971,7 +971,7 @@
|
||||
url-regex "~4.1.1"
|
||||
video-extensions "~1.1.0"
|
||||
|
||||
"@metascraper/helpers@^5.6.6", "@metascraper/helpers@^5.7.0", "@metascraper/helpers@^5.7.4":
|
||||
"@metascraper/helpers@^5.6.6", "@metascraper/helpers@^5.7.4":
|
||||
version "5.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.7.4.tgz#c91c1b11ce585fa973a544a9d24c5d88d50a9354"
|
||||
integrity sha512-GMLFu8j7e65n04w+dfOVF8RWOqNHCqimITtTHYSa1XdLR8vSqE2PjvSOhGoS5ELU5fRlRQKy9EOrKDeRV3/K0w==
|
||||
@ -2859,10 +2859,10 @@ data-urls@^1.0.0:
|
||||
whatwg-mimetype "^2.2.0"
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
date-fns@2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.1.0.tgz#0d7e806c3cefe14a943532dbf968995ccfd46bd9"
|
||||
integrity sha512-eKeLk3sLCnxB/0PN4t1+zqDtSs4jb4mXRSTZ2okmx/myfWyDqeO4r5nnmA5LClJiCwpuTMeK2v5UQPuE4uMaxA==
|
||||
date-fns@2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.2.1.tgz#b3f79cf56760af106050c686f4c72586a3383ee9"
|
||||
integrity sha512-4V1i5CnTinjBvJpXTq7sDHD4NY6JPcl15112IeSNNLUWQOQ+kIuCvRGOFZMQZNvkadw8F9QTyZxz59rIRU6K+w==
|
||||
|
||||
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
||||
version "2.6.9"
|
||||
@ -3420,10 +3420,10 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
|
||||
integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
|
||||
|
||||
eslint@~6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.3.0.tgz#1f1a902f67bfd4c354e7288b81e40654d927eb6a"
|
||||
integrity sha512-ZvZTKaqDue+N8Y9g0kp6UPZtS4FSY3qARxBs7p4f0H0iof381XHduqVerFWtK8DPtKmemqbqCFENWSQgPR/Gow==
|
||||
eslint@~6.4.0:
|
||||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.4.0.tgz#5aa9227c3fbe921982b2eda94ba0d7fae858611a"
|
||||
integrity sha512-WTVEzK3lSFoXUovDHEbkJqCVPEPwbhCq4trDktNI6ygs7aO41d4cDT0JFAT5MivzZeVLWlg7vHL+bgrQv/t3vA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
ajv "^6.10.0"
|
||||
@ -4200,10 +4200,10 @@ graphql-upload@^8.0.2:
|
||||
http-errors "^1.7.2"
|
||||
object-path "^0.11.4"
|
||||
|
||||
graphql@^14.2.1, graphql@^14.5.4:
|
||||
version "14.5.4"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.4.tgz#b33fe957854e90c10d4c07c7d26b6c8e9f159a13"
|
||||
integrity sha512-dPLvHoxy5m9FrkqWczPPRnH0X80CyvRE6e7Fa5AWEqEAzg9LpxHvKh24po/482E6VWHigOkAmb4xCp6P9yT9gw==
|
||||
graphql@^14.2.1, graphql@^14.5.6:
|
||||
version "14.5.6"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.6.tgz#3fa12173b50e6ccdef953c31c82f37c50ef58bec"
|
||||
integrity sha512-zJ6Oz8P1yptV4O4DYXdArSwvmirPetDOBnGFRBl0zQEC68vNW3Ny8qo8VzMgfr+iC8PKiRYJ+f2wub41oDCoQg==
|
||||
dependencies:
|
||||
iterall "^1.2.2"
|
||||
|
||||
@ -5859,12 +5859,12 @@ merge-stream@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
|
||||
|
||||
metascraper-audio@^5.6.5:
|
||||
version "5.6.6"
|
||||
resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.6.6.tgz#1de5673e18fd9154fb0ead93a184d228514eaa26"
|
||||
integrity sha512-wlpfJkZ2Bgc25Kf+XMFVSh9EwWKyJVUTfJ4nIkQ9XCYoUpEC2uwN2aHfF1TCZehCJKLioU6++zeuXEaq5APk9g==
|
||||
metascraper-audio@^5.7.4:
|
||||
version "5.7.4"
|
||||
resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.7.4.tgz#a476ed484b2642060208243843dc7ef5c9eb7a3e"
|
||||
integrity sha512-5M+C+tirJlR71PXymAkBnEXu8KG0+fkX3G0Dm2UO6jDEchIo+DhW2aGSgB8w9kQN80Ni8jaLjNH3+Mtwbsbbkw==
|
||||
dependencies:
|
||||
"@metascraper/helpers" "^5.6.6"
|
||||
"@metascraper/helpers" "^5.7.4"
|
||||
|
||||
metascraper-author@^5.6.5:
|
||||
version "5.6.6"
|
||||
@ -5925,12 +5925,12 @@ metascraper-logo@^5.7.4:
|
||||
dependencies:
|
||||
"@metascraper/helpers" "^5.7.4"
|
||||
|
||||
metascraper-publisher@^5.6.5:
|
||||
version "5.6.6"
|
||||
resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.6.6.tgz#855515689e7d16870333e44af49ccb954bec45b3"
|
||||
integrity sha512-micUEltrytzwKb1iJjnclJ/l5970xs0PWwoY/RFcIDviBVQ/EQNCW+DieK9P6gFJq3dSLPTJ9cv2QPHkDj+ORw==
|
||||
metascraper-publisher@^5.7.4:
|
||||
version "5.7.4"
|
||||
resolved "https://registry.yarnpkg.com/metascraper-publisher/-/metascraper-publisher-5.7.4.tgz#5f69ad838636ef1fff015c45a66f683fcc2d3d37"
|
||||
integrity sha512-l2FI63BC7cSQCvPD5OMKY4yRYpcxhhrrpsIrC0sIViKs6XrYRphb745NcNgFvKgi/PO0yw0bIEyhAroACaJKOA==
|
||||
dependencies:
|
||||
"@metascraper/helpers" "^5.6.6"
|
||||
"@metascraper/helpers" "^5.7.4"
|
||||
|
||||
metascraper-soundcloud@^5.7.4:
|
||||
version "5.7.4"
|
||||
@ -5941,12 +5941,12 @@ metascraper-soundcloud@^5.7.4:
|
||||
memoize-one "~5.1.1"
|
||||
tldts "~5.4.0"
|
||||
|
||||
metascraper-title@^5.7.0:
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.7.0.tgz#4e37ec7cb914b70b60c9867fc05b14779d2199dd"
|
||||
integrity sha512-+GwhBfw7P+S44DRak6H2oe/ESbYMfYpmFzBny2vR6a3vc8KnRivVkK8vF6PLYuTj2bteEcpvZbbPZ2w/H32CMQ==
|
||||
metascraper-title@^5.7.4:
|
||||
version "5.7.4"
|
||||
resolved "https://registry.yarnpkg.com/metascraper-title/-/metascraper-title-5.7.4.tgz#1f9f05032c2abab2db9a6634b9f6abec5489c116"
|
||||
integrity sha512-qrOw5uRmGVfqiaZxPF6dfXYM79RY13E+nITukPZsktiGMNIknRJmEVI/GSW+6Ek/+GxlW0gByvE+yqFOrgjCTw==
|
||||
dependencies:
|
||||
"@metascraper/helpers" "^5.7.0"
|
||||
"@metascraper/helpers" "^5.7.4"
|
||||
lodash "~4.17.15"
|
||||
|
||||
metascraper-url@^5.7.4:
|
||||
|
||||
@ -44,7 +44,7 @@ export_collection_query "follows" '{"foreignService": "users"}' "users"
|
||||
# export_collection "settings"
|
||||
export_collection "shouts"
|
||||
# export_collection "status"
|
||||
export_collection "users"
|
||||
export_collection_query "users" '{"isVerified": true }' "verified"
|
||||
# export_collection "userscandos"
|
||||
# export_collection "usersettings"
|
||||
|
||||
|
||||
@ -78,7 +78,7 @@ echo "DONE"
|
||||
echo "Start Importing Data"
|
||||
import_collection "badges" "badges/badges.cql"
|
||||
import_collection "categories" "categories/categories.cql"
|
||||
import_collection "users" "users/users.cql"
|
||||
import_collection "users_verified" "users/users.cql"
|
||||
import_collection "follows_users" "follows/follows.cql"
|
||||
#import_collection "follows_organizations" "follows/follows.cql"
|
||||
import_collection "contributions_post" "contributions/contributions.cql"
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
<script>
|
||||
import defaultExtensions from './defaultExtensions.js'
|
||||
import Hashtag from './nodes/Hashtag.js'
|
||||
import { Editor, EditorContent } from 'tiptap'
|
||||
|
||||
export default {
|
||||
@ -21,7 +22,12 @@ export default {
|
||||
doc: this.doc,
|
||||
content: this.content,
|
||||
editable: false,
|
||||
extensions: defaultExtensions(this),
|
||||
extensions: [
|
||||
// Hashtags must come first, see
|
||||
// https://github.com/scrumpy/tiptap/issues/421#issuecomment-523037460
|
||||
new Hashtag(),
|
||||
...defaultExtensions(this),
|
||||
],
|
||||
}),
|
||||
}
|
||||
},
|
||||
|
||||
@ -134,10 +134,12 @@ export default {
|
||||
content: this.value || '',
|
||||
doc: this.doc,
|
||||
extensions: [
|
||||
// Hashtags must come first, see
|
||||
// https://github.com/scrumpy/tiptap/issues/421#issuecomment-523037460
|
||||
...this.optionalExtensions,
|
||||
...defaultExtensions(this),
|
||||
new EventHandler(),
|
||||
new History(),
|
||||
...this.optionalExtensions,
|
||||
],
|
||||
onUpdate: e => {
|
||||
clearTimeout(throttleInputEvent)
|
||||
|
||||
@ -21,11 +21,15 @@ export default class Hashtag extends TipTapMention {
|
||||
return {
|
||||
...super.schema,
|
||||
toDOM: node => {
|
||||
// use a dummy domain because URL cannot handle relative urls
|
||||
const url = new URL('/', 'https://human-connection.org')
|
||||
url.searchParams.append('hashtag', node.attrs.id)
|
||||
|
||||
return [
|
||||
'a',
|
||||
{
|
||||
class: this.options.mentionClass,
|
||||
href: `/search/hashtag/${node.attrs.id}`,
|
||||
href: `/${url.search}`,
|
||||
'data-hashtag-id': node.attrs.id,
|
||||
target: '_blank',
|
||||
},
|
||||
@ -33,8 +37,14 @@ export default class Hashtag extends TipTapMention {
|
||||
]
|
||||
},
|
||||
parseDOM: [
|
||||
// simply don't parse mentions from html
|
||||
// just treat them as normal links
|
||||
{
|
||||
tag: 'a[data-hashtag-id]',
|
||||
getAttrs: dom => {
|
||||
const id = dom.getAttribute('data-hashtag-id')
|
||||
const label = dom.innerText.split(this.options.matcher.char).join('')
|
||||
return { id, label }
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,8 @@ export default class Link extends TipTapLink {
|
||||
inclusive: false,
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'a[href]:not(.embed)', // do not trigger on embed links
|
||||
// if this is an embed link or a hashtag, ignore
|
||||
tag: 'a[href]:not(.embed):not([data-hashtag-id])',
|
||||
getAttrs: dom => ({
|
||||
href: dom.getAttribute('href'),
|
||||
}),
|
||||
|
||||
@ -269,7 +269,7 @@
|
||||
"categoryName": "Name",
|
||||
"postCount": "Beiträge"
|
||||
},
|
||||
"tags": {
|
||||
"hashtags": {
|
||||
"name": "Hashtags",
|
||||
"number": "Nr.",
|
||||
"nameOfHashtag": "Name",
|
||||
|
||||
@ -270,7 +270,7 @@
|
||||
"categoryName": "Name",
|
||||
"postCount": "Posts"
|
||||
},
|
||||
"tags": {
|
||||
"hashtags": {
|
||||
"name": "Hashtags",
|
||||
"number": "No.",
|
||||
"nameOfHashtag": "Name",
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@human-connection/styleguide": "0.5.21",
|
||||
"@nuxtjs/apollo": "^4.0.0-rc12",
|
||||
"@nuxtjs/apollo": "^4.0.0-rc13",
|
||||
"@nuxtjs/axios": "~5.6.0",
|
||||
"@nuxtjs/dotenv": "~1.4.1",
|
||||
"@nuxtjs/sentry": "^3.0.0",
|
||||
|
||||
@ -48,8 +48,8 @@ export default {
|
||||
path: `/admin/categories`,
|
||||
},
|
||||
{
|
||||
name: this.$t('admin.tags.name'),
|
||||
path: `/admin/tags`,
|
||||
name: this.$t('admin.hashtags.name'),
|
||||
path: `/admin/hashtags`,
|
||||
},
|
||||
{
|
||||
name: this.$t('admin.invites.name'),
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<ds-card :header="$t('admin.tags.name')">
|
||||
<ds-card :header="$t('admin.hashtags.name')">
|
||||
<ds-table :data="Tag" :fields="fields" condensed>
|
||||
<template slot="index" slot-scope="scope">
|
||||
{{ scope.index + 1 }}.
|
||||
</template>
|
||||
<template slot="id" slot-scope="scope">
|
||||
<nuxt-link :to="{ path: '/', query: { hashtag: scope.row.id } }">
|
||||
<nuxt-link :to="{ path: '/', query: { hashtag: encodeURI(scope.row.id) } }">
|
||||
<b>#{{ scope.row.id | truncate(20) }}</b>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
@ -25,14 +25,14 @@ export default {
|
||||
computed: {
|
||||
fields() {
|
||||
return {
|
||||
index: this.$t('admin.tags.number'),
|
||||
id: this.$t('admin.tags.name'),
|
||||
index: this.$t('admin.hashtags.number'),
|
||||
id: this.$t('admin.hashtags.name'),
|
||||
taggedCountUnique: {
|
||||
label: this.$t('admin.tags.tagCountUnique'),
|
||||
label: this.$t('admin.hashtags.tagCountUnique'),
|
||||
align: 'right',
|
||||
},
|
||||
taggedCount: {
|
||||
label: this.$t('admin.tags.tagCount'),
|
||||
label: this.$t('admin.hashtags.tagCount'),
|
||||
align: 'right',
|
||||
},
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
mounted() {
|
||||
const { id: hashtag } = this.$route.params
|
||||
this.$router.push({ path: '/', query: { hashtag } })
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1458,14 +1458,14 @@
|
||||
webpack-node-externals "^1.7.2"
|
||||
webpackbar "^4.0.0"
|
||||
|
||||
"@nuxtjs/apollo@^4.0.0-rc12":
|
||||
version "4.0.0-rc12"
|
||||
resolved "https://registry.yarnpkg.com/@nuxtjs/apollo/-/apollo-4.0.0-rc12.tgz#4008c31a76b689b513292b39b9e4a6a04ef12edf"
|
||||
integrity sha512-qHlDsvJoWYJAGt7ucWcFgPpFG89STyby8OoX07+mYCRV52tVaCdrLO95dFZx7k705hSYPzU8gBsMvJte3lEjjQ==
|
||||
"@nuxtjs/apollo@^4.0.0-rc13":
|
||||
version "4.0.0-rc13"
|
||||
resolved "https://registry.yarnpkg.com/@nuxtjs/apollo/-/apollo-4.0.0-rc13.tgz#b18a5db3c9f1e5dac4be4848a469b84ae1833b62"
|
||||
integrity sha512-JoIoexBz0AGkrWl3vkPN7+2hHSY0EJJaI04lfMhc5MQy4PjmYTtpOUFgxLHWSB/HUPeioQwyEdClzPTJrM4yxw==
|
||||
dependencies:
|
||||
cross-fetch "^3.0.4"
|
||||
universal-cookie "^4.0.2"
|
||||
vue-apollo "^3.0.0-rc.2"
|
||||
vue-apollo "^3.0.0-rc.5"
|
||||
vue-cli-plugin-apollo "^0.21.0"
|
||||
webpack-node-externals "^1.7.2"
|
||||
|
||||
@ -15110,10 +15110,10 @@ vscode-uri@^1.0.6:
|
||||
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.8.tgz#9769aaececae4026fb6e22359cb38946580ded59"
|
||||
integrity sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ==
|
||||
|
||||
vue-apollo@^3.0.0-rc.2:
|
||||
version "3.0.0-rc.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.0.0-rc.2.tgz#3b59a93084cb37c19be46b9f18cd63214a782718"
|
||||
integrity sha512-bYgC3sK77rBlLEHi4XFFAm9NTTB10/onAUzUIhioKM8BKnnFOagjykqBtA9xFRVmFvOcgZBNNbgJhzScBeTn4Q==
|
||||
vue-apollo@^3.0.0-rc.5:
|
||||
version "3.0.0-rc.5"
|
||||
resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.0.0-rc.5.tgz#0280c0855318ca89439014c24f3a693a301fa25d"
|
||||
integrity sha512-FuVP93Zq0cglj/S7xRJtmeuGa47BuE7PVEuEbmpGObhEu6sV8TzUMVI6UUTiOHMXYS02NvCAazyy4GAh8Duz8A==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
serialize-javascript "^1.7.0"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user