diff --git a/circle.yml b/circle.yml index d45828540..bd5c2c520 100644 --- a/circle.yml +++ b/circle.yml @@ -37,7 +37,7 @@ dependencies: # Install node dependencies. - yarn --version - - yarn global add node-gyp nsp --force + - yarn global add node-gyp --force - yarn post: @@ -59,8 +59,6 @@ test: - yarn test # Run the end to end tests - yarn e2e:ci - # Check dependancies using nsp. - - nsp check deployment: release: diff --git a/client/coral-admin/src/components/ModerationKeysModal.js b/client/coral-admin/src/components/ModerationKeysModal.js index c8066b81d..4e0eb3ac3 100644 --- a/client/coral-admin/src/components/ModerationKeysModal.js +++ b/client/coral-admin/src/components/ModerationKeysModal.js @@ -23,7 +23,7 @@ export default class ModerationKeysModal extends React.Component { 'ctrl+f': 'modqueue.toggle_search', t: 'modqueue.next_queue', [`1...${this.props.queueCount}`]: 'modqueue.jump_to_queue', - s: 'modqueue.singleview', + z: 'modqueue.singleview', '?': 'modqueue.thismenu', }, }, diff --git a/client/coral-admin/src/routes/Configure/components/Configure.js b/client/coral-admin/src/routes/Configure/components/Configure.js index 2b3e44f1e..f81bf7735 100644 --- a/client/coral-admin/src/routes/Configure/components/Configure.js +++ b/client/coral-admin/src/routes/Configure/components/Configure.js @@ -86,7 +86,6 @@ export default class Configure extends Component { } Configure.propTypes = { - notify: PropTypes.func.isRequired, savePending: PropTypes.func.isRequired, auth: PropTypes.object.isRequired, data: PropTypes.object.isRequired, diff --git a/client/coral-admin/src/routes/Moderation/components/Moderation.js b/client/coral-admin/src/routes/Moderation/components/Moderation.js index 760dfcbb1..c1de008fe 100644 --- a/client/coral-admin/src/routes/Moderation/components/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/components/Moderation.js @@ -19,7 +19,7 @@ class Moderation extends Component { componentWillMount() { const { toggleModal, singleView } = this.props; - key('s', () => singleView()); + key('z', () => singleView()); key('shift+/', () => toggleModal(true)); key('esc', () => toggleModal(false)); key('ctrl+f', () => this.openSearch()); @@ -113,7 +113,7 @@ class Moderation extends Component { }; componentWillUnmount() { - key.unbind('s'); + key.unbind('z'); key.unbind('shift+/'); key.unbind('esc'); key.unbind('ctrl+f'); diff --git a/client/coral-admin/src/routes/Moderation/graphql.js b/client/coral-admin/src/routes/Moderation/graphql.js index c542b8f84..aa6267880 100644 --- a/client/coral-admin/src/routes/Moderation/graphql.js +++ b/client/coral-admin/src/routes/Moderation/graphql.js @@ -112,6 +112,10 @@ function getOlderDate(a, b) { function determineLatestChange(comment) { let lc = null; + comment.body_history.forEach(item => { + lc = getOlderDate(lc, item.created_at); + }); + comment.status_history.forEach(item => { lc = getOlderDate(lc, item.created_at); }); @@ -124,12 +128,16 @@ function determineLatestChange(comment) { } function reconstructPreviousCommentState(comment) { - const history = comment.status_history; + const statusHistory = comment.status_history; + const bodyHistory = comment.body_history; const actions = comment.actions; const lastChangeDate = determineLatestChange(comment); const previousComment = { ...comment, - status_history: history.filter( + body_history: bodyHistory.filter( + item => new Date(item.created_at) < lastChangeDate + ), + status_history: statusHistory.filter( item => new Date(item.created_at) < lastChangeDate ), actions: actions.filter(item => new Date(item.created_at) < lastChangeDate), @@ -145,6 +153,9 @@ function reconstructPreviousCommentState(comment) { previousComment.status_history.length - 1 ].type; + previousComment.body = + previousComment.body_history[previousComment.body_history.length - 1].body; + return previousComment; } @@ -383,6 +394,11 @@ export function handleIndicatorChange(root, comment, queueConfig) { export const subscriptionFields = ` status + body + body_history { + body + created_at + } actions { __typename created_at diff --git a/client/coral-embed-stream/src/graphql/index.js b/client/coral-embed-stream/src/graphql/index.js index dcded6b45..279a1dcf6 100644 --- a/client/coral-embed-stream/src/graphql/index.js +++ b/client/coral-embed-stream/src/graphql/index.js @@ -198,7 +198,7 @@ export default { { mutationResult: { data: { createComment: { comment } } } } ) => { if ( - (prev.me.role !== 'ADMIN' && + (!['ADMIN', 'MODERATOR'].includes(prev.me.role) && prev.asset.settings.moderation === 'PRE') || comment.status === 'PREMOD' || comment.status === 'REJECTED' || diff --git a/docs/_docs/03-03-product-guide-moderator-features.md b/docs/_docs/03-03-product-guide-moderator-features.md index 064257623..671461f31 100644 --- a/docs/_docs/03-03-product-guide-moderator-features.md +++ b/docs/_docs/03-03-product-guide-moderator-features.md @@ -133,16 +133,18 @@ Talk also allows you to moderate a commenters recent comments from this view. Talk also supports a number of keyboard shortcuts that moderators can leverage to moderate quickly: -| Shortcut | Action | -| -------- | ------------------------------- | -| `j` | Go to the next comment | -| `k` | Go to the previous comment | -| `ctrl+f` | Open search | -| `t` | Switch queues | -| `s` | Toggle single comment edit view | -| `?` | Open this menu | -| `d` | Approve | -| `f` | Reject | +| Shortcut | Action | +| -------- | -------------------------- | +| `j` | Go to the next comment | +| `k` | Go to the previous comment | +| `ctrl+f` | Open search | +| `t` | Switch queues | +| `z` | Zen mode | +| `?` | Open this menu | +| `d` | Approve | +| `f` | Reject | + +Note: "Zen mode" allows a moderator to view and action only one comment at a time. Enjoy the silence! ### Stories diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 550856082..cec8e0015 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -453,6 +453,11 @@ type EditInfo { editableUntil: Date } +type CommentBodyHistory { + body: String! + created_at: Date! +} + type CommentStatusHistory { type: COMMENT_STATUS! created_at: Date! @@ -471,6 +476,9 @@ type Comment { # The actual comment data. body: String! + # The body history of the comment. + body_history: [CommentBodyHistory!]! + # the tags on the comment tags: [TagLink!] diff --git a/locales/da.yml b/locales/da.yml index d4544043c..b3cf32ea4 100644 --- a/locales/da.yml +++ b/locales/da.yml @@ -274,7 +274,7 @@ da: shift_key: "⇧" shortcuts: "Genveje" show_shortcuts: "Vis genveje" - singleview: "Skift enkeltkommentar redigerings visning" + singleview: "Zen mode" thismenu: "Åben denne menu" thousand: "k" try_these: "Prøv disse" diff --git a/locales/en.yml b/locales/en.yml index 8a9ce4bcb..efcd7a1d9 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -330,7 +330,7 @@ en: shortcuts: "Shortcuts" sort: "Sort" show_shortcuts: "Show Shortcuts" - singleview: "Toggle single comment edit view" + singleview: "Zen mode" thismenu: "Open this menu" jump_to_queue: "Jump to specific queue" thousand: k diff --git a/locales/es.yml b/locales/es.yml index 010119728..6b695851e 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -291,7 +291,7 @@ es: shortcuts: Atajos sort: "Ordenar" show_shortcuts: "Mostrar Atajos" - singleview: "Colocar vista de edición de comentario único" + singleview: "Modo zen" thismenu: "Abrir este menu" thousand: k try_these: "Intentar estos" diff --git a/locales/fr.yml b/locales/fr.yml index e9f291b65..890582f7a 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -225,7 +225,7 @@ fr: shift_key: ⇧ shortcuts: Raccourcis show_shortcuts: "Afficher les raccourcis" - singleview: "Passer en mode d'édition de commentaire unique" + singleview: "Mode zen" spam_ads: Spam / Publicités thismenu: "Ouvrir ce menu" thousand: k diff --git a/locales/nl_NL.yml b/locales/nl_NL.yml index 43e7d90f5..be8888600 100644 --- a/locales/nl_NL.yml +++ b/locales/nl_NL.yml @@ -325,7 +325,7 @@ nl_NL: shortcuts: "Sneltoetsen" sort: "Sorteer" show_shortcuts: "Toon sneltoetsen" - singleview: "Schakel wijzigen enkele reactie aan of uit" + singleview: "Zen-modus" thismenu: "Open dit menu" jump_to_queue: "Spring naar specifieke wachtrij" thousand: k diff --git a/locales/pt_BR.yml b/locales/pt_BR.yml index 7ed111287..bc36409cc 100644 --- a/locales/pt_BR.yml +++ b/locales/pt_BR.yml @@ -276,7 +276,7 @@ pt_BR: shift_key: "⇧" shortcuts: "Atalhos" show_shortcuts: "Ver atalhos" - singleview: "Alternar vista de edição de comentário único" + singleview: "Modo zen" spam_ads: Spam/Anuncios thismenu: "Abra este menu" thousand: k diff --git a/locales/zh_CN.yml b/locales/zh_CN.yml index 480115cbc..1b1709a38 100644 --- a/locales/zh_CN.yml +++ b/locales/zh_CN.yml @@ -290,7 +290,7 @@ zh_CN: shortcuts: "快捷键" sort: "排序" show_shortcuts: "显示快捷键" - singleview: "展开单评论编辑视图" + singleview: "禅宗模式" thismenu: "开启该菜单" jump_to_queue: "跳转到特定序列" thousand: "千" diff --git a/locales/zh_TW.yml b/locales/zh_TW.yml index f18971a8f..0edceedb4 100644 --- a/locales/zh_TW.yml +++ b/locales/zh_TW.yml @@ -290,7 +290,7 @@ zh_TW: shortcuts: "快捷鍵" sort: "排序" show_shortcuts: "顯示快捷鍵" - singleview: "切換單個評論編輯視圖" + singleview: "禪宗模式" thismenu: "打開這個菜單" jump_to_queue: "跳轉到特定隊列" thousand: 千 diff --git a/services/comments.js b/services/comments.js index e18e04970..68f991324 100644 --- a/services/comments.js +++ b/services/comments.js @@ -19,6 +19,7 @@ module.exports = class CommentsService { static async publicCreate(input) { // Extract the parent_id from the comment, if there is one. const { status = 'NONE', parent_id = null } = input; + const created_at = new Date(); // Check to see if we are replying to a comment, and if that comment is // visible. @@ -37,14 +38,14 @@ module.exports = class CommentsService { ? [ { type: status, - created_at: new Date(), + created_at, }, ] : [], body_history: [ { body: input.body, - created_at: new Date(), + created_at, }, ], }, @@ -85,6 +86,7 @@ module.exports = class CommentsService { */ static async edit({ id, author_id, body, status }) { const EDITABLE_STATUSES = ['NONE', 'PREMOD', 'ACCEPTED']; + const created_at = new Date(); const query = { id, @@ -112,11 +114,11 @@ module.exports = class CommentsService { $push: { body_history: { body, - created_at: new Date(), + created_at, }, status_history: { type: status, - created_at: new Date(), + created_at, }, }, }); @@ -160,53 +162,13 @@ module.exports = class CommentsService { editedComment.body = body; editedComment.body_history.push({ body, - created_at: new Date(), + created_at, }); editedComment.status_history.push({ type: status, - created_at: new Date(), + created_at, }); - // We should adjust the comment's status such that if it was approved - // previously, we should mark the comment as 'NONE' or 'PREMOD', which ever - // was most recent if the new comment is destined to be `NONE` or `PREMOD`. - if (originalComment.status === 'ACCEPTED' && status === 'NONE') { - const lastUnmoderatedStatus = CommentsService.lastUnmoderatedStatus( - originalComment - ); - - // If the last moderated status was found and the current comment doesn't - // match this already. - if (lastUnmoderatedStatus && status !== lastUnmoderatedStatus) { - // Update the comment model (if at this point, the status is still - // accepted) with the previously unmoderated status - await CommentModel.update( - { - id, - status, - }, - { - $set: { - status: lastUnmoderatedStatus, - }, - $push: { - status_history: { - type: lastUnmoderatedStatus, - created_at: new Date(), - }, - }, - } - ); - - // Update the returned comment. - editedComment.status = lastUnmoderatedStatus; - editedComment.status_history.push({ - type: lastUnmoderatedStatus, - created_at: new Date(), - }); - } - } - await events.emitAsync(COMMENTS_EDIT, originalComment, editedComment); return editedComment; diff --git a/test/server/services/comments.js b/test/server/services/comments.js index 2d0b37163..e0fc6da67 100644 --- a/test/server/services/comments.js +++ b/test/server/services/comments.js @@ -227,12 +227,12 @@ describe('services.CommentsService', () => { id: originalComment.id, author_id: '123', body: 'This is a body!', - status: 'NONE', + status: 'PREMOD', }); expect(editedComment).to.have.property('status', 'PREMOD'); - expect(editedComment.status_history).to.have.length(4); - expect(editedComment.status_history[3]).to.have.property( + expect(editedComment.status_history).to.have.length(3); + expect(editedComment.status_history[2]).to.have.property( 'type', 'PREMOD' ); @@ -240,8 +240,8 @@ describe('services.CommentsService', () => { retrivedComment = await CommentsService.findById(originalComment.id); expect(retrivedComment).to.have.property('status', 'PREMOD'); - expect(retrivedComment.status_history).to.have.length(4); - expect(retrivedComment.status_history[3]).to.have.property( + expect(retrivedComment.status_history).to.have.length(3); + expect(retrivedComment.status_history[2]).to.have.property( 'type', 'PREMOD' );