mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' of github.com:Human-Connection/Human-Connection into 1707-reporting-with-specific-information
This commit is contained in:
commit
90fdd24697
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -9,4 +9,4 @@
|
||||
],
|
||||
"editor.formatOnSave": false,
|
||||
"eslint.autoFixOnSave": true
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,19 +43,18 @@
|
||||
"dependencies": {
|
||||
"@hapi/joi": "^16.1.4",
|
||||
"@sentry/node": "^5.6.2",
|
||||
"activitystrea.ms": "~2.1.3",
|
||||
"apollo-cache-inmemory": "~1.6.3",
|
||||
"apollo-client": "~2.6.4",
|
||||
"apollo-link-context": "~1.0.19",
|
||||
"apollo-link-http": "~1.5.16",
|
||||
"apollo-server": "~2.9.3",
|
||||
"apollo-server": "~2.9.4",
|
||||
"apollo-server-express": "^2.9.4",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"cheerio": "~1.0.0-rc.3",
|
||||
"cors": "~2.8.5",
|
||||
"cross-env": "~6.0.0",
|
||||
"date-fns": "2.3.0",
|
||||
"date-fns": "2.4.1",
|
||||
"debug": "~4.1.1",
|
||||
"dotenv": "~8.1.0",
|
||||
"express": "^4.17.1",
|
||||
@ -63,8 +62,8 @@
|
||||
"graphql": "^14.5.8",
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~3.0.5",
|
||||
"graphql-middleware-sentry": "^3.2.0",
|
||||
"graphql-middleware": "~4.0.1",
|
||||
"graphql-middleware-sentry": "^3.2.1",
|
||||
"graphql-shield": "~6.1.0",
|
||||
"graphql-tag": "~2.10.1",
|
||||
"helmet": "~3.21.1",
|
||||
@ -73,21 +72,21 @@
|
||||
"lodash": "~4.17.14",
|
||||
"merge-graphql-schemas": "^1.7.0",
|
||||
"metascraper": "^4.10.3",
|
||||
"metascraper-audio": "^5.7.5",
|
||||
"metascraper-author": "^5.7.4",
|
||||
"metascraper-audio": "^5.7.6",
|
||||
"metascraper-author": "^5.7.6",
|
||||
"metascraper-clearbit-logo": "^5.3.0",
|
||||
"metascraper-date": "^5.7.4",
|
||||
"metascraper-date": "^5.7.6",
|
||||
"metascraper-description": "^5.7.5",
|
||||
"metascraper-image": "^5.7.5",
|
||||
"metascraper-lang": "^5.7.4",
|
||||
"metascraper-image": "^5.7.6",
|
||||
"metascraper-lang": "^5.7.6",
|
||||
"metascraper-lang-detector": "^4.8.5",
|
||||
"metascraper-logo": "^5.7.5",
|
||||
"metascraper-publisher": "^5.7.4",
|
||||
"metascraper-soundcloud": "^5.7.4",
|
||||
"metascraper-title": "^5.7.5",
|
||||
"metascraper-url": "^5.7.5",
|
||||
"metascraper-video": "^5.7.5",
|
||||
"metascraper-youtube": "^5.7.5",
|
||||
"metascraper-logo": "^5.7.6",
|
||||
"metascraper-publisher": "^5.7.6",
|
||||
"metascraper-soundcloud": "^5.7.6",
|
||||
"metascraper-title": "^5.7.6",
|
||||
"metascraper-url": "^5.7.6",
|
||||
"metascraper-video": "^5.7.6",
|
||||
"metascraper-youtube": "^5.7.6",
|
||||
"minimatch": "^3.0.4",
|
||||
"mustache": "^3.1.0",
|
||||
"neo4j-driver": "~1.7.6",
|
||||
@ -108,9 +107,9 @@
|
||||
"devDependencies": {
|
||||
"@babel/cli": "~7.6.2",
|
||||
"@babel/core": "~7.6.2",
|
||||
"@babel/node": "~7.6.1",
|
||||
"@babel/node": "~7.6.2",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
|
||||
"@babel/preset-env": "~7.6.0",
|
||||
"@babel/preset-env": "~7.6.2",
|
||||
"@babel/register": "~7.6.2",
|
||||
"apollo-server-testing": "~2.9.4",
|
||||
"babel-core": "~7.0.0-0",
|
||||
@ -118,7 +117,7 @@
|
||||
"babel-jest": "~24.9.0",
|
||||
"chai": "~4.2.0",
|
||||
"cucumber": "~5.1.0",
|
||||
"eslint": "~6.4.0",
|
||||
"eslint": "~6.5.1",
|
||||
"eslint-config-prettier": "~6.3.0",
|
||||
"eslint-config-standard": "~14.1.0",
|
||||
"eslint-plugin-import": "~2.18.2",
|
||||
@ -129,7 +128,7 @@
|
||||
"eslint-plugin-standard": "~4.0.1",
|
||||
"graphql-request": "~1.8.2",
|
||||
"jest": "~24.9.0",
|
||||
"nodemon": "~1.19.2",
|
||||
"nodemon": "~1.19.3",
|
||||
"prettier": "~1.18.2",
|
||||
"supertest": "~4.0.2"
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { extractNameFromId, extractDomainFromUrl, signAndSend } from './utils'
|
||||
import { isPublicAddressed, sendAcceptActivity, sendRejectActivity } from './utils/activity'
|
||||
// import { extractDomainFromUrl, signAndSend } from './utils'
|
||||
import { extractNameFromId, signAndSend } from './utils'
|
||||
import { isPublicAddressed } from './utils/activity'
|
||||
// import { isPublicAddressed, sendAcceptActivity, sendRejectActivity } from './utils/activity'
|
||||
import request from 'request'
|
||||
import as from 'activitystrea.ms'
|
||||
// import as from 'activitystrea.ms'
|
||||
import NitroDataSource from './NitroDataSource'
|
||||
import router from './routes'
|
||||
import Collections from './Collections'
|
||||
@ -33,71 +35,71 @@ export default class ActivityPub {
|
||||
}
|
||||
}
|
||||
|
||||
handleFollowActivity(activity) {
|
||||
debug(`inside FOLLOW ${activity.actor}`)
|
||||
const toActorName = extractNameFromId(activity.object)
|
||||
const fromDomain = extractDomainFromUrl(activity.actor)
|
||||
const dataSource = this.dataSource
|
||||
// handleFollowActivity(activity) {
|
||||
// debug(`inside FOLLOW ${activity.actor}`)
|
||||
// const toActorName = extractNameFromId(activity.object)
|
||||
// const fromDomain = extractDomainFromUrl(activity.actor)
|
||||
// const dataSource = this.dataSource
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request(
|
||||
{
|
||||
url: activity.actor,
|
||||
headers: {
|
||||
Accept: 'application/activity+json',
|
||||
},
|
||||
},
|
||||
async (err, response, toActorObject) => {
|
||||
if (err) return reject(err)
|
||||
// save shared inbox
|
||||
toActorObject = JSON.parse(toActorObject)
|
||||
await this.dataSource.addSharedInboxEndpoint(toActorObject.endpoints.sharedInbox)
|
||||
// return new Promise((resolve, reject) => {
|
||||
// request(
|
||||
// {
|
||||
// url: activity.actor,
|
||||
// headers: {
|
||||
// Accept: 'application/activity+json',
|
||||
// },
|
||||
// },
|
||||
// async (err, response, toActorObject) => {
|
||||
// if (err) return reject(err)
|
||||
// // save shared inbox
|
||||
// toActorObject = JSON.parse(toActorObject)
|
||||
// await this.dataSource.addSharedInboxEndpoint(toActorObject.endpoints.sharedInbox)
|
||||
|
||||
const followersCollectionPage = await this.dataSource.getFollowersCollectionPage(
|
||||
activity.object,
|
||||
)
|
||||
// const followersCollectionPage = await this.dataSource.getFollowersCollectionPage(
|
||||
// activity.object,
|
||||
// )
|
||||
|
||||
const followActivity = as
|
||||
.follow()
|
||||
.id(activity.id)
|
||||
.actor(activity.actor)
|
||||
.object(activity.object)
|
||||
// const followActivity = as
|
||||
// .follow()
|
||||
// .id(activity.id)
|
||||
// .actor(activity.actor)
|
||||
// .object(activity.object)
|
||||
|
||||
// add follower if not already in collection
|
||||
if (followersCollectionPage.orderedItems.includes(activity.actor)) {
|
||||
debug('follower already in collection!')
|
||||
debug(`inbox = ${toActorObject.inbox}`)
|
||||
resolve(
|
||||
sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox),
|
||||
)
|
||||
} else {
|
||||
followersCollectionPage.orderedItems.push(activity.actor)
|
||||
}
|
||||
debug(`toActorObject = ${toActorObject}`)
|
||||
toActorObject =
|
||||
typeof toActorObject !== 'object' ? JSON.parse(toActorObject) : toActorObject
|
||||
debug(`followers = ${JSON.stringify(followersCollectionPage.orderedItems, null, 2)}`)
|
||||
debug(`inbox = ${toActorObject.inbox}`)
|
||||
debug(`outbox = ${toActorObject.outbox}`)
|
||||
debug(`followers = ${toActorObject.followers}`)
|
||||
debug(`following = ${toActorObject.following}`)
|
||||
// // add follower if not already in collection
|
||||
// if (followersCollectionPage.orderedItems.includes(activity.actor)) {
|
||||
// debug('follower already in collection!')
|
||||
// debug(`inbox = ${toActorObject.inbox}`)
|
||||
// resolve(
|
||||
// sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox),
|
||||
// )
|
||||
// } else {
|
||||
// followersCollectionPage.orderedItems.push(activity.actor)
|
||||
// }
|
||||
// debug(`toActorObject = ${toActorObject}`)
|
||||
// toActorObject =
|
||||
// typeof toActorObject !== 'object' ? JSON.parse(toActorObject) : toActorObject
|
||||
// debug(`followers = ${JSON.stringify(followersCollectionPage.orderedItems, null, 2)}`)
|
||||
// debug(`inbox = ${toActorObject.inbox}`)
|
||||
// debug(`outbox = ${toActorObject.outbox}`)
|
||||
// debug(`followers = ${toActorObject.followers}`)
|
||||
// debug(`following = ${toActorObject.following}`)
|
||||
|
||||
try {
|
||||
await dataSource.saveFollowersCollectionPage(followersCollectionPage)
|
||||
debug('follow activity saved')
|
||||
resolve(
|
||||
sendAcceptActivity(followActivity, toActorName, fromDomain, toActorObject.inbox),
|
||||
)
|
||||
} catch (e) {
|
||||
debug('followers update error!', e)
|
||||
resolve(
|
||||
sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
// try {
|
||||
// await dataSource.saveFollowersCollectionPage(followersCollectionPage)
|
||||
// debug('follow activity saved')
|
||||
// resolve(
|
||||
// sendAcceptActivity(followActivity, toActorName, fromDomain, toActorObject.inbox),
|
||||
// )
|
||||
// } catch (e) {
|
||||
// debug('followers update error!', e)
|
||||
// resolve(
|
||||
// sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox),
|
||||
// )
|
||||
// }
|
||||
// },
|
||||
// )
|
||||
// })
|
||||
// }
|
||||
|
||||
handleUndoActivity(activity) {
|
||||
debug('inside UNDO')
|
||||
|
||||
@ -18,9 +18,9 @@ router.post('/', async function(req, res, next) {
|
||||
case 'Undo':
|
||||
await activityPub.handleUndoActivity(req.body).catch(next)
|
||||
break
|
||||
case 'Follow':
|
||||
await activityPub.handleFollowActivity(req.body).catch(next)
|
||||
break
|
||||
// case 'Follow':
|
||||
// await activityPub.handleFollowActivity(req.body).catch(next)
|
||||
// break
|
||||
case 'Delete':
|
||||
await activityPub.handleDeleteActivity(req.body).catch(next)
|
||||
break
|
||||
|
||||
@ -56,9 +56,9 @@ router.post('/:name/inbox', verify, async function(req, res, next) {
|
||||
case 'Undo':
|
||||
await activityPub.handleUndoActivity(req.body).catch(next)
|
||||
break
|
||||
case 'Follow':
|
||||
await activityPub.handleFollowActivity(req.body).catch(next)
|
||||
break
|
||||
// case 'Follow':
|
||||
// await activityPub.handleFollowActivity(req.body).catch(next)
|
||||
// break
|
||||
case 'Delete':
|
||||
await activityPub.handleDeleteActivity(req.body).catch(next)
|
||||
break
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { activityPub } from '../ActivityPub'
|
||||
import { signAndSend, throwErrorIfApolloErrorOccurred } from './index'
|
||||
import { throwErrorIfApolloErrorOccurred } from './index'
|
||||
// import { signAndSend, throwErrorIfApolloErrorOccurred } from './index'
|
||||
|
||||
import crypto from 'crypto'
|
||||
import as from 'activitystrea.ms'
|
||||
// import as from 'activitystrea.ms'
|
||||
import gql from 'graphql-tag'
|
||||
const debug = require('debug')('ea:utils:activity')
|
||||
// const debug = require('debug')('ea:utils:activity')
|
||||
|
||||
export function createNoteObject(text, name, id, published) {
|
||||
const createUuid = crypto.randomBytes(16).toString('hex')
|
||||
@ -62,41 +63,41 @@ export async function getActorId(name) {
|
||||
}
|
||||
}
|
||||
|
||||
export function sendAcceptActivity(theBody, name, targetDomain, url) {
|
||||
as.accept()
|
||||
.id(
|
||||
`${activityPub.endpoint}/activitypub/users/${name}/status/` +
|
||||
crypto.randomBytes(16).toString('hex'),
|
||||
)
|
||||
.actor(`${activityPub.endpoint}/activitypub/users/${name}`)
|
||||
.object(theBody)
|
||||
.prettyWrite((err, doc) => {
|
||||
if (!err) {
|
||||
return signAndSend(doc, name, targetDomain, url)
|
||||
} else {
|
||||
debug(`error serializing Accept object: ${err}`)
|
||||
throw new Error('error serializing Accept object')
|
||||
}
|
||||
})
|
||||
}
|
||||
// export function sendAcceptActivity(theBody, name, targetDomain, url) {
|
||||
// as.accept()
|
||||
// .id(
|
||||
// `${activityPub.endpoint}/activitypub/users/${name}/status/` +
|
||||
// crypto.randomBytes(16).toString('hex'),
|
||||
// )
|
||||
// .actor(`${activityPub.endpoint}/activitypub/users/${name}`)
|
||||
// .object(theBody)
|
||||
// .prettyWrite((err, doc) => {
|
||||
// if (!err) {
|
||||
// return signAndSend(doc, name, targetDomain, url)
|
||||
// } else {
|
||||
// debug(`error serializing Accept object: ${err}`)
|
||||
// throw new Error('error serializing Accept object')
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
export function sendRejectActivity(theBody, name, targetDomain, url) {
|
||||
as.reject()
|
||||
.id(
|
||||
`${activityPub.endpoint}/activitypub/users/${name}/status/` +
|
||||
crypto.randomBytes(16).toString('hex'),
|
||||
)
|
||||
.actor(`${activityPub.endpoint}/activitypub/users/${name}`)
|
||||
.object(theBody)
|
||||
.prettyWrite((err, doc) => {
|
||||
if (!err) {
|
||||
return signAndSend(doc, name, targetDomain, url)
|
||||
} else {
|
||||
debug(`error serializing Accept object: ${err}`)
|
||||
throw new Error('error serializing Accept object')
|
||||
}
|
||||
})
|
||||
}
|
||||
// export function sendRejectActivity(theBody, name, targetDomain, url) {
|
||||
// as.reject()
|
||||
// .id(
|
||||
// `${activityPub.endpoint}/activitypub/users/${name}/status/` +
|
||||
// crypto.randomBytes(16).toString('hex'),
|
||||
// )
|
||||
// .actor(`${activityPub.endpoint}/activitypub/users/${name}`)
|
||||
// .object(theBody)
|
||||
// .prettyWrite((err, doc) => {
|
||||
// if (!err) {
|
||||
// return signAndSend(doc, name, targetDomain, url)
|
||||
// } else {
|
||||
// debug(`error serializing Accept object: ${err}`)
|
||||
// throw new Error('error serializing Accept object')
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
export function isPublicAddressed(postObject) {
|
||||
if (typeof postObject.to === 'string') {
|
||||
|
||||
@ -1,51 +1,51 @@
|
||||
import { generateRsaKeyPair } from '../activitypub/security'
|
||||
import { activityPub } from '../activitypub/ActivityPub'
|
||||
import as from 'activitystrea.ms'
|
||||
// import as from 'activitystrea.ms'
|
||||
|
||||
const debug = require('debug')('backend:schema')
|
||||
// const debug = require('debug')('backend:schema')
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreatePost: async (resolve, root, args, context, info) => {
|
||||
args.activityId = activityPub.generateStatusId(context.user.slug)
|
||||
args.objectId = activityPub.generateStatusId(context.user.slug)
|
||||
// CreatePost: async (resolve, root, args, context, info) => {
|
||||
// args.activityId = activityPub.generateStatusId(context.user.slug)
|
||||
// args.objectId = activityPub.generateStatusId(context.user.slug)
|
||||
|
||||
const post = await resolve(root, args, context, info)
|
||||
// const post = await resolve(root, args, context, info)
|
||||
|
||||
const { user: author } = context
|
||||
const actorId = author.actorId
|
||||
debug(`actorId = ${actorId}`)
|
||||
const createActivity = await new Promise((resolve, reject) => {
|
||||
as.create()
|
||||
.id(`${actorId}/status/${args.activityId}`)
|
||||
.actor(`${actorId}`)
|
||||
.object(
|
||||
as
|
||||
.article()
|
||||
.id(`${actorId}/status/${post.id}`)
|
||||
.content(post.content)
|
||||
.to('https://www.w3.org/ns/activitystreams#Public')
|
||||
.publishedNow()
|
||||
.attributedTo(`${actorId}`),
|
||||
)
|
||||
.prettyWrite((err, doc) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
debug(doc)
|
||||
const parsedDoc = JSON.parse(doc)
|
||||
parsedDoc.send = true
|
||||
resolve(JSON.stringify(parsedDoc))
|
||||
}
|
||||
})
|
||||
})
|
||||
try {
|
||||
await activityPub.sendActivity(createActivity)
|
||||
} catch (e) {
|
||||
debug(`error sending post activity\n${e}`)
|
||||
}
|
||||
return post
|
||||
},
|
||||
// const { user: author } = context
|
||||
// const actorId = author.actorId
|
||||
// debug(`actorId = ${actorId}`)
|
||||
// const createActivity = await new Promise((resolve, reject) => {
|
||||
// as.create()
|
||||
// .id(`${actorId}/status/${args.activityId}`)
|
||||
// .actor(`${actorId}`)
|
||||
// .object(
|
||||
// as
|
||||
// .article()
|
||||
// .id(`${actorId}/status/${post.id}`)
|
||||
// .content(post.content)
|
||||
// .to('https://www.w3.org/ns/activitystreams#Public')
|
||||
// .publishedNow()
|
||||
// .attributedTo(`${actorId}`),
|
||||
// )
|
||||
// .prettyWrite((err, doc) => {
|
||||
// if (err) {
|
||||
// reject(err)
|
||||
// } else {
|
||||
// debug(doc)
|
||||
// const parsedDoc = JSON.parse(doc)
|
||||
// parsedDoc.send = true
|
||||
// resolve(JSON.stringify(parsedDoc))
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// try {
|
||||
// await activityPub.sendActivity(createActivity)
|
||||
// } catch (e) {
|
||||
// debug(`error sending post activity\n${e}`)
|
||||
// }
|
||||
// return post
|
||||
// },
|
||||
SignupVerification: async (resolve, root, args, context, info) => {
|
||||
const keys = generateRsaKeyPair()
|
||||
Object.assign(args, keys)
|
||||
|
||||
@ -21,7 +21,7 @@ if (!hasEmailConfig) {
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: CONFIG.SMTP_HOST,
|
||||
port: CONFIG.SMTP_PORT,
|
||||
ignoreTLS: CONFIG.SMTP_IGNORE_TLS,
|
||||
ignoreTLS: CONFIG.SMTP_IGNORE_TLS === 'true',
|
||||
secure: false, // true for 465, false for other ports
|
||||
auth: hasAuthData && {
|
||||
user: CONFIG.SMTP_USERNAME,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -351,10 +351,12 @@ When("I log in with the following credentials:", table => {
|
||||
});
|
||||
|
||||
When("open the notification menu and click on the first item", () => {
|
||||
cy.get(".notifications-menu").click();
|
||||
cy.get(".notifications-menu").invoke('show').click(); // "invoke('show')" because of the delay for show the menu
|
||||
cy.get(".notification-mention-post")
|
||||
.first()
|
||||
.click();
|
||||
.click({
|
||||
force: true
|
||||
});
|
||||
});
|
||||
|
||||
Then("see {int} unread notifications in the top menu", count => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
Feature: Notifications for a mentions
|
||||
Feature: Notification for a mention
|
||||
As a user
|
||||
I want to be notified if sb. mentions me in a post or comment
|
||||
In order join conversations about or related to me
|
||||
|
||||
10
webapp/app/router.scrollBehavior.js
Normal file
10
webapp/app/router.scrollBehavior.js
Normal file
@ -0,0 +1,10 @@
|
||||
export default function(to, from, savedPosition) {
|
||||
if (savedPosition) return savedPosition
|
||||
|
||||
// Edge case: If you click on a notification from a comment and then on the
|
||||
// post page you click on 'comments', we avoid a "jumping" scroll behavior,
|
||||
// ie. jump to the top and scroll back from there
|
||||
if (to.path === from.path && to.hash !== from.hash) return false
|
||||
|
||||
return { x: 0, y: 0 }
|
||||
}
|
||||
@ -32,6 +32,7 @@ describe('Comment.vue', () => {
|
||||
truncate: a => a,
|
||||
removeHtml: a => a,
|
||||
},
|
||||
$scrollTo: jest.fn(),
|
||||
$apollo: {
|
||||
mutate: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
@ -51,6 +52,8 @@ describe('Comment.vue', () => {
|
||||
})
|
||||
|
||||
describe('shallowMount', () => {
|
||||
beforeEach(jest.useFakeTimers)
|
||||
|
||||
Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
@ -117,7 +120,35 @@ describe('Comment.vue', () => {
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(jest.useFakeTimers)
|
||||
describe('scrollToAnchor mixin', () => {
|
||||
describe('$route.hash !== comment.id', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$route = {
|
||||
hash: '',
|
||||
}
|
||||
})
|
||||
|
||||
it('skips $scrollTo', () => {
|
||||
wrapper = Wrapper()
|
||||
jest.runAllTimers()
|
||||
expect(mocks.$scrollTo).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('$route.hash === comment.id', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$route = {
|
||||
hash: '#commentId-2',
|
||||
}
|
||||
})
|
||||
|
||||
it('calls $scrollTo', () => {
|
||||
wrapper = Wrapper()
|
||||
jest.runAllTimers()
|
||||
expect(mocks.$scrollTo).toHaveBeenCalledWith('#commentId-2')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('test callbacks', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
</ds-card>
|
||||
</div>
|
||||
<div v-else :class="{ comment: true, 'disabled-content': comment.deleted || comment.disabled }">
|
||||
<ds-card :id="`commentId-${comment.id}`">
|
||||
<ds-card :id="anchor">
|
||||
<ds-space margin-bottom="small" margin-top="small">
|
||||
<hc-user :user="author" :date-time="comment.createdAt" />
|
||||
<!-- Content Menu (can open Modals) -->
|
||||
@ -80,8 +80,10 @@ import ContentMenu from '~/components/ContentMenu'
|
||||
import ContentViewer from '~/components/Editor/ContentViewer'
|
||||
import HcCommentForm from '~/components/CommentForm/CommentForm'
|
||||
import CommentMutations from '~/graphql/CommentMutations'
|
||||
import scrollToAnchor from '~/mixins/scrollToAnchor.js'
|
||||
|
||||
export default {
|
||||
mixins: [scrollToAnchor],
|
||||
data: function() {
|
||||
return {
|
||||
isCollapsed: true,
|
||||
@ -109,6 +111,9 @@ export default {
|
||||
user: 'auth/user',
|
||||
isModerator: 'auth/isModerator',
|
||||
}),
|
||||
anchor() {
|
||||
return `commentId-${this.comment.id}`
|
||||
},
|
||||
displaysComment() {
|
||||
return !this.unavailable || this.isModerator
|
||||
},
|
||||
@ -142,6 +147,9 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
checkAnchor(anchor) {
|
||||
return `#${this.anchor}` === anchor
|
||||
},
|
||||
isAuthor(id) {
|
||||
return this.user.id === id
|
||||
},
|
||||
|
||||
@ -42,6 +42,7 @@ describe('CommentList.vue', () => {
|
||||
truncate: a => a,
|
||||
removeHtml: a => a,
|
||||
},
|
||||
$scrollTo: jest.fn(),
|
||||
$apollo: {
|
||||
queries: {
|
||||
Post: {
|
||||
@ -65,12 +66,46 @@ describe('CommentList.vue', () => {
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
it('displays a comments counter', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('span.ds-tag').text()).toEqual('1')
|
||||
})
|
||||
|
||||
it('displays a comments counter', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('span.ds-tag').text()).toEqual('1')
|
||||
})
|
||||
|
||||
describe('scrollToAnchor mixin', () => {
|
||||
beforeEach(jest.useFakeTimers)
|
||||
|
||||
describe('$route.hash !== `#comments`', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$route = {
|
||||
hash: '',
|
||||
}
|
||||
})
|
||||
|
||||
it('skips $scrollTo', () => {
|
||||
wrapper = Wrapper()
|
||||
jest.runAllTimers()
|
||||
expect(mocks.$scrollTo).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('$route.hash === `#comments`', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$route = {
|
||||
hash: '#comments',
|
||||
}
|
||||
})
|
||||
|
||||
it('calls $scrollTo', () => {
|
||||
wrapper = Wrapper()
|
||||
jest.runAllTimers()
|
||||
expect(mocks.$scrollTo).toHaveBeenCalledWith('#comments')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -30,8 +30,10 @@
|
||||
</template>
|
||||
<script>
|
||||
import Comment from '~/components/Comment/Comment'
|
||||
import scrollToAnchor from '~/mixins/scrollToAnchor'
|
||||
|
||||
export default {
|
||||
mixins: [scrollToAnchor],
|
||||
components: {
|
||||
Comment,
|
||||
},
|
||||
@ -39,6 +41,9 @@ export default {
|
||||
post: { type: Object, default: () => {} },
|
||||
},
|
||||
methods: {
|
||||
checkAnchor(anchor) {
|
||||
return anchor === '#comments'
|
||||
},
|
||||
updateCommentList(updatedComment) {
|
||||
this.post.comments = this.post.comments.map(comment => {
|
||||
return comment.id === updatedComment.id ? updatedComment : comment
|
||||
|
||||
@ -75,7 +75,7 @@ export default {
|
||||
|
||||
const followedUser = follow ? data.followUser : data.unfollowUser
|
||||
this.$emit('update', followedUser)
|
||||
} catch {
|
||||
} catch (err) {
|
||||
optimisticResult.followedByCurrentUser = !follow
|
||||
this.$emit('optimistic', optimisticResult)
|
||||
}
|
||||
|
||||
@ -69,16 +69,16 @@ describe('Notification', () => {
|
||||
it('renders reason', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.reason-text-for-test').text()).toEqual(
|
||||
'notifications.menu.commented_on_post',
|
||||
'notifications.reason.commented_on_post',
|
||||
)
|
||||
})
|
||||
it('renders title', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.text()).toContain("It's a post title")
|
||||
})
|
||||
it('renders the "Comment:"', () => {
|
||||
it('renders the identifier "notifications.comment"', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.text()).toContain('Comment:')
|
||||
expect(wrapper.text()).toContain('notifications.comment')
|
||||
})
|
||||
it('renders the contentExcerpt', () => {
|
||||
wrapper = Wrapper()
|
||||
@ -119,7 +119,7 @@ describe('Notification', () => {
|
||||
it('renders reason', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.reason-text-for-test').text()).toEqual(
|
||||
'notifications.menu.mentioned_in_post',
|
||||
'notifications.reason.mentioned_in_post',
|
||||
)
|
||||
})
|
||||
it('renders title', () => {
|
||||
@ -169,7 +169,7 @@ describe('Notification', () => {
|
||||
it('renders reason', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.reason-text-for-test').text()).toEqual(
|
||||
'notifications.menu.mentioned_in_comment',
|
||||
'notifications.reason.mentioned_in_comment',
|
||||
)
|
||||
})
|
||||
it('renders title', () => {
|
||||
@ -177,9 +177,9 @@ describe('Notification', () => {
|
||||
expect(wrapper.text()).toContain("It's a post title")
|
||||
})
|
||||
|
||||
it('renders the "Comment:"', () => {
|
||||
it('renders the identifier "notifications.comment"', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.text()).toContain('Comment:')
|
||||
expect(wrapper.text()).toContain('notifications.comment')
|
||||
})
|
||||
|
||||
it('renders the contentExcerpt', () => {
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<hc-user :user="from.author" :date-time="from.createdAt" :trunc="35" />
|
||||
</ds-space>
|
||||
<ds-text class="reason-text-for-test" color="soft">
|
||||
{{ $t(`notifications.menu.${notification.reason}`) }}
|
||||
{{ $t(`notifications.reason.${notification.reason}`) }}
|
||||
</ds-text>
|
||||
</client-only>
|
||||
<ds-space margin-bottom="x-small" />
|
||||
@ -23,7 +23,9 @@
|
||||
>
|
||||
<ds-space margin-bottom="x-small" />
|
||||
<div>
|
||||
<span v-if="isComment" class="comment-notification-header">Comment:</span>
|
||||
<span v-if="isComment" class="comment-notification-header">
|
||||
{{ $t(`notifications.comment`) }}:
|
||||
</span>
|
||||
{{ from.contentExcerpt | removeHtml }}
|
||||
</div>
|
||||
</ds-card>
|
||||
|
||||
@ -46,11 +46,46 @@ describe('NotificationMenu.vue', () => {
|
||||
expect(wrapper.contains('.dropdown')).toBe(false)
|
||||
})
|
||||
|
||||
describe('given only unread notifications', () => {
|
||||
beforeEach(() => {
|
||||
data = () => {
|
||||
return {
|
||||
displayedNotifications: [
|
||||
{
|
||||
id: 'notification-41',
|
||||
read: true,
|
||||
post: {
|
||||
id: 'post-1',
|
||||
title: 'some post title',
|
||||
contentExcerpt: 'this is a post content',
|
||||
author: {
|
||||
id: 'john-1',
|
||||
slug: 'john-doe',
|
||||
name: 'John Doe',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('counter displays 0', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('ds-button-stub').text()).toEqual('0')
|
||||
})
|
||||
|
||||
it('button is not primary', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('ds-button-stub').props('primary')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('given some notifications', () => {
|
||||
beforeEach(() => {
|
||||
data = () => {
|
||||
return {
|
||||
notifications: [
|
||||
displayedNotifications: [
|
||||
{
|
||||
id: 'notification-41',
|
||||
read: false,
|
||||
@ -79,15 +114,34 @@ describe('NotificationMenu.vue', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'notification-43',
|
||||
read: true,
|
||||
post: {
|
||||
id: 'post-3',
|
||||
title: 'read post title',
|
||||
contentExcerpt: 'this is yet another post content',
|
||||
author: {
|
||||
id: 'john-1',
|
||||
slug: 'john-doe',
|
||||
name: 'John Doe',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('displays the total number of notifications', () => {
|
||||
it('displays the number of unread notifications', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('ds-button-stub').text()).toEqual('2')
|
||||
})
|
||||
|
||||
it('renders primary button', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('ds-button-stub').props('primary')).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<ds-button v-if="totalNotifications <= 0" class="notifications-menu" disabled icon="bell">
|
||||
{{ totalNotifications }}
|
||||
<ds-button v-if="!notificationsCount" class="notifications-menu" disabled icon="bell">
|
||||
{{ unreadNotificationsCount }}
|
||||
</ds-button>
|
||||
<dropdown v-else class="notifications-menu" :placement="placement">
|
||||
<template slot="default" slot-scope="{ toggleMenu }">
|
||||
<ds-button primary icon="bell" @click.prevent="toggleMenu">
|
||||
{{ totalNotifications }}
|
||||
<ds-button :primary="!!unreadNotificationsCount" icon="bell" @click.prevent="toggleMenu">
|
||||
{{ unreadNotificationsCount }}
|
||||
</ds-button>
|
||||
</template>
|
||||
<template slot="popover">
|
||||
<div class="notifications-menu-popover">
|
||||
<notification-list :notifications="notifications" @markAsRead="markAsRead" />
|
||||
<notification-list :notifications="displayedNotifications" @markAsRead="markAsRead" />
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
@ -18,6 +18,7 @@
|
||||
|
||||
<script>
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import { NOTIFICATIONS_POLL_INTERVAL } from '~/constants/notifications'
|
||||
import { notificationQuery, markAsReadMutation } from '~/graphql/User'
|
||||
import NotificationList from '../NotificationList/NotificationList'
|
||||
|
||||
@ -29,6 +30,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
displayedNotifications: [],
|
||||
notifications: [],
|
||||
}
|
||||
},
|
||||
@ -46,17 +48,29 @@ export default {
|
||||
variables,
|
||||
})
|
||||
if (!(markAsRead && markAsRead.read === true)) return
|
||||
this.notifications = this.notifications.map(n => {
|
||||
return n.from.id === markAsRead.from.id ? markAsRead : n
|
||||
this.displayedNotifications = this.displayedNotifications.map(n => {
|
||||
return this.equalNotification(n, markAsRead) ? markAsRead : n
|
||||
})
|
||||
} catch (err) {
|
||||
throw new Error(err)
|
||||
this.$toast.error(err.message)
|
||||
}
|
||||
},
|
||||
equalNotification(a, b) {
|
||||
return a.from.id === b.from.id && a.createdAt === b.createdAt && a.reason === b.reason
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
totalNotifications() {
|
||||
return (this.notifications || []).length
|
||||
notificationsCount() {
|
||||
return (this.displayedNotifications || []).length
|
||||
},
|
||||
unreadNotificationsCount() {
|
||||
let countUnread = 0
|
||||
if (this.displayedNotifications) {
|
||||
this.displayedNotifications.forEach(notification => {
|
||||
if (!notification.read) countUnread++
|
||||
})
|
||||
}
|
||||
return countUnread
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
@ -64,6 +78,23 @@ export default {
|
||||
query() {
|
||||
return notificationQuery(this.$i18n)
|
||||
},
|
||||
pollInterval() {
|
||||
return NOTIFICATIONS_POLL_INTERVAL
|
||||
},
|
||||
update(data) {
|
||||
const newNotifications = data.notifications.filter(newN => {
|
||||
return !this.displayedNotifications.find(oldN => this.equalNotification(newN, oldN))
|
||||
})
|
||||
this.displayedNotifications = newNotifications
|
||||
.concat(this.displayedNotifications)
|
||||
.sort((a, b) => {
|
||||
return new Date(b.createdAt) - new Date(a.createdAt)
|
||||
})
|
||||
return data.notifications
|
||||
},
|
||||
error(error) {
|
||||
this.$toast.error(error)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
1
webapp/constants/notifications.js
Normal file
1
webapp/constants/notifications.js
Normal file
@ -0,0 +1 @@
|
||||
export const NOTIFICATIONS_POLL_INTERVAL = 60000
|
||||
@ -135,11 +135,12 @@
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"menu": {
|
||||
"reason": {
|
||||
"mentioned_in_post": "Hat dich in einem Beitrag erwähnt …",
|
||||
"mentioned_in_comment": "Hat dich in einem Kommentar erwähnt …",
|
||||
"commented_on_post": "Hat deinen Beitrag kommentiert …"
|
||||
}
|
||||
},
|
||||
"comment": "Kommentar"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Suchen",
|
||||
|
||||
@ -136,11 +136,12 @@
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"menu": {
|
||||
"reason": {
|
||||
"mentioned_in_post": "Mentioned you in a post …",
|
||||
"mentioned_in_comment": "Mentioned you in a comment …",
|
||||
"commented_on_post": "Commented on your post …"
|
||||
}
|
||||
},
|
||||
"comment": "Comment"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Search",
|
||||
|
||||
23
webapp/mixins/scrollToAnchor.js
Normal file
23
webapp/mixins/scrollToAnchor.js
Normal file
@ -0,0 +1,23 @@
|
||||
function scrollToAnchor(anchor, { checkAnchor, $scrollTo }) {
|
||||
if (typeof checkAnchor !== 'function')
|
||||
throw new Error(
|
||||
'You must define `checkAnchor` on the component if you use scrollToAnchor mixin!',
|
||||
)
|
||||
if (!checkAnchor(anchor)) return
|
||||
setTimeout(() => {
|
||||
$scrollTo(anchor)
|
||||
}, 250)
|
||||
}
|
||||
|
||||
export default {
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
const anchor = to && to.hash
|
||||
scrollToAnchor(anchor, this)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const anchor = this.$route && this.$route.hash
|
||||
scrollToAnchor(anchor, this)
|
||||
},
|
||||
}
|
||||
@ -124,80 +124,6 @@ export default {
|
||||
middleware: ['authenticated', 'termsAndConditions'],
|
||||
linkActiveClass: 'router-link-active',
|
||||
linkExactActiveClass: 'router-link-exact-active',
|
||||
scrollBehavior: (to, _from, savedPosition) => {
|
||||
let position = false
|
||||
// if no children detected and scrollToTop is not explicitly disabled
|
||||
if (
|
||||
to.matched.length < 2 &&
|
||||
to.matched.every(r => r.components.default.options.scrollToTop !== false)
|
||||
) {
|
||||
// scroll to the top of the page
|
||||
position = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
}
|
||||
} else if (to.matched.some(r => r.components.default.options.scrollToTop)) {
|
||||
// if one of the children has scrollToTop option set to true
|
||||
position = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// savedPosition is only available for popstate navigations (back button)
|
||||
if (savedPosition) {
|
||||
position = savedPosition
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
// wait for the out transition to complete (if necessary)
|
||||
window.$nuxt.$once('triggerScroll', () => {
|
||||
let processInterval = null
|
||||
let processTime = 0
|
||||
const callInterval = 100
|
||||
const callIntervalLimit = 2000
|
||||
|
||||
// coords will be used if no selector is provided,
|
||||
// or if the selector didn't match any element.
|
||||
if (to.hash) {
|
||||
let hash = to.hash
|
||||
// CSS.escape() is not supported with IE and Edge.
|
||||
if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape !== 'undefined') {
|
||||
hash = '#' + window.CSS.escape(hash.substr(1))
|
||||
}
|
||||
try {
|
||||
processInterval = setInterval(() => {
|
||||
const hashIsFound = document.querySelector(hash)
|
||||
|
||||
if (hashIsFound) {
|
||||
position = {
|
||||
selector: hash,
|
||||
offset: { x: 0, y: -500 },
|
||||
}
|
||||
}
|
||||
processTime += callInterval
|
||||
if (hashIsFound || processTime >= callIntervalLimit) {
|
||||
clearInterval(processInterval)
|
||||
processInterval = null
|
||||
}
|
||||
}, callInterval)
|
||||
} catch (e) {
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.warn(
|
||||
'Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let resolveInterval = setInterval(() => {
|
||||
if (!processInterval) {
|
||||
clearInterval(resolveInterval)
|
||||
resolve(position)
|
||||
}
|
||||
}, callInterval)
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
/*
|
||||
@ -216,6 +142,13 @@ export default {
|
||||
keys: envWhitelist,
|
||||
},
|
||||
],
|
||||
[
|
||||
'vue-scrollto/nuxt',
|
||||
{
|
||||
offset: -100, // to compensate fixed navbar height
|
||||
duration: 1000,
|
||||
},
|
||||
],
|
||||
'cookie-universal-nuxt',
|
||||
'@nuxtjs/apollo',
|
||||
'@nuxtjs/axios',
|
||||
|
||||
@ -63,7 +63,7 @@
|
||||
"apollo-client": "~2.6.4",
|
||||
"cookie-universal-nuxt": "~2.0.18",
|
||||
"cross-env": "~6.0.0",
|
||||
"date-fns": "2.4.0",
|
||||
"date-fns": "2.4.1",
|
||||
"express": "~4.17.1",
|
||||
"graphql": "~14.5.8",
|
||||
"isemail": "^3.2.0",
|
||||
@ -76,13 +76,14 @@
|
||||
"stack-utils": "^1.0.2",
|
||||
"string-hash": "^1.1.3",
|
||||
"tippy.js": "^4.3.5",
|
||||
"tiptap": "~1.25.0",
|
||||
"tiptap-extensions": "~1.27.0",
|
||||
"tiptap": "~1.26.0",
|
||||
"tiptap-extensions": "~1.28.0",
|
||||
"trunc-html": "^1.1.2",
|
||||
"v-tooltip": "~2.0.2",
|
||||
"vue-count-to": "~1.0.13",
|
||||
"vue-infinite-scroll": "^2.0.2",
|
||||
"vue-izitoast": "^1.2.1",
|
||||
"vue-scrollto": "^2.17.1",
|
||||
"vue-sweetalert-icons": "~4.2.0",
|
||||
"vuex-i18n": "~1.13.1",
|
||||
"xregexp": "^4.2.4",
|
||||
@ -110,7 +111,7 @@
|
||||
"eslint": "~5.16.0",
|
||||
"eslint-config-prettier": "~6.3.0",
|
||||
"eslint-config-standard": "~12.0.0",
|
||||
"eslint-loader": "~3.0.1",
|
||||
"eslint-loader": "~3.0.2",
|
||||
"eslint-plugin-import": "~2.18.2",
|
||||
"eslint-plugin-jest": "~22.17.0",
|
||||
"eslint-plugin-node": "~10.0.0",
|
||||
|
||||
@ -77,17 +77,6 @@ export default {
|
||||
]
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
if (to.hash === '#comments') {
|
||||
window.scroll({
|
||||
top: document.getElementById('comments').offsetTop,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -197,7 +197,7 @@ export default {
|
||||
|
||||
.ds-card-image {
|
||||
img {
|
||||
max-height: 300px;
|
||||
height: 300px;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
144
webapp/yarn.lock
144
webapp/yarn.lock
@ -4235,6 +4235,11 @@ bcrypt-pbkdf@^1.0.0:
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
bezier-easing@2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bezier-easing/-/bezier-easing-2.1.0.tgz#c04dfe8b926d6ecaca1813d69ff179b7c2025d86"
|
||||
integrity sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY=
|
||||
|
||||
bfj@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.1.tgz#05a3b7784fbd72cfa3c22e56002ef99336516c48"
|
||||
@ -5822,10 +5827,10 @@ data-urls@^1.0.0:
|
||||
whatwg-mimetype "^2.2.0"
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
date-fns@2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.4.0.tgz#e02d1d08ce80ae1db3de40a0028c9f54203d034b"
|
||||
integrity sha512-xS547fK1omgCgOGbyU0fBY2pdeXQ9/WO/PMsVgX1jtF56dXNHrV3Z+GKWIOE7IG+UEeu+fTyTlnIvBKbxXxdSw==
|
||||
date-fns@2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.4.1.tgz#b53f9bb65ae6bd9239437035710e01cf383b625e"
|
||||
integrity sha512-2RhmH/sjDSCYW2F3ZQxOUx/I7PvzXpi89aQL2d3OAxSTwLx6NilATeUbe0menFE3Lu5lFkOFci36ivimwYHHxw==
|
||||
|
||||
date-fns@^1.27.2:
|
||||
version "1.30.1"
|
||||
@ -6477,11 +6482,12 @@ eslint-import-resolver-node@^0.3.2:
|
||||
debug "^2.6.9"
|
||||
resolve "^1.5.0"
|
||||
|
||||
eslint-loader@~3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-3.0.1.tgz#03f5693d7c2dc5b710c2bbe85ca500536dc3c852"
|
||||
integrity sha512-opQF7tGGf793wrpBex6WP7TzcGqJ5/vpQ9nziuznYNWSw/g4dB/5M4y8h7TJP5u6R6tBIFkJheV3MJxsVbNHNg==
|
||||
eslint-loader@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-3.0.2.tgz#5a627316a51d6f41d357b9f6f0554e91506cdd6e"
|
||||
integrity sha512-S5VnD+UpVY1PyYRqeBd/4pgsmkvSokbHqTXAQMpvCyRr3XN2tvSLo9spm2nEpqQqh9dezw3os/0zWihLeOg2Rw==
|
||||
dependencies:
|
||||
fs-extra "^8.1.0"
|
||||
loader-fs-cache "^1.0.2"
|
||||
loader-utils "^1.2.3"
|
||||
object-hash "^1.3.1"
|
||||
@ -7316,16 +7322,7 @@ fs-extra@^7.0.0, fs-extra@^7.0.1:
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-extra@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.0.1.tgz#90294081f978b1f182f347a440a209154344285b"
|
||||
integrity sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-extra@^8.1.0:
|
||||
fs-extra@^8.0.1, fs-extra@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
||||
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
|
||||
@ -12333,10 +12330,10 @@ prosemirror-commands@^1.0.8:
|
||||
prosemirror-state "^1.0.0"
|
||||
prosemirror-transform "^1.0.0"
|
||||
|
||||
prosemirror-dropcursor@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.1.1.tgz#c60ed1ed6c58804a06a75db06a0d993b087b7622"
|
||||
integrity sha512-GeUyMO/tOEf8MXrP7Xb7UIMrfK86OGh0fnyBrHfhav4VjY9cw65mNoqHy87CklE5711AhCP5Qzfp8RL/hVKusg==
|
||||
prosemirror-dropcursor@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.1.2.tgz#d54428e0fdbc0fb3d4c5809acd1ad031e6cb6855"
|
||||
integrity sha512-QHZbYPr8AY0g88TC/Wp7jpYbUoSpTSO8sqHNGvvZOInsAyylIdOpsrfhY1NC+/lh+iuwka0YogGtq2mmE7cr4g==
|
||||
dependencies:
|
||||
prosemirror-state "^1.0.0"
|
||||
prosemirror-transform "^1.1.0"
|
||||
@ -12377,10 +12374,10 @@ prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.0.1:
|
||||
prosemirror-state "^1.0.0"
|
||||
w3c-keyname "^1.1.8"
|
||||
|
||||
prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.7.1:
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.7.1.tgz#f140a6e366e1e283aa7a94dbb8c2c7d13139689e"
|
||||
integrity sha512-hYrZPbJvdo2QWERmkCuS80BEf5Rcf3+S28ETr4xu8XKPYjmU6aeQn23G1Fu/2rwqUmk5ZyWYo2nyEsN+Cdv2Qg==
|
||||
prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.7.2:
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.7.2.tgz#829abd7fb496783ba088936d2d7aff228206829a"
|
||||
integrity sha512-mopozod/qNTB6utEyY8q4w1nCLDakpr39d8smzHno/wuAivCzBU8HkC9YOx1MBdTcTU6sXiIEh08hQfkC3damw==
|
||||
dependencies:
|
||||
orderedmap "^1.0.0"
|
||||
|
||||
@ -12400,10 +12397,10 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.2.4:
|
||||
prosemirror-model "^1.0.0"
|
||||
prosemirror-transform "^1.0.0"
|
||||
|
||||
prosemirror-tables@^0.9.1:
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-0.9.1.tgz#1669100ee9f64b0c269824dcd1c0584c66075acb"
|
||||
integrity sha512-n5h2OvlnQGsW1ToT1WOIlemV/3PDw4miUQoHEpawOk2oDhi46czKdzEg/rq3z0f/aZ3CwoyxviuqAZChBILC4A==
|
||||
prosemirror-tables@^0.9.5:
|
||||
version "0.9.5"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-0.9.5.tgz#94d9881a46051e6fff3c51edffafa346da084def"
|
||||
integrity sha512-RlAF/D7OvnDCOL8B6Qt6KuBkb0w3SedTdrou7wH7Nn2ml7+M5xUalW/h1f7dMD3wjsU47/Cn8zTbEkCDIpIggw==
|
||||
dependencies:
|
||||
prosemirror-keymap "^1.0.0"
|
||||
prosemirror-model "^1.0.0"
|
||||
@ -12411,10 +12408,10 @@ prosemirror-tables@^0.9.1:
|
||||
prosemirror-transform "^1.0.0"
|
||||
prosemirror-view "^1.0.0"
|
||||
|
||||
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.1.3.tgz#28cfdf1f9ee514edc40466be7b7db39eed545fdf"
|
||||
integrity sha512-1O6Di5lOL1mp4nuCnQNkHY7l2roIW5y8RH4ZG3hMYmkmDEWzTaFFnxxAAHsE5ipGLBSRcTlP7SsDhYBIdSuLpQ==
|
||||
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.1.4.tgz#30b35f02dd7761dd8139e5eb7612831fd031036a"
|
||||
integrity sha512-1Y3XuaFJtwusYDvojcCxi3VZvNIntPVoh/dpeVaIM5Vf1V+M6xiIWcDgktUWWRovMxEhdibnpt5eyFmYJJhHtQ==
|
||||
dependencies:
|
||||
prosemirror-model "^1.0.0"
|
||||
|
||||
@ -12423,10 +12420,10 @@ prosemirror-utils@^0.9.6:
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.9.6.tgz#3d97bd85897e3b535555867dc95a51399116a973"
|
||||
integrity sha512-UC+j9hQQ1POYfMc5p7UFxBTptRiGPR7Kkmbl3jVvU8VgQbkI89tR/GK+3QYC8n+VvBZrtAoCrJItNhWSxX3slA==
|
||||
|
||||
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.10.0.tgz#7de5de75f0c90f8b9f09d09ed4467554d59adddb"
|
||||
integrity sha512-STHw0xHfk+XPMqMLTKykRL1qEMtO+n1GWINBl94IPIq82AmWO1Ors4wVw93HKo/oIadWRrP/7faNJKh1UVLrTg==
|
||||
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.11.4:
|
||||
version "1.11.4"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.11.4.tgz#f80aec8924d59d4c3456dcc5bfea733758ec9b40"
|
||||
integrity sha512-J0g7xiCDx+p3CtpC69E7HvMmnW7yCILEhOXxSANZPX8iIwUrVTfdWKAzufi9F9MoM08ewsaF254xV90NpkGWVQ==
|
||||
dependencies:
|
||||
prosemirror-model "^1.1.0"
|
||||
prosemirror-state "^1.0.0"
|
||||
@ -14515,62 +14512,62 @@ tippy.js@^4.3.5:
|
||||
dependencies:
|
||||
popper.js "^1.14.7"
|
||||
|
||||
tiptap-commands@^1.11.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.11.0.tgz#8c259e93d70447e93cedefcfa73a36301cd60a81"
|
||||
integrity sha512-WDX3JfI6Z80CCxkDfKUn6ya2UT3r1AM/McbB63oXq6iUyY5wZmw+qu/9LkSe3aISRTy9tfUKzJLjB7w9UnQ9Ig==
|
||||
tiptap-commands@^1.12.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.12.0.tgz#603b1c710c6950950eb1a7fc5279008f36bc2962"
|
||||
integrity sha512-LWAVHOxsFR4yUJuruEwJ2QMwe0e9S4kHQ4HVIPEIofhuXKW4vmjvvX9Lzgi4cHy5cXC/TBAU2D43BNy7vdH1Kg==
|
||||
dependencies:
|
||||
prosemirror-commands "^1.0.8"
|
||||
prosemirror-inputrules "^1.0.4"
|
||||
prosemirror-model "^1.7.1"
|
||||
prosemirror-model "^1.7.2"
|
||||
prosemirror-schema-list "^1.0.3"
|
||||
prosemirror-state "^1.2.4"
|
||||
prosemirror-tables "^0.9.1"
|
||||
prosemirror-tables "^0.9.5"
|
||||
prosemirror-utils "^0.9.6"
|
||||
tiptap-utils "^1.7.0"
|
||||
tiptap-utils "^1.8.0"
|
||||
|
||||
tiptap-extensions@~1.27.0:
|
||||
version "1.27.0"
|
||||
resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.27.0.tgz#547d2fbc8234818d195eef45a46e14aed7859a70"
|
||||
integrity sha512-DVazwQuEkWGjE45nhznB9LbD233s/0KOmWHcN6V1Ixm+/97Gaw1fEPUTIz/tHPYg3WKhPFOxI965sSB8Ne7Dnw==
|
||||
tiptap-extensions@~1.28.0:
|
||||
version "1.28.0"
|
||||
resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.28.0.tgz#4704945e7a4fe33a77de11847f7ca3058008895e"
|
||||
integrity sha512-yGKXGUnOrLhnXpnhTrL4tDJv+CSgyqVu0//M80uiY097btYnf/K0t7i0StRCY3Xg5mX5YFL9Q01f9Ppyi2jgtQ==
|
||||
dependencies:
|
||||
lowlight "^1.12.1"
|
||||
prosemirror-collab "^1.1.2"
|
||||
prosemirror-history "^1.0.4"
|
||||
prosemirror-model "^1.7.1"
|
||||
prosemirror-model "^1.7.2"
|
||||
prosemirror-state "^1.2.4"
|
||||
prosemirror-tables "^0.9.1"
|
||||
prosemirror-transform "^1.1.3"
|
||||
prosemirror-tables "^0.9.5"
|
||||
prosemirror-transform "^1.1.4"
|
||||
prosemirror-utils "^0.9.6"
|
||||
prosemirror-view "^1.10.0"
|
||||
tiptap "^1.25.0"
|
||||
tiptap-commands "^1.11.0"
|
||||
prosemirror-view "^1.11.4"
|
||||
tiptap "^1.26.0"
|
||||
tiptap-commands "^1.12.0"
|
||||
|
||||
tiptap-utils@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.7.0.tgz#eb9d1f9e6be7b2e6b50b8aaf0bb47f1a68266b41"
|
||||
integrity sha512-nJUrzR2cf+kcPyE2bIfzjnOewOynLm9kofQPIk2tMYwXfsgeNvYhMIbul4AJPYNoYyOLGUQ+vGpF6/5eUtC2Ew==
|
||||
tiptap-utils@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.8.0.tgz#cb03a263a1b1672bf4cccccb2078506fa91bd112"
|
||||
integrity sha512-0k7zuhwrNpEAnoiH8kjAE9IUnqV8FNX1bv9W7we+jhQZPUuxODcpMX1oUkrN9i1seFVfPcxgQa+SmIy63kRKig==
|
||||
dependencies:
|
||||
prosemirror-model "^1.7.1"
|
||||
prosemirror-model "^1.7.2"
|
||||
prosemirror-state "^1.2.4"
|
||||
prosemirror-tables "^0.9.1"
|
||||
prosemirror-tables "^0.9.5"
|
||||
prosemirror-utils "^0.9.6"
|
||||
|
||||
tiptap@^1.25.0, tiptap@~1.25.0:
|
||||
version "1.25.0"
|
||||
resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.25.0.tgz#4e518805ac99bb3d157b99f8b902231ad89f054a"
|
||||
integrity sha512-wPE96JjoHIMaWTPkZZqz0cayVe+QgR+1J7FR4h5MvJepPgrtwwQmgVVx7jAG7yXXZZdUhhrYlL2yMji4V7Vpjw==
|
||||
tiptap@^1.26.0, tiptap@~1.26.0:
|
||||
version "1.26.0"
|
||||
resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.26.0.tgz#edaa07b4b9b6836d433d0b8017d26d37cc0cc3c9"
|
||||
integrity sha512-lKJnZ4jL3luu3C5Y5aZIEj2spAfNPSwc5HPB+n9HhpSaWAfGM9XTOLm6I0EIbkLHiCnYNjItlLP6p1g+KPdtSw==
|
||||
dependencies:
|
||||
prosemirror-commands "^1.0.8"
|
||||
prosemirror-dropcursor "^1.1.1"
|
||||
prosemirror-dropcursor "^1.1.2"
|
||||
prosemirror-gapcursor "^1.0.4"
|
||||
prosemirror-inputrules "^1.0.4"
|
||||
prosemirror-keymap "^1.0.1"
|
||||
prosemirror-model "^1.7.1"
|
||||
prosemirror-model "^1.7.2"
|
||||
prosemirror-state "^1.2.4"
|
||||
prosemirror-view "^1.10.0"
|
||||
tiptap-commands "^1.11.0"
|
||||
tiptap-utils "^1.7.0"
|
||||
prosemirror-view "^1.11.4"
|
||||
tiptap-commands "^1.12.0"
|
||||
tiptap-utils "^1.8.0"
|
||||
|
||||
title-case@^2.1.0:
|
||||
version "2.1.1"
|
||||
@ -15315,6 +15312,13 @@ vue-router@~3.0.7:
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.7.tgz#b36ca107b4acb8ff5bc4ff824584059c23fcb87b"
|
||||
integrity sha512-utJ+QR3YlIC/6x6xq17UMXeAfxEvXA0VKD3PiSio7hBOZNusA1jXcbxZxVEfJunLp48oonjTepY8ORoIlRx/EQ==
|
||||
|
||||
vue-scrollto@^2.17.1:
|
||||
version "2.17.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-scrollto/-/vue-scrollto-2.17.1.tgz#cd62ee0b98cf7e2ba9fd94f029addcd093978a48"
|
||||
integrity sha512-uxOJXg6cZL88B+hTXRHDJMR+gHGiaS70ZTNk55fE5Z2TdwyIx9K/IHoNeTrtBrM6u3FASAIymKjZaQLmDf8Ykg==
|
||||
dependencies:
|
||||
bezier-easing "2.1.0"
|
||||
|
||||
vue-server-renderer@^2.6.10:
|
||||
version "2.6.10"
|
||||
resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.10.tgz#cb2558842ead360ae2ec1f3719b75564a805b375"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user