Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
tibetsprague committed Jan 20, 2023
2 parents abc20e5 + e263604 commit ec8cc85
Show file tree
Hide file tree
Showing 19 changed files with 115 additions and 66 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

## [5.2.1] - 2023-01-20

### Changed
- Links from comment notification emails go to the first group of the post
- Adds extra CSS class for mentioned current user

### Fixed
- All notifications work with chat posts - showing chat content instead of trying to show empty post title
- Comment digest emails now link to the right comment

## [5.2.0] - 2023-01-11

### Added
Expand Down
2 changes: 2 additions & 0 deletions api/graphql/makeModels.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export default function makeModels (userId, isAdmin, apiClient) {
getters: {
commenters: (p, { first }) => p.getCommenters(first, userId),
commentersTotal: p => p.getCommentersTotal(userId),
details: p => p.details(userId),
myReactions: p => userId ? p.postReactions(userId).fetch() : [],
myVote: p => userId ? p.userVote(userId).then(v => !!v) : false, // Remove once Mobile has been updated
myEventResponse: p =>
Expand Down Expand Up @@ -651,6 +652,7 @@ export default function makeModels (userId, isAdmin, apiClient) {
}
],
getters: {
text: comment => comment.text(userId),
parentComment: (c) => c.parentComment().fetch(),
myReactions: c => userId ? c.commentReactions(userId).fetch() : [],
commentReactions: c => c.commentReactions().fetch()
Expand Down
4 changes: 2 additions & 2 deletions api/models/Activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,9 @@ module.exports = bookshelf.Model.extend({

groupIds: function (activity) {
if (activity.get('post_id')) {
return get(activity, 'relations.post.relations.groups', []).map(c => c.id)
return get(activity, 'relations.post.relations.groups', []).map(g => g.id)
} else if (activity.get('comment_id')) {
return get(activity, 'relations.comment.relations.post.relations.groups', []).map(c => c.id)
return get(activity, 'relations.comment.relations.post.relations.groups', []).map(g => g.id)
} else if (activity.get('group_id')) {
// For group to group join requests/invites other group is the one related to the reader of the notification
return activity.get('other_group_id') ? [activity.relations.group.id, activity.relations.otherGroup.id] : [activity.relations.group.id]
Expand Down
8 changes: 4 additions & 4 deletions api/models/Comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ module.exports = bookshelf.Model.extend(Object.assign({
return this.belongsTo(Post)
},

text: function () {
return RichText.processHTML(this.get('text'))
},

mentions: function () {
return RichText.getUserMentions(this.text())
},

text: function (forUserId) {
return RichText.processHTML(this.get('text'), { forUserId })
},

thanks: function () {
return this.hasMany(Thank)
},
Expand Down
2 changes: 1 addition & 1 deletion api/models/FlaggedItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ module.exports = bookshelf.Model.extend({
return Frontend.Route.post(this.get('object_id'), group, isPublic)
case FlaggedItem.Type.COMMENT:
const comment = await this.getObject()
return Frontend.Route.comment({ comment, group })
return Frontend.Route.comment({ comment, groupSlug: group ? group.get('slug') : null })
case FlaggedItem.Type.MEMBER:
return Frontend.Route.profile(this.get('object_id'))
default:
Expand Down
33 changes: 19 additions & 14 deletions api/models/Notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ module.exports = bookshelf.Model.extend({
post_user_profile_url: Frontend.Route.tokenLogin(reader, token,
Frontend.Route.profile(user) + '?ctt=announcement_email&cti=' + reader.id),
post_description: RichText.qualifyLinks(post.details(), group.get('slug')),
post_title: decode(post.get('name')),
post_title: decode(post.summary()),
post_url: Frontend.Route.tokenLogin(reader, token,
Frontend.Route.post(post, group) + '?ctt=announcement_email&cti=' + reader.id),
unfollow_url: Frontend.Route.tokenLogin(reader, token,
Expand All @@ -362,13 +362,16 @@ module.exports = bookshelf.Model.extend({
},

sendPostMentionEmail: function () {
var post = this.post()
var reader = this.reader()
var user = post.relations.user
var replyTo = Email.postReplyAddress(post.id, reader.id)
const post = this.post()
const reader = this.reader()
const user = post.relations.user
const tags = post.relations.tags
const firstTag = tags && tags.first()?.get('name')
const replyTo = Email.postReplyAddress(post.id, reader.id)

var groupIds = Activity.groupIds(this.relations.activity)
const groupIds = Activity.groupIds(this.relations.activity)
if (isEmpty(groupIds)) throw new Error('no group ids in activity')

return Group.find(groupIds[0])
.then(group => reader.generateToken()
.then(token => Email.sendPostMentionNotification({
Expand All @@ -387,10 +390,10 @@ module.exports = bookshelf.Model.extend({
post_user_profile_url: Frontend.Route.tokenLogin(reader, token,
Frontend.Route.profile(user) + '?ctt=post_mention_email&cti=' + reader.id),
post_description: RichText.qualifyLinks(post.details(), group.get('slug')),
post_title: decode(post.get('name')),
post_title: post.get('type') === Post.Type.CHAT ? '#' + firstTag : decode(post.summary()),
post_type: post.get('type'),
post_url: Frontend.Route.tokenLogin(reader, token,
Frontend.Route.post(post) + '?ctt=post_mention_email&cti=' + reader.id),
Frontend.Route.post(post, group, false, firstTag) + '?ctt=post_mention_email&cti=' + reader.id),
unfollow_url: Frontend.Route.tokenLogin(reader, token,
Frontend.Route.unfollow(post, group) + '?ctt=post_mention_email&cti=' + reader.id),
tracking_pixel_url: Analytics.pixelUrl('Mention in Post', {userId: reader.id})
Expand All @@ -399,6 +402,7 @@ module.exports = bookshelf.Model.extend({
},

// version corresponds to names of versions in SendWithUs
// XXX: This is not used right now, we send Comment Digests instead
sendCommentNotificationEmail: function (version) {
const comment = this.comment()
const reader = this.reader()
Expand All @@ -407,7 +411,7 @@ module.exports = bookshelf.Model.extend({
const post = comment.relations.post
const commenter = comment.relations.user
const replyTo = Email.postReplyAddress(post.id, reader.id)
const title = decode(post.get('name'))
const title = decode(post.summary())

var postLabel = `"${title}"`
if (post.get('type') === 'welcome') {
Expand Down Expand Up @@ -613,9 +617,9 @@ module.exports = bookshelf.Model.extend({
const token = await reader.generateToken()
return Email.sendDonationToEmail({
email: reader.get('email'),
sender: {name: project.get('name')},
sender: {name: project.summary()},
data: {
project_title: project.get('name'),
project_title: project.summary(),
project_url: Frontend.Route.tokenLogin(reader, token,
Frontend.Route.post(project) + '?ctt=post_mention_email&cti=' + reader.id),
contribution_amount: projectContribution.get('amount') / 100,
Expand All @@ -636,9 +640,9 @@ module.exports = bookshelf.Model.extend({
const token = await reader.generateToken()
return Email.sendDonationFromEmail({
email: reader.get('email'),
sender: {name: project.get('name')},
sender: {name: project.summary()},
data: {
project_title: project.get('name'),
project_title: project.summary(),
project_url: Frontend.Route.tokenLogin(reader, token,
Frontend.Route.post(project) + '?ctt=post_mention_email&cti=' + reader.id),
contribution_amount: projectContribution.get('amount') / 100,
Expand Down Expand Up @@ -676,7 +680,7 @@ module.exports = bookshelf.Model.extend({
post_user_profile_url: Frontend.Route.tokenLogin(reader, token,
Frontend.Route.profile(inviter) + '?ctt=post_mention_email&cti=' + reader.id),
post_description: RichText.qualifyLinks(post.details(), group.get('slug')),
post_title: decode(post.get('name')),
post_title: decode(post.summary()),
post_type: 'event',
post_date: post.prettyEventDates(post.get('start_time'), post.get('end_time')),
post_url: Frontend.Route.tokenLogin(reader, token,
Expand Down Expand Up @@ -764,6 +768,7 @@ module.exports = bookshelf.Model.extend({
return Notification.findUnsent({withRelated: [
'activity',
'activity.post',
'activity.post.tags',
'activity.post.groups',
'activity.post.user',
'activity.comment',
Expand Down
8 changes: 4 additions & 4 deletions api/models/Post.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ module.exports = bookshelf.Model.extend(Object.assign({
// Simple attribute getters

// This should be always used when accessing this attribute
details: function () {
return RichText.processHTML(this.get('description'))
details: function (forUserId) {
return RichText.processHTML(this.get('description'), { forUserId })
},

description: function () {
description: function (forUserId) {
console.warn('Deprecation warning: Post#description called but has been replaced by Post#details')
return this.details()
return this.details(forUserId)
},

title: function () {
Expand Down
14 changes: 7 additions & 7 deletions api/models/PushNotification.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ module.exports = bookshelf.Model.extend({
}, {
textForContribution: function (contribution) {
const post = contribution.relations.post
return `You have been added as a contributor to the request "${post.get('name')}"`
return `You have been added as a contributor to the request "${post.summary()}"`
},

textForComment: function (comment, version) {
Expand All @@ -54,7 +54,7 @@ module.exports = bookshelf.Model.extend({
return `${person} sent an image`
}
const blurb = TextHelpers.presentHTMLToText(comment.text(), { truncate: 140 })
const postName = comment.relations.post.get('name')
const postName = comment.relations.post.summary()

return version === 'mention'
? `${person} mentioned you: "${blurb}" (in "${postName}")`
Expand All @@ -63,7 +63,7 @@ module.exports = bookshelf.Model.extend({

textForPost: function (post, group, userId, version) {
const person = post.relations.user.get('name')
const postName = decode(post.get('name'))
const postName = decode(post.summary())

return version === 'mention'
? `${person} mentioned you in "${postName}"`
Expand All @@ -72,13 +72,13 @@ module.exports = bookshelf.Model.extend({

textForAnnouncement: function (post) {
const person = post.relations.user.get('name')
const postName = decode(post.get('name'))
const postName = decode(post.summary())

return `${person} sent an announcement titled "${postName}"`
},

textForEventInvitation: function (post, actor) {
const postName = decode(post.get('name'))
const postName = decode(post.summary())

return `${actor.get('name')} invited you to "${postName}"`
},
Expand Down Expand Up @@ -133,7 +133,7 @@ module.exports = bookshelf.Model.extend({

textForDonationTo: function (contribution) {
const project = contribution.relations.project
const postName = decode(project.get('name'))
const postName = decode(project.summary())
const amount = contribution.get('amount') / 100

return `You contributed $${amount} to "${postName}"`
Expand All @@ -142,7 +142,7 @@ module.exports = bookshelf.Model.extend({
textForDonationFrom: function (contribution) {
const actor = contribution.relations.user
const project = contribution.relations.project
const postName = decode(project.get('name'))
const postName = decode(project.summary())

const amount = contribution.get('amount') / 100
return `${actor.get('name')} contributed $${amount} to "${postName}"`
Expand Down
5 changes: 4 additions & 1 deletion api/models/comment/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const sendDigests = async () => {
q.orderBy('created_at', 'asc')
}},
'user',
'groups',
'comments.user',
'comments.media'
]})
Expand All @@ -57,6 +58,7 @@ export const sendDigests = async () => {
if (comments.length === 0) return []

const followers = await post.followers().fetch()
const firstGroup = post.relations.groups.first()

return Promise.map(followers.models, user => {
// select comments not written by this user and newer than user's last
Expand All @@ -72,6 +74,7 @@ export const sendDigests = async () => {

const presentComment = comment => {
const presented = {
id: comment.id,
name: comment.relations.user.get('name'),
avatar_url: comment.relations.user.get('avatar_url')
}
Expand Down Expand Up @@ -119,7 +122,7 @@ export const sendDigests = async () => {
count: commentData.length,
post_title: post.summary(),
post_creator_avatar_url: post.relations.user.get('avatar_url'),
thread_url: Frontend.Route.comment({ comment: commentData[0], post }),
thread_url: Frontend.Route.comment({ comment: commentData[0], groupSlug: firstGroup.get('slug'), post }),
comments: commentData,
subject_prefix: some(hasMention, commentData)
? 'You were mentioned in'
Expand Down
4 changes: 2 additions & 2 deletions api/services/Frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,14 @@ module.exports = {
return url(`/members/${getModelId(user)}`)
},

post: function (post, group, isPublic) {
post: function (post, group, isPublic, topic) {
let groupSlug = getSlug(group)
let groupUrl = '/all'

if (isPublic) {
groupUrl = '/public'
} else if (!isEmpty(groupSlug)) {
groupUrl = `/groups/${groupSlug}`
groupUrl = `/groups/${groupSlug}` + (topic ? `/topics/${topic}` : '')
}
return url(`${groupUrl}/post/${getModelId(post)}`)
},
Expand Down
34 changes: 22 additions & 12 deletions api/services/RichText.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ Note: `Post#details()` and `Comment#text()` both run this by default, and it sho
be ran against those fields.
*/
export function processHTML (contentHTML, providedInsaneOptions) {
export function processHTML (
contentHTML,
{
forUserId = null,
insaneOptions = {}
} = {}
) {
// NOTE: Will probably fail silently if bad content sent
if (!contentHTML) return ''

Expand All @@ -48,7 +54,7 @@ export function processHTML (contentHTML, providedInsaneOptions) {

// Normalize legacy Mentions
if (
el.getAttribute('data-entity-type') === 'mention'||
el.getAttribute('data-entity-type') === 'mention' ||
el.getAttribute('data-user-id')
) {
const newSpanElement = dom.createElement('span')
Expand All @@ -60,8 +66,6 @@ export function processHTML (contentHTML, providedInsaneOptions) {
newSpanElement.innerHTML = el.innerHTML

el.parentNode.replaceChild(newSpanElement, el)

return
}

// Normalize legacy Topics
Expand All @@ -78,15 +82,21 @@ export function processHTML (contentHTML, providedInsaneOptions) {
newSpanElement.innerHTML = el.innerHTML

el.parentNode.replaceChild(newSpanElement, el)

return
}
}, dom.querySelectorAll('a'))


// Add extra CSS class to mentions of `forUserId` (usually currentUser)
if (forUserId) {
forEach(
el => el.classList.add('mention-current-user'),
dom.querySelectorAll(`span.mention[data-id="${forUserId.toString()}"]`)
)
}

// Always sanitize on output, but only once and only here
const santizedHTML = insane(
const santizedHTML = insane(
dom.querySelector('body').innerHTML,
TextHelpers.insaneOptions(providedInsaneOptions)
TextHelpers.insaneOptions(insaneOptions)
)

return santizedHTML
Expand All @@ -111,7 +121,7 @@ export function qualifyLinks (processedHTML, groupSlug) {
// Convert Mention and Topic `span` elements to `a` elements
const convertSpansToAnchors = forEach(el => {
const anchorElement = dom.createElement('a')
let href = el.className === 'mention'
const href = el.className === 'mention'
? PathHelpers.mentionPath(el.getAttribute('data-id'), groupSlug)
: PathHelpers.topicPath(el.getAttribute('data-id'), groupSlug)

Expand Down Expand Up @@ -146,10 +156,10 @@ in the provided HTML. Used for generating notifications.
*/
export function getUserMentions (processedHTML) {
if (!processedHTML) return []

const dom = getDOM(processedHTML)
const mentionElements = dom.querySelectorAll('.mention')
const mentionedUserIDs = map(el => el.getAttribute('data-id'), mentionElements)

return filter(el => !isNull(el), uniq(mentionedUserIDs))
}
2 changes: 1 addition & 1 deletion api/services/Slack.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = {
} else {
return format('<%s|%s> posted <%s|%s> in <%s|%s>',
Frontend.Route.profile(creator), creator.get('name'),
Frontend.Route.post(post, group), post.get('name'),
Frontend.Route.post(post, group), post.summary(),
Frontend.Route.group(group), group.get('name'))
}
},
Expand Down
Loading

0 comments on commit ec8cc85

Please sign in to comment.