From dbb0a68766cd8a722469a555569db67570eb82c5 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Thu, 8 Jun 2017 15:53:12 -0400 Subject: [PATCH 01/17] Prep to release 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d78ba8713..bb804c6e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "talk", - "version": "1.9.1", + "version": "2.0.0", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", "scripts": { From f929059daf38d6b9539b26abd23a94659cd1b283 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 9 Jun 2017 13:23:16 -0600 Subject: [PATCH 02/17] added asset merging + url updates --- bin/cli-assets | 156 +++++++++++++++++++++++---------- errors.js | 10 ++- services/assets.js | 59 ++++++++++++- test/server/services/assets.js | 94 ++++++++++++++++++-- 4 files changed, 261 insertions(+), 58 deletions(-) diff --git a/bin/cli-assets b/bin/cli-assets index 97b5b043c..4805bead0 100755 --- a/bin/cli-assets +++ b/bin/cli-assets @@ -8,9 +8,12 @@ const program = require('./commander'); const parseDuration = require('ms'); const Table = require('cli-table'); const AssetModel = require('../models/asset'); +const CommentModel = require('../models/comment'); +const AssetsService = require('../services/assets'); const mongoose = require('../services/mongoose'); const scraper = require('../services/scraper'); const util = require('./util'); +const inquirer = require('inquirer'); // Register the shutdown criteria. util.onshutdown([ @@ -20,65 +23,112 @@ util.onshutdown([ /** * Lists all the assets registered in the database. */ -function listAssets() { - AssetModel - .find({}) - .sort({'created_at': 1}) - .then((asset) => { - let table = new Table({ - head: [ - 'ID', - 'Title', - 'URL' - ] - }); +async function listAssets() { + try { + let assets = await AssetModel.find({}).sort({'created_at': 1}); - asset.forEach((asset) => { - table.push([ - asset.id, - asset.title ? asset.title : '', - asset.url ? asset.url : '' - ]); - }); - - console.log(table.toString()); - util.shutdown(); - }) - .catch((err) => { - console.error(err); - util.shutdown(1); + let table = new Table({ + head: [ + 'ID', + 'Title', + 'URL' + ] }); + + assets.forEach((asset) => { + table.push([ + asset.id, + asset.title ? asset.title : '', + asset.url ? asset.url : '' + ]); + }); + + console.log(table.toString()); + util.shutdown(); + } catch (e) { + console.error(e); + util.shutdown(1); + } } -function refreshAssets(ageString) { - const now = new Date().getTime(); - const ageMs = parseDuration(ageString); - const age = new Date(now - ageMs); +async function refreshAssets(ageString) { + try { + const now = new Date().getTime(); + const ageMs = parseDuration(ageString); + const age = new Date(now - ageMs); - AssetModel.find({ - $or: [ - { - scraped: { - $lte: age + let assets = await AssetModel.find({ + $or: [ + { + scraped: { + $lte: age + } + }, + { + scraped: null } - }, - { - scraped: null - } - ] - }) + ] + }); - // Queue all the assets for scraping. - .then((assets) => Promise.all(assets.map(scraper.create))) + // Queue all the assets for scraping. + await Promise.all(assets.map(scraper.create)); - .then(() => { console.log('Assets were queued to be scraped'); util.shutdown(); - }) - .catch((err) => { - console.error(err); + } catch (e) { + console.error(e); util.shutdown(1); - }); + } +} + +async function updateURL(assetID, assetURL) { + try { + await AssetsService.updateURL(assetID, assetURL); + + console.log(`Asset ${assetID} was updated to have url ${assetURL}.`); + util.shutdown(); + } catch (e) { + console.error(e); + util.shutdown(1); + } +} + +async function merge(srcID, dstID) { + try { + + // Grab the assets... + let [srcAsset, dstAsset] = await AssetsService.findByIDs([srcID, dstID]); + if (!srcAsset || !dstAsset) { + throw new Error('Not all assets indicated by id exist, cannot merge'); + } + + // Count the affected resources... + let srcCommentCount = await CommentModel.find({asset_id: srcID}).count(); + + console.log(`Now going to update ${srcCommentCount} comments and delete the source Asset[${srcID}].`); + + let {confirm} = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: 'Proceed with merge', + default: false + } + ]); + + if (confirm) { + + // Perform the merge! + await AssetsService.merge(srcID, dstID); + } else { + console.warn('Aborting merge'); + } + + util.shutdown(0); + } catch (e) { + console.error(e); + util.shutdown(1); + } } //============================================================================== @@ -95,6 +145,16 @@ program .description('queues the assets that exceed the age requested') .action(refreshAssets); +program + .command('update-url ') + .description('update the URL of an asset') + .action(updateURL); + +program + .command('merge ') + .description('merges two assets together by moving comments from src to dst and deleting the src asset') + .action(merge); + program.parse(process.argv); // If there is no command listed, output help. diff --git a/errors.js b/errors.js index c203b1d25..87c3a2a65 100644 --- a/errors.js +++ b/errors.js @@ -168,6 +168,13 @@ const ErrCommentTooShort = new APIError('Comment was too short', { status: 400 }); +// ErrAssetURLAlreadyExists is returned when a rename operation is requested +// but an asset already exists with the new url. +const ErrAssetURLAlreadyExists = new APIError('Asset URL already exists, cannot rename', { + translation_key: 'ASSET_URL_ALREADY_EXISTS', + status: 409 +}); + module.exports = { ExtendableError, APIError, @@ -191,5 +198,6 @@ module.exports = { ErrInstallLock, ErrLoginAttemptMaximumExceeded, ErrEditWindowHasEnded, - ErrCommentTooShort + ErrCommentTooShort, + ErrAssetURLAlreadyExists }; diff --git a/services/assets.js b/services/assets.js index a86c4281f..b81513b72 100644 --- a/services/assets.js +++ b/services/assets.js @@ -1,3 +1,4 @@ +const CommentModel = require('../models/comment'); const AssetModel = require('../models/asset'); const SettingsService = require('./settings'); const domainlist = require('./domainlist'); @@ -128,9 +129,61 @@ module.exports = class AssetsService { * @param {Array} ids an array of Strings of asset_id * @return {Promise} resolves to list of Assets */ - static findMultipleById(ids) { - const query = ids.map((id) => ({id})); - return AssetModel.find(query); + static async findByIDs(ids) { + + // Find the assets. + let assets = await AssetModel.find({ + id: { + $in: ids + } + }); + + // Return them in the right order. + return ids.map((id) => assets.find((asset) => asset.id === id)); + } + + static async updateURL(id, url) { + + // Try to see if an asset already exists with the given url. + let asset = await AssetsService.findByUrl(url); + if (asset !== null) { + throw errors.ErrAssetURLAlreadyExists; + } + + // Seems that there was no other asset with the same url, try and perform + // the rename operation! An error may be thrown from this if the operation + // fails. This is ok. + await AssetModel.update({id}, {$set: {url}}); + } + + static async merge(srcAssetID, dstAssetID) { + + // Fetch both assets. + let [srcAsset, dstAsset] = await AssetsService.findByIDs([srcAssetID, dstAssetID]); + if (!srcAsset || !dstAsset) { + throw errors.ErrNotFound; + } + + // Resolve the merge operation, this invloves moving all resources attached + // to the src asset to the dst asset, and then removing the src asset. + + // First, update all the old comments to the new asset. + await CommentModel.update({ + asset_id: srcAssetID + }, { + $set: { + asset_id: dstAssetID + } + }, { + multi: true + }); + + // Second remove the old asset. + await AssetModel.remove({ + id: srcAssetID + }); + + // That's it! } static all(skip = null, limit = null) { diff --git a/test/server/services/assets.js b/test/server/services/assets.js index e46e75576..748e177e0 100644 --- a/test/server/services/assets.js +++ b/test/server/services/assets.js @@ -1,22 +1,29 @@ const AssetModel = require('../../../models/asset'); +const CommentModel = require('../../../models/comment'); const AssetsService = require('../../../services/assets'); +const CommentsService = require('../../../services/comments'); const SettingsService = require('../../../services/settings'); +const url = require('url'); const chai = require('chai'); const expect = chai.expect; +const chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); // Use the chai should. chai.should(); +const settings = {id: '1', moderation: 'PRE', domains: {whitelist: ['new.test.com', 'test.com', 'override.test.com']}}; +const defaults = {url:'http://test.com'}; + describe('services.AssetsService', () => { - beforeEach(() => { - const settings = {id: '1', moderation: 'PRE', domains: {whitelist: ['new.test.com', 'test.com', 'override.test.com']}}; - const defaults = {url:'http://test.com'}; + let asset; + beforeEach(async () => { + await SettingsService.init(settings); - return SettingsService.init(settings).then(() => { - return AssetModel.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}); - }); + asset = await AssetModel.findOneAndUpdate({id: '1'}, {$setOnInsert: defaults}, {upsert: true, new: true}); }); describe('#findById', ()=> { @@ -120,4 +127,79 @@ describe('services.AssetsService', () => { }); }); }); + + describe('#updateURL', () => { + + it('should change the url if the asset was found, and there was no conflict', async () => { + let newURL = url.resolve(asset.url, '/new-url'); + + // Update the asset. + await AssetsService.updateURL(asset.id, newURL); + + // Check that the url was updated. + let {url: databaseURL} = await AssetsService.findById(asset.id); + + expect(databaseURL).to.equal(newURL); + }); + + it('should error if the new url already exists', async () => { + let newURL = url.resolve(asset.url, '/new-url'); + + // Create a new asset with our new URL. + await AssetModel.findOneAndUpdate({id: '2'}, {$setOnInsert: {url: newURL}}, {upsert: true, new: true}); + + return AssetsService.updateURL(asset.id, newURL).should.eventually.be.rejected; + }); + + }); + + describe('#merge', () => { + + it('should error if either the src or the dst is missing', () => { + return AssetsService.merge('not-found', asset.id).should.eventually.be.rejected; + }); + + it('should merge the assets', async () => { + let newURL = url.resolve(asset.url, '/new-url'); + + // Create a new asset with our new URL. + await AssetModel.findOneAndUpdate({id: '2'}, {$setOnInsert: {url: newURL}}, {upsert: true, new: true}); + + // Create some comments on both assets. + await CommentsService.publicCreate([ + { + asset_id: '1', + body: 'This is a comment!', + status: 'ACCEPTED' + }, + { + asset_id: '1', + body: 'This is a comment!', + status: 'ACCEPTED' + }, + { + asset_id: '2', + body: 'This is a comment!', + status: 'ACCEPTED' + }, + { + asset_id: '2', + body: 'This is a comment!', + status: 'ACCEPTED' + } + ]); + + // Merge all the comments from asset 1 into asset 2, followed by deleting + // asset 1. + await AssetsService.merge('1', '2'); + + // Check to see if the comments are moved. + expect(await CommentModel.find({asset_id: '1'}).count()).to.equal(0); + expect(await CommentModel.find({asset_id: '2'}).count()).to.equal(4); + + // Check to see if the asset was removed. + expect(await AssetModel.findOne({id: '1'})).to.equal(null); + }); + + }); }); From cc4c943a21c7c6950b194c891b7b9ad347dfdbe5 Mon Sep 17 00:00:00 2001 From: IAmSamHankins Date: Fri, 9 Jun 2017 17:27:35 -0400 Subject: [PATCH 03/17] hover states, link styling, alignment fixes --- .../coral-admin/src/components/ui/Header.css | 2 +- .../routes/Dashboard/components/Widget.css | 22 +++++----- .../Moderation/components/CommentCount.css | 1 + .../Moderation/components/UserDetail.css | 4 +- .../routes/Moderation/components/styles.css | 44 ++++++++++++------- locales/en.yml | 4 +- 6 files changed, 45 insertions(+), 32 deletions(-) diff --git a/client/coral-admin/src/components/ui/Header.css b/client/coral-admin/src/components/ui/Header.css index 7cb68ecbf..d99a7f295 100644 --- a/client/coral-admin/src/components/ui/Header.css +++ b/client/coral-admin/src/components/ui/Header.css @@ -77,7 +77,7 @@ letter-spacing: .8; &:hover { - background-color: #232323; + background-color: #404040; } &.active { diff --git a/client/coral-admin/src/routes/Dashboard/components/Widget.css b/client/coral-admin/src/routes/Dashboard/components/Widget.css index 587c51ada..cf83fcbdc 100644 --- a/client/coral-admin/src/routes/Dashboard/components/Widget.css +++ b/client/coral-admin/src/routes/Dashboard/components/Widget.css @@ -17,8 +17,9 @@ .heading { margin: 0; padding-left: 10px; - font-size: 1.5rem; - font-weight: bold; + font-size: 1.3rem; + font-weight: 600; + color: #2c2c2c; } .widgetTable { @@ -32,7 +33,8 @@ } .widgetHead p { - color: rgb(35, 102, 223); + color: #2c2c2c; + font-weight: 500; padding: 10px; text-align: left; text-transform: capitalize; @@ -47,11 +49,11 @@ } .rowLinkify { - cursor: pointer; border-bottom: 1px solid lightgrey; color: #555; height: var(--row-height); padding: 10px; + transition: background-color 200ms; } .rowLinkify:last-child { @@ -60,6 +62,7 @@ .rowLinkify:hover { background-color: #f8f8f8; + pointer: default; } .linkToAsset { @@ -68,16 +71,17 @@ } .linkToModerate { - background-color: #e0e0e0; + background-color: #BDBDBD; padding: 10px 14px; text-decoration: none; color: black; float: right; margin-left: 15px; + transition: background-color 200ms; } .linkToModerate:hover { - background-color: #ccc; + background-color: #9E9E9E; } .lede { @@ -89,14 +93,10 @@ color: #555; text-decoration: none; font-size: 1.2em; - font-weight: normal; + font-weight: 500; margin: 0; } -.assetTitle:hover { - text-decoration: underline; -} - .widgetCount { color: #555; font-size: 1.3em; diff --git a/client/coral-admin/src/routes/Moderation/components/CommentCount.css b/client/coral-admin/src/routes/Moderation/components/CommentCount.css index b684d0f4a..4d24d5702 100644 --- a/client/coral-admin/src/routes/Moderation/components/CommentCount.css +++ b/client/coral-admin/src/routes/Moderation/components/CommentCount.css @@ -12,4 +12,5 @@ right: 0; margin-top: -2px; font-size: 12px; + color: white; } diff --git a/client/coral-admin/src/routes/Moderation/components/UserDetail.css b/client/coral-admin/src/routes/Moderation/components/UserDetail.css index d4b5f0db7..710f84759 100644 --- a/client/coral-admin/src/routes/Moderation/components/UserDetail.css +++ b/client/coral-admin/src/routes/Moderation/components/UserDetail.css @@ -15,7 +15,7 @@ display: flex; .stat { - margin: 0 4px 12px; + margin: 0 4px 10px 0px; } .stat:last-child { @@ -49,7 +49,7 @@ li { display: inline-block; - margin: 0 10px; + margin-right: 10px; cursor: pointer; padding: 0 10px; } diff --git a/client/coral-admin/src/routes/Moderation/components/styles.css b/client/coral-admin/src/routes/Moderation/components/styles.css index d10311f74..fc825eccb 100644 --- a/client/coral-admin/src/routes/Moderation/components/styles.css +++ b/client/coral-admin/src/routes/Moderation/components/styles.css @@ -18,14 +18,20 @@ .tab { flex: 1; - color: #EEEEEE; + color: #C0C0C0; text-transform: capitalize; font-weight: 100; - font-size: 15px; + font-size: 14px; letter-spacing: 1px; transition: border-bottom 200ms; - padding: 0px 5px; - margin-right: 30px; + transition: color 200ms; + padding: 0px 10px; + margin-right: 20px; + &:hover { + color: white; + border-bottom: solid 2px #F36451; + box-sizing: border-box; + } } .active { @@ -33,6 +39,10 @@ box-sizing: border-box; border-bottom: solid 4px #F36451; font-weight: 400; + &:hover { + border-bottom: solid 4px #F36451; + font-weight: 400; + } } .active > span { @@ -103,19 +113,18 @@ span { font-weight: 400; font-size: 15px; letter-spacing: 1px; - transition: opacity 200ms; + transition: background-color 200ms; opacity: 1; - &:hover { - opacity: .8; - cursor: pointer; - } - &:first-child { text-align: left; } &:nth-child(2) { + &:hover { + cursor: pointer; + background-color: #212121; + } span { text-align: center; text-overflow: ellipsis; @@ -245,7 +254,6 @@ span { padding: 5px; color: #262626; font-size: 14px; - margin-left: 15px; line-height: 1px; font-weight: 300; } @@ -421,17 +429,21 @@ span { .tabIcon { position: relative; - top: 2px; + top: 3px; font-size: 16px; } .username { - color: blue; - text-decoration: underline; + color: #393B44; + text-decoration: none; cursor: pointer; - + font-weight: 600; + padding: 2px 5px; + border-radius: 2px; + margin-left: -5px; + transition: background-color 200ms ease; &:hover { - background-color: rgba(255, 0, 0, .1); + background-color: #E0E0E0; } } diff --git a/locales/en.yml b/locales/en.yml index ce9abdce5..930a4eadb 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -144,8 +144,8 @@ en: auto_update: "Data automatically updates every five minutes or when you Reload." comment_count: comments flags: Flags - most_flags: "Articles with the most flags" - most_conversations: "Articles with the most conversations" + most_flags: "Stories with the most flags" + most_conversations: "Stories with the most conversations" next_update: "{0} minutes until next update." no_activity: "There haven't been any comments anywhere in the last five minutes." no_flags: "There have been no flags in the last 5 minutes! Hooray!" From 65f7ee2ab3ef3cdcd1d7baec3bab33067b2eae6f Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 12 Jun 2017 19:09:42 -0300 Subject: [PATCH 04/17] coral-plugin-commnet-content --- .eslintignore | 1 + .gitignore | 1 + .../src/components/Comment.js | 4 +--- .../CommentContent.js | 17 -------------- .../__tests__/commentContent.spec.js | 11 --------- client/coral-plugin-history/Comment.js | 7 +++--- .../client/components/CreateUsernameDialog.js | 2 +- .../client/components/FakeComment.js | 8 +++---- .../client/.babelrc | 14 +++++++++++ .../client/.eslintrc.json | 23 +++++++++++++++++++ .../client/components/CommentContent.js | 23 +++++++++++++++++++ .../client/components/Link.js | 13 +++++++++++ .../client/components/styles.css | 8 +++++++ .../client/helpers/isLink.js | 5 ++++ .../client/index.js | 7 ++++++ plugins/coral-plugin-comment-content/index.js | 1 + 16 files changed, 106 insertions(+), 39 deletions(-) delete mode 100644 client/coral-plugin-commentcontent/CommentContent.js delete mode 100644 client/coral-plugin-commentcontent/__tests__/commentContent.spec.js create mode 100644 plugins/coral-plugin-comment-content/client/.babelrc create mode 100644 plugins/coral-plugin-comment-content/client/.eslintrc.json create mode 100644 plugins/coral-plugin-comment-content/client/components/CommentContent.js create mode 100644 plugins/coral-plugin-comment-content/client/components/Link.js create mode 100644 plugins/coral-plugin-comment-content/client/components/styles.css create mode 100644 plugins/coral-plugin-comment-content/client/helpers/isLink.js create mode 100644 plugins/coral-plugin-comment-content/client/index.js create mode 100644 plugins/coral-plugin-comment-content/index.js diff --git a/.eslintignore b/.eslintignore index 6fecd9d9b..8a8c3a213 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,4 +11,5 @@ plugins/* !plugins/coral-plugin-mod !plugins/coral-plugin-love !plugins/coral-plugin-viewing-options +!plugins/coral-plugin-comment-content node_modules diff --git a/.gitignore b/.gitignore index abd490356..b3e0eaa73 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,6 @@ plugins/* !plugins/coral-plugin-mod !plugins/coral-plugin-love !plugins/coral-plugin-viewing-options +!plugins/coral-plugin-comment-content **/node_modules/* diff --git a/client/coral-embed-stream/src/components/Comment.js b/client/coral-embed-stream/src/components/Comment.js index 92c59d936..82befeb2a 100644 --- a/client/coral-embed-stream/src/components/Comment.js +++ b/client/coral-embed-stream/src/components/Comment.js @@ -3,7 +3,6 @@ import React, {PropTypes} from 'react'; import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton'; import AuthorName from 'coral-plugin-author-name/AuthorName'; import TagLabel from 'coral-plugin-tag-label/TagLabel'; -import Content from 'coral-plugin-commentcontent/CommentContent'; import PubDate from 'coral-plugin-pubdate/PubDate'; import {ReplyBox, ReplyButton} from 'coral-plugin-replies'; import FlagComment from 'coral-plugin-flags/FlagComment'; @@ -466,8 +465,7 @@ export default class Comment extends React.Component { stopEditing={this.stopEditing} /> :
- - +
} diff --git a/client/coral-plugin-commentcontent/CommentContent.js b/client/coral-plugin-commentcontent/CommentContent.js deleted file mode 100644 index 501005b3d..000000000 --- a/client/coral-plugin-commentcontent/CommentContent.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -const name = 'coral-plugin-commentcontent'; - -const Content = ({body, styles}) => { - const textbreaks = body.split('\n'); - return
- { - textbreaks.map((line, i) => - {line}
-
) - } -
; -}; - -export default Content; diff --git a/client/coral-plugin-commentcontent/__tests__/commentContent.spec.js b/client/coral-plugin-commentcontent/__tests__/commentContent.spec.js deleted file mode 100644 index 377726b6d..000000000 --- a/client/coral-plugin-commentcontent/__tests__/commentContent.spec.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import {shallow} from 'enzyme'; -import {expect} from 'chai'; -import CommentContent from '../CommentContent'; - -describe('CommentContent', () => { - it('should render content', () => { - const render = shallow(); - expect(render.contains('test')).to.be.truthy; - }); -}); diff --git a/client/coral-plugin-history/Comment.js b/client/coral-plugin-history/Comment.js index cb188e53e..d85b7fb1c 100644 --- a/client/coral-plugin-history/Comment.js +++ b/client/coral-plugin-history/Comment.js @@ -1,8 +1,8 @@ import React, {PropTypes} from 'react'; import {Icon} from '../coral-ui'; import styles from './Comment.css'; +import Slot from 'coral-framework/components/Slot'; import PubDate from '../coral-plugin-pubdate/PubDate'; -import Content from '../coral-plugin-commentcontent/CommentContent'; import t from 'coral-framework/services/i18n'; @@ -10,9 +10,10 @@ const Comment = (props) => { return (
-

{t('createdisplay.if_you_dont_change_your_name')} diff --git a/plugins/coral-plugin-auth/client/components/FakeComment.js b/plugins/coral-plugin-auth/client/components/FakeComment.js index 390dd409b..5517b72aa 100644 --- a/plugins/coral-plugin-auth/client/components/FakeComment.js +++ b/plugins/coral-plugin-auth/client/components/FakeComment.js @@ -1,17 +1,17 @@ import React from 'react'; +import t from 'coral-framework/services/i18n'; import {ReplyButton} from 'coral-plugin-replies'; import PubDate from 'coral-plugin-pubdate/PubDate'; +import Slot from 'coral-framework/components/Slot'; import AuthorName from 'coral-plugin-author-name/AuthorName'; -import Content from 'coral-plugin-commentcontent/CommentContent'; import styles from 'coral-embed-stream/src/components/Comment.css'; -import t from 'coral-framework/services/i18n'; -export const FakeComment = ({username, created_at, body}) => ( +export const FakeComment = ({username, created_at, comment}) => (


- +