From 2b1108cd883affeab55491cad2486999793afb69 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 7 May 2018 14:27:00 -0600 Subject: [PATCH 001/162] Settings Cache Refactor --- bin/cli-settings | 2 +- graph/loaders/assets.js | 2 +- graph/loaders/settings.js | 79 +++++++++++---- graph/mutators/user.js | 6 +- graph/resolvers/asset.js | 9 +- graph/resolvers/comment.js | 10 +- graph/resolvers/root_query.js | 10 +- graph/resolvers/util.js | 7 +- middleware/staticTemplate.js | 2 +- models/schema/asset.js | 3 +- models/schema/setting.js | 17 ---- package.json | 1 + .../talk-plugin-local-auth/server/mutators.js | 6 +- .../talk-plugin-notifications/server/util.js | 2 +- .../server/connect.js | 6 +- .../server/mutators.js | 6 +- services/assets.js | 47 +++------ services/cache.js | 66 ------------- services/comments.js | 2 +- services/domain_list.js | 4 +- services/moderation/index.js | 15 +-- services/passport.js | 6 +- services/settings.js | 99 +++++++++---------- services/setup.js | 2 +- services/tags.js | 50 ++++------ services/wordlist.js | 6 +- yarn.lock | 4 + 27 files changed, 209 insertions(+), 260 deletions(-) diff --git a/bin/cli-settings b/bin/cli-settings index 72f42672d..05c7f2888 100755 --- a/bin/cli-settings +++ b/bin/cli-settings @@ -18,7 +18,7 @@ async function changeOrgName() { await cache.init(); // Get the original settings. - const settings = await Settings.retrieve('organizationName'); + const settings = await Settings.select('organizationName'); const { organizationName } = await inquirer.prompt([ { diff --git a/graph/loaders/assets.js b/graph/loaders/assets.js index 40305760c..868c8af68 100644 --- a/graph/loaders/assets.js +++ b/graph/loaders/assets.js @@ -71,7 +71,7 @@ const findOrCreateAssetByURL = async (ctx, url) => { // Check for whitelisting + get the settings at the same time. const [whitelisted, settings] = await Promise.all([ DomainList.urlCheck(url), - Settings.load('autoCloseStream closedTimeout'), + Settings.select('autoCloseStream', 'closedTimeout'), ]); // If the domain wasn't whitelisted, then we shouldn't create this asset! diff --git a/graph/loaders/settings.js b/graph/loaders/settings.js index fa0a17459..a1329b619 100644 --- a/graph/loaders/settings.js +++ b/graph/loaders/settings.js @@ -1,23 +1,66 @@ -const SettingsService = require('../../services/settings'); +const Setting = require('../../models/setting'); +const Settings = require('../../services/settings'); const DataLoader = require('dataloader'); +const { zipObject } = require('lodash'); /** - * Creates a set of loaders based on a GraphQL context. - * @param {Object} context the context of the GraphQL request - * @return {Object} object of loaders + * SettingsLoader manages loading specific fields only of the Settings object. */ -module.exports = () => { - const loader = new DataLoader(selections => - Promise.all( - selections.map(fields => { - return SettingsService.retrieve(fields); - }) - ) - ); +class SettingsLoader { + constructor() { + this._loader = new DataLoader(this._batchLoadFn.bind(this)); + this._cache = null; + } - return { - Settings: { - load: (fields = false) => loader.load(fields), - }, - }; -}; + async _batchLoadFn(fields) { + // Load a settings object with all the requested fields, unless we have the + // entire object cached, in which case we'll return the whole cache. + const model = this._cache + ? await this._cache + : await Settings.retrieve(...fields); + + // Convert the model into an object for easier manipulation. + const obj = model.toObject(); + + // Return the specific fields for each of the fields that were loaded. + return fields.map(field => obj[field]); + } + + /** + * load will return the entire Settings object with all fields. + */ + load() { + if (this._cache) { + // Return the cached settings promise. + return this._cache; + } + + // Create a promise that will return the settings object. + const promise = Settings.retrieve(); + + // Set this as the cached value. + this._cache = promise; + + // Return the promised settings. + return promise; + } + + /** + * select will return a promise which resolves to the Settings object that + * contains the requested fields only. + * + * @param {Array} fields the fields from Settings we want to load. + */ + async select(...fields) { + // Load all the values for the specific fields. + const values = await this._loader.loadMany(fields); + + // Zip up the fields and values to create an object to return. + const obj = zipObject(fields, values); + + // Return the assembled Settings object. + return new Setting(obj); + } +} + +module.exports = () => ({ Settings: new SettingsLoader() }); diff --git a/graph/mutators/user.js b/graph/mutators/user.js index 8657905d9..7487eec2e 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -177,10 +177,10 @@ const changeUserPassword = async (ctx, oldPassword, newPassword) => { await Users.changePassword(user.id, newPassword); // Get some context for the email to be sent. - const { organizationName, organizationContactEmail } = await Settings.load([ + const { organizationName, organizationContactEmail } = await Settings.select( 'organizationName', - 'organizationContactEmail', - ]); + 'organizationContactEmail' + ); // Send the password change email. await Users.sendEmail(user, { diff --git a/graph/resolvers/asset.js b/graph/resolvers/asset.js index 6bdf4d19e..ae5188e6e 100644 --- a/graph/resolvers/asset.js +++ b/graph/resolvers/asset.js @@ -1,4 +1,4 @@ -const { decorateWithTags } = require('./util'); +const { decorateWithTags, getRequestedFields } = require('./util'); const Asset = { async comment({ id }, { id: commentId }, { loaders: { Comments } }) { @@ -64,10 +64,13 @@ const Asset = { return Comments.countByAssetID.load(id); }, - async settings({ settings = null }, _, { loaders: { Settings } }) { + async settings({ settings = null }, _, { loaders: { Settings } }, info) { + // Get the fields we want from the settings. + const fields = getRequestedFields(info); + // Load the global settings, and merge them into the asset specific settings // if we have some. - let globalSettings = await Settings.load(); + let globalSettings = await Settings.select(...fields); if (settings !== null) { settings = Object.assign({}, globalSettings.toObject(), settings); } else { diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js index e562c062c..c40aa49e0 100644 --- a/graph/resolvers/comment.js +++ b/graph/resolvers/comment.js @@ -58,10 +58,14 @@ const Comment = { return Assets.getByID.load(asset_id); }, async editing(comment, _, { loaders: { Settings } }) { - const settings = await Settings.load(); - const editableUntil = new Date( - Number(new Date(comment.created_at)) + settings.editCommentWindowLength + const { editCommentWindowLength } = await Settings.select( + 'editCommentWindowLength' ); + + const editableUntil = new Date( + Number(new Date(comment.created_at)) + editCommentWindowLength + ); + return { edited: comment.edited, editableUntil: editableUntil, diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index 23fdef288..53296a2ca 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -1,4 +1,4 @@ -const { decorateWithPermissionCheck } = require('./util'); +const { decorateWithPermissionCheck, getRequestedFields } = require('./util'); const { SEARCH_ASSETS, SEARCH_OTHERS_COMMENTS, @@ -16,8 +16,12 @@ const RootQuery = { return Assets.getByURL(query.url); }, - settings(_, args, { loaders: { Settings } }) { - return Settings.load(); + settings(_, args, { loaders: { Settings } }, info) { + // Get the fields we want from the settings. + const fields = getRequestedFields(info); + + // Load only the requested fields. + return Settings.select(...fields); }, // This endpoint is used for loading moderation queues, so hide it in the diff --git a/graph/resolvers/util.js b/graph/resolvers/util.js index cec2a71dd..a1a745ab2 100644 --- a/graph/resolvers/util.js +++ b/graph/resolvers/util.js @@ -2,7 +2,8 @@ const { ADD_COMMENT_TAG, SEARCH_OTHER_USERS, } = require('../../perms/constants'); -const { property, isBoolean } = require('lodash'); +const { property, isBoolean, pull } = require('lodash'); +const graphqlFields = require('graphql-fields'); /** * getResolver will get the resolver from the typeResolver or apply the default @@ -207,7 +208,11 @@ const decorateWithTags = ( }; }; +const getRequestedFields = info => + pull(Object.keys(graphqlFields(info)), '__typename'); + module.exports = { + getRequestedFields, decorateUserField, decorateWithTags, decorateWithPermissionCheck, diff --git a/middleware/staticTemplate.js b/middleware/staticTemplate.js index 0dc05a7be..f16caaea9 100644 --- a/middleware/staticTemplate.js +++ b/middleware/staticTemplate.js @@ -95,7 +95,7 @@ const createResolveFactory = (() => { module.exports = async (req, res, next) => { try { // Attach the custom css url. - const { customCssUrl } = await SettingsService.retrieve('customCssUrl'); + const { customCssUrl } = await SettingsService.select('customCssUrl'); res.locals.customCssUrl = customCssUrl; } catch (err) { console.warn(err); diff --git a/models/schema/asset.js b/models/schema/asset.js index 043bc9a3f..82de92120 100644 --- a/models/schema/asset.js +++ b/models/schema/asset.js @@ -43,8 +43,7 @@ const Asset = new Schema( modified_date: Date, // This object is used exclusively for storing settings that are to override - // the base settings from the base Settings object. This is to be accessed - // always after running `rectifySettings` against it. + // the base settings from the base Settings object. settings: { default: {}, type: Object, diff --git a/models/schema/setting.js b/models/schema/setting.js index af7932910..eb9dc76d1 100644 --- a/models/schema/setting.js +++ b/models/schema/setting.js @@ -125,21 +125,4 @@ const Setting = new Schema( } ); -/** - * Merges two settings objects. - */ -Setting.method('merge', function(src) { - Setting.eachPath(path => { - // Exclude internal fields... - if (['id', '_id', '__v', 'created_at', 'updated_at'].includes(path)) { - return; - } - - // If the source object contains the path, shallow copy it. - if (path in src) { - this[path] = src[path]; - } - }); -}); - module.exports = Setting; diff --git a/package.json b/package.json index 1127dd05c..59ceceb95 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,7 @@ "graphql-ast-tools": "0.2.3", "graphql-docs": "0.2.0", "graphql-errors": "^2.1.0", + "graphql-fields": "^1.0.2", "graphql-redis-subscriptions": "1.3.0", "graphql-subscriptions": "^0.4.3", "graphql-tag": "^1.2.3", diff --git a/plugins/talk-plugin-local-auth/server/mutators.js b/plugins/talk-plugin-local-auth/server/mutators.js index 389574d2d..385d84c91 100644 --- a/plugins/talk-plugin-local-auth/server/mutators.js +++ b/plugins/talk-plugin-local-auth/server/mutators.js @@ -47,9 +47,9 @@ async function updateUserEmailAddress(ctx, email, confirmPassword) { ); // Get some context for the email to be sent. - const { organizationContactEmail } = await Settings.load([ - 'organizationContactEmail', - ]); + const { organizationContactEmail } = await Settings.select( + 'organizationContactEmail' + ); // Send off the email to the old email address that we have changed it. await Mailer.send({ diff --git a/plugins/talk-plugin-notifications/server/util.js b/plugins/talk-plugin-notifications/server/util.js index 7198df1e1..bbebef4b2 100644 --- a/plugins/talk-plugin-notifications/server/util.js +++ b/plugins/talk-plugin-notifications/server/util.js @@ -3,7 +3,7 @@ const getOrganizationName = async ctx => { const { loaders: { Settings } } = ctx; // Get the settings. - const { organizationName = null } = await Settings.load('organizationName'); + const { organizationName = null } = await Settings.select('organizationName'); return organizationName; }; diff --git a/plugins/talk-plugin-profile-data/server/connect.js b/plugins/talk-plugin-profile-data/server/connect.js index 0e5284e57..676d67176 100644 --- a/plugins/talk-plugin-profile-data/server/connect.js +++ b/plugins/talk-plugin-profile-data/server/connect.js @@ -36,10 +36,10 @@ module.exports = connectors => { const { organizationName, organizationContactEmail, - } = await Settings.load([ + } = await Settings.select( 'organizationName', - 'organizationContactEmail', - ]); + 'organizationContactEmail' + ); // rescheduledDeletionDate is the date in the future that we'll set the // user's account to be deleted on if this delete fails. diff --git a/plugins/talk-plugin-profile-data/server/mutators.js b/plugins/talk-plugin-profile-data/server/mutators.js index eec54772c..4a1e97791 100644 --- a/plugins/talk-plugin-profile-data/server/mutators.js +++ b/plugins/talk-plugin-profile-data/server/mutators.js @@ -81,7 +81,7 @@ async function sendDownloadLink(ctx) { // Generate the download links. const { downloadLandingURL } = await generateDownloadLinks(ctx, user.id); - const { organizationName } = await Settings.load('organizationName'); + const { organizationName } = await Settings.select('organizationName'); // Send the download link via the user's attached email account. await Users.sendEmail(user, { @@ -130,7 +130,7 @@ async function requestDeletion({ } ); - const { organizationName } = await Settings.load('organizationName'); + const { organizationName } = await Settings.select('organizationName'); // Send the download link via the user's attached email account. await Users.sendEmail(user, { @@ -171,7 +171,7 @@ async function cancelDeletion({ { $unset: { 'metadata.scheduledDeletionDate': 1 } } ); - const { organizationName } = await Settings.load('organizationName'); + const { organizationName } = await Settings.select('organizationName'); // Send the download link via the user's attached email account. await Users.sendEmail(user, { diff --git a/services/assets.js b/services/assets.js index fbed22287..794145eb3 100644 --- a/services/assets.js +++ b/services/assets.js @@ -1,13 +1,12 @@ const CommentModel = require('../models/comment'); const AssetModel = require('../models/asset'); -const SettingsService = require('./settings'); +const Settings = require('./settings'); const DomainList = require('./domain_list'); const { ErrAssetURLAlreadyExists, ErrNotFound, ErrInvalidAssetURL, } = require('../errors'); -const { merge, isEmpty } = require('lodash'); const { dotize } = require('./utils'); module.exports = class AssetsService { @@ -27,28 +26,6 @@ module.exports = class AssetsService { return AssetModel.findOne({ url }); } - /** - * Retrieves the settings given an asset query and rectifies it against the - * global settings. - * @param {Promise} assetQuery an asset query that returns a single asset. - * @return {Promise} - */ - static async rectifySettings(assetQuery, settings = null) { - const [globalSettings, asset] = await Promise.all([ - settings !== null ? settings : SettingsService.retrieve(), - assetQuery, - ]); - - // If the asset exists and has settings then return the merged object. - if (asset && asset.settings && !isEmpty(asset.settings)) { - settings = merge({}, globalSettings, asset.settings); - } else { - settings = globalSettings; - } - - return settings; - } - /** * Finds a asset by its url. * @@ -65,13 +42,13 @@ module.exports = class AssetsService { // Check the URL to confirm that is in the domain whitelist return Promise.all([ DomainList.urlCheck(url), - SettingsService.retrieve(), - ]).then(([whitelisted, settings]) => { + Settings.select('autoCloseStream', 'closedTimeout'), + ]).then(([whitelisted, { autoCloseStream, closedTimeout }]) => { const update = { $setOnInsert: { url } }; - if (settings.autoCloseStream) { + if (autoCloseStream) { update.$setOnInsert.closedAt = new Date( - Date.now() + settings.closedTimeout * 1000 + Date.now() + closedTimeout * 1000 ); } @@ -139,10 +116,10 @@ module.exports = class AssetsService { * @return {Promise} */ static search({ value, limit, open, sortOrder, cursor } = {}) { - let assets = AssetModel.find({}); + let query = AssetModel.find({}); if (value && value.length > 0) { - assets.merge({ + query.merge({ $text: { $search: value, }, @@ -151,7 +128,7 @@ module.exports = class AssetsService { if (open != null) { if (open) { - assets.merge({ + query.merge({ $or: [ { closedAt: null, @@ -164,7 +141,7 @@ module.exports = class AssetsService { ], }); } else { - assets.merge({ + query.merge({ closedAt: { $lt: Date.now(), }, @@ -174,13 +151,13 @@ module.exports = class AssetsService { if (cursor) { if (sortOrder === 'DESC') { - assets.merge({ + query.merge({ created_at: { $lt: cursor, }, }); } else { - assets.merge({ + query.merge({ created_at: { $gt: cursor, }, @@ -188,7 +165,7 @@ module.exports = class AssetsService { } } - return assets + return query .sort({ created_at: sortOrder === 'DESC' ? -1 : 1 }) .limit(limit); } diff --git a/services/cache.js b/services/cache.js index c413537a7..64f8ad934 100644 --- a/services/cache.js +++ b/services/cache.js @@ -271,69 +271,3 @@ cache.set = async (key, value, expiry, kf = keyfunc) => { return cache.client.set(kf(key), reply, 'EX', expiry); }; - -/** - * h is the hash form of the cache. - */ -cache.h = {}; - -cache.h.get = async (key, field = '__default__') => { - // Get the current value from redis. - const reply = await cache.client.hget(keyfunc(key), field); - - if (typeof reply !== 'undefined' && reply !== null) { - return JSON.parse(reply); - } - - return null; -}; - -cache.h.set = async (key, field = '__default__', value, expiry = 60) => { - // Serialize the value as JSON. - let reply = JSON.stringify(value); - - return cache.client - .pipeline() - .hset(keyfunc(key), field, reply) - .expire(keyfunc(key), expiry) - .exec(); -}; - -cache.h.invalidate = async (key, field = null) => { - if (field === null) { - return cache.invalidate(key); - } - - debug(`invalidate: ${keyfunc(key)} ${field}`); - - return cache.client.hdel(keyfunc(key), field); -}; - -cache.h.wrap = async (key, field, expiry, work) => { - let value = await cache.h.get(key, field); - if (value !== null) { - debug('wrap: hit', keyfunc(key)); - return value; - } - - debug('wrap: miss', keyfunc(key)); - - value = await work(); - - process.nextTick(async () => { - try { - await cache.h.set(key, field, value, expiry); - debug('wrap: set complete'); - } catch (err) { - console.error(err); - } - }); - - return value; -}; - -cache.h.incr = async (key, field = '__default__', expiry) => - cache.client.hincrbyex(keyfunc(key), field, 1, expiry); - -cache.h.decr = async (key, field = '__default__', expiry) => - cache.client.hincrbyex(keyfunc(key), field, -1, expiry); diff --git a/services/comments.js b/services/comments.js index b9d73d91c..4b436b5f4 100644 --- a/services/comments.js +++ b/services/comments.js @@ -100,7 +100,7 @@ module.exports = { // original query. const { editCommentWindowLength: editWindowMs, - } = await SettingsService.retrieve(); + } = await SettingsService.select('editCommentWindowLength'); const lastEditableCommentCreatedAt = new Date(Date.now() - editWindowMs); query.created_at = { $gt: lastEditableCommentCreatedAt, diff --git a/services/domain_list.js b/services/domain_list.js index aab1f9ea8..b194305b1 100644 --- a/services/domain_list.js +++ b/services/domain_list.js @@ -1,6 +1,6 @@ const debug = require('debug')('talk:services:domain_list'); const _ = require('lodash'); -const SettingsService = require('./settings'); +const Settings = require('./settings'); const { ROOT_URL } = require('../config'); @@ -19,7 +19,7 @@ class DomainList { * Loads domains white list in from the database */ async load() { - const { domains } = await SettingsService.retrieve(); + const { domains } = await Settings.select('domains'); this.upsert(domains); } diff --git a/services/moderation/index.js b/services/moderation/index.js index 530d6f2a6..27346e620 100644 --- a/services/moderation/index.js +++ b/services/moderation/index.js @@ -1,5 +1,5 @@ const { ErrNotFound } = require('../../errors'); -const get = require('lodash/get'); +const { get, merge, isEmpty } = require('lodash'); // Load in the phases to use. const { @@ -80,10 +80,7 @@ const compose = phases => async (ctx, comment, options) => { * @param {Object} comment comment object to use */ const fetchOptions = async (ctx, comment) => { - const { - connectors: { services: { Assets: AssetsService } }, - loaders: { Settings, Assets }, - } = ctx; + const { loaders: { Settings, Assets } } = ctx; // Load the settings. const settings = await Settings.load(); @@ -102,8 +99,12 @@ const fetchOptions = async (ctx, comment) => { throw new ErrNotFound(); } - // Combine the asset and the settings to get the asset settings. - asset.settings = await AssetsService.rectifySettings(asset, settings); + // If the asset exists and has settings then return the merged object. + if (asset && asset.settings && !isEmpty(asset.settings)) { + asset.settings = merge({}, settings, asset.settings); + } else { + asset.settings = settings; + } // Create the options that will be consumed by the phases. return { diff --git a/services/passport.js b/services/passport.js index 0ae06afb8..e4813e419 100644 --- a/services/passport.js +++ b/services/passport.js @@ -1,7 +1,7 @@ const passport = require('passport'); const { set, get } = require('lodash'); const UsersService = require('./users'); -const SettingsService = require('./settings'); +const Settings = require('./settings'); const TokensService = require('./tokens'); const fetch = require('node-fetch'); const FormData = require('form-data'); @@ -157,7 +157,9 @@ async function ValidateUserLogin(loginProfile, user, done) { } // The user is a local user, check if we need email confirmation. - const { requireEmailConfirmation = false } = await SettingsService.retrieve(); + const { requireEmailConfirmation = false } = await Settings.select( + 'requireEmailConfirmation' + ); // If we have the requirement of checking that emails for users are // verified, then we need to check the email address to ensure that it has diff --git a/services/settings.js b/services/settings.js index ee5125183..34b6fead4 100644 --- a/services/settings.js +++ b/services/settings.js @@ -1,59 +1,61 @@ -const SettingModel = require('../models/setting'); -const cache = require('./cache'); +const Setting = require('../models/setting'); const { ErrSettingsNotInit } = require('../errors'); const { dotize } = require('./utils'); -const { SETTINGS_CACHE_TIME } = require('../config'); +const { isEmpty, zipObject, uniq } = require('lodash'); +const DataLoader = require('dataloader'); -/** - * The selector used to uniquely identify the settings document. - */ const selector = { id: '1' }; -const retrieve = async fields => { - let settings; - if (fields) { - settings = await SettingModel.findOne(selector).select(fields); - } else { - settings = await SettingModel.findOne(selector); - } - if (!settings) { +async function loadFn(fields = []) { + const model = await Setting.findOne(selector).select(uniq(fields)); + if (!model) { throw new ErrSettingsNotInit(); } - return settings; -}; + return model; +} -/** - * The Setting Service object exposing the Setting model. - */ -module.exports = class SettingsService { - /** - * Gets the entire settings record and sends it back - * @return {Promise} settings the whole settings record - */ - static async retrieve(fields) { - if (process.env.NODE_ENV === 'production') { - // When in production, wrap the settings retrieval with a cache. - const settings = await cache.h.wrap( - 'settings', - fields, - SETTINGS_CACHE_TIME / 1000, - () => retrieve(fields) - ); +// batchLoadFn will load a settings object with all the requested fields. +async function batchLoadFn(fields) { + // Load a settings object with all the requested fields. + const model = await loadFn(fields); - return new SettingModel(settings); + // Convert the model into an object for easier manipulation. + const obj = model.toObject(); + + // Return the specific fields for each of the fields that were loaded. + return fields.map(field => obj[field]); +} + +// batchedSettingsLoader will load setting fields for each request. This isn't a +// cached loader, so this is really just for optimizing the requests made to the +// database by batching. +const batchedSettingsLoader = new DataLoader(batchLoadFn, { cache: false }); + +class Settings { + static async retrieve(...fields) { + if (!isEmpty(fields)) { + // Included for backwards compatibility. + return Settings.select(...fields); } - return retrieve(fields); + // Call the loadFn directly if we need to load all the fields. + return loadFn(fields); + } + + static async select(...fields) { + // Load all the values for the specific fields. + const values = await batchedSettingsLoader.loadMany(fields); + + // Zip up the fields and values to create an object to return. + const obj = zipObject(fields, values); + + // Return the assembled Settings object. + return new Setting(obj); } - /** - * This will update the settings object with whatever you pass in - * @param {object} setting a hash of whatever settings you want to update - * @return {Promise} settings Promise that resolves to the entire (updated) settings object. - */ static async update(settings) { - const updatedSettings = await SettingModel.findOneAndUpdate( + const updatedSettings = await Setting.findOneAndUpdate( selector, { $set: dotize(settings), @@ -65,21 +67,16 @@ module.exports = class SettingsService { } ); - if (process.env.NODE_ENV === 'production') { - await cache.h.invalidate('settings'); - } - return updatedSettings; } - /** - * This is run once when the app starts to ensure settings are populated. - */ static init(defaults = {}) { - return SettingsService.retrieve().catch(() => { - let settings = new SettingModel(defaults); + return Settings.retrieve().catch(() => { + const settings = new Setting(defaults); return settings.save(); }); } -}; +} + +module.exports = Settings; diff --git a/services/setup.js b/services/setup.js index 2517d376b..a691550ef 100644 --- a/services/setup.js +++ b/services/setup.js @@ -26,7 +26,7 @@ module.exports = class SetupService { try { // Get the current settings, we are expecting an error here. - await SettingsService.retrieve(); + await SettingsService.select('id'); // We should NOT have gotten a settings object, this means that the // application is already setup. Error out here. diff --git a/services/tags.js b/services/tags.js index cc8934e0a..b4e70cb29 100644 --- a/services/tags.js +++ b/services/tags.js @@ -1,23 +1,24 @@ -const CommentModel = require('../models/comment'); -const AssetModel = require('../models/asset'); -const UserModel = require('../models/user'); -const AssetsService = require('./assets'); -const SettingsService = require('./settings'); +const Comment = require('../models/comment'); +const Asset = require('../models/asset'); +const User = require('../models/user'); +const Assets = require('./assets'); +const Settings = require('./settings'); const { ADD_COMMENT_TAG } = require('../perms/constants'); const { ErrNotAuthorized } = require('../errors'); +const { get, has } = require('lodash'); const updateModel = async (item_type, query, update) => { // Get the model to update with. let Model; switch (item_type) { case 'COMMENTS': - Model = CommentModel; + Model = Comment; break; case 'ASSETS': - Model = AssetModel; + Model = Asset; break; case 'USERS': - Model = UserModel; + Model = User; break; default: throw new Error( @@ -44,32 +45,23 @@ const ownershipQuery = async (item_type, link, query) => { class TagsService { /** - * Retrives a global tag from the settings based on the input_type. + * Retrieves a global tag from the settings based on the input_type. */ static async getAll({ id, item_type, asset_id = null }) { - // Extract the settings from the database. - let settings; - switch (item_type) { - case 'COMMENTS': - settings = await AssetsService.rectifySettings( - AssetsService.findById(asset_id) - ); - break; - case 'ASSETS': - settings = await AssetsService.rectifySettings( - AssetsService.findById(id) - ); - break; - case 'USERS': - settings = await SettingsService.retrieve(); - break; - default: - settings = await SettingsService.retrieve(); - break; + // Optionally get an asset. + let asset; + if (item_type === 'COMMENTS') { + asset = await Assets.findById(asset_id); + } else if (item_type === 'ASSETS') { + asset = await Assets.findById(id); + } + + if (asset && has(asset, 'settings.tags')) { + return get(asset, 'settings.tags'); } // Extract the tags from the settings object. - let { tags = [] } = settings; + const { tags = [] } = await Settings.select('tags'); // Return the first tag that matches the requested form. return tags; diff --git a/services/wordlist.js b/services/wordlist.js index 9e7e1581e..22bde090a 100644 --- a/services/wordlist.js +++ b/services/wordlist.js @@ -1,12 +1,12 @@ const debug = require('debug')('talk:services:wordlist'); const _ = require('lodash'); -const SettingsService = require('./settings'); +const Settings = require('./settings'); const { ErrContainsProfanity } = require('../errors'); const memoize = require('lodash/memoize'); const { escapeRegExp } = require('./regex'); /** - * Generate a regulare expression that catches the `phrases`. + * Generate a regular expression that catches the `phrases`. */ function generateRegExp(phrases) { const inner = phrases @@ -49,7 +49,7 @@ class Wordlist { * Loads wordlists in from the database */ load() { - return SettingsService.retrieve().then(settings => { + return Settings.select('wordlist').then(settings => { // Insert the settings wordlist. this.upsert(settings.wordlist); }); diff --git a/yarn.lock b/yarn.lock index 9a455a9a6..75911aaa8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4451,6 +4451,10 @@ graphql-extensions@^0.0.x: core-js "^2.5.1" source-map-support "^0.5.0" +graphql-fields@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/graphql-fields/-/graphql-fields-1.0.2.tgz#099ee1d4445b42d0f47e06d622acebb33abc6cce" + graphql-redis-subscriptions@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/graphql-redis-subscriptions/-/graphql-redis-subscriptions-1.3.0.tgz#bbc52b0f77bf7d50945c6bf4e8b8aba5135555b4" From 732ff98f9bb9c2e6ecfc7affa1757be8ca2ccfc2 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 7 May 2018 14:48:47 -0600 Subject: [PATCH 002/162] fixed test --- test/server/services/settings.js | 43 +++++++++++++++----------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/test/server/services/settings.js b/test/server/services/settings.js index ff2c898e1..44068e6cd 100644 --- a/test/server/services/settings.js +++ b/test/server/services/settings.js @@ -1,15 +1,13 @@ -const SettingsService = require('../../../services/settings'); +const Settings = require('../../../services/settings'); const chai = require('chai'); const expect = chai.expect; describe('services.SettingsService', () => { - beforeEach(() => - SettingsService.init({ moderation: 'PRE', wordlist: ['donut'] }) - ); + beforeEach(() => Settings.init({ moderation: 'PRE', wordlist: ['donut'] })); describe('#retrieve()', () => { it('should have a moderation field defined', () => { - return SettingsService.retrieve().then(settings => { + return Settings.retrieve().then(settings => { expect(settings) .to.have.property('moderation') .and.to.equal('PRE'); @@ -17,7 +15,7 @@ describe('services.SettingsService', () => { }); it('should have two infoBox fields defined', () => { - return SettingsService.retrieve().then(settings => { + return Settings.retrieve().then(settings => { expect(settings) .to.have.property('infoBoxEnable') .and.to.equal(false); @@ -28,6 +26,17 @@ describe('services.SettingsService', () => { }); }); + describe('#select()', () => { + it('should have a moderation field defined and not wordlist', () => { + return Settings.select('moderation').then(settings => { + expect(settings) + .to.have.property('moderation') + .and.to.equal('PRE'); + expect(settings).to.not.have.property('wordlist'); + }); + }); + }); + describe('#update()', () => { it('should update the settings with a passed object', () => { const mockSettings = { @@ -35,7 +44,7 @@ describe('services.SettingsService', () => { infoBoxEnable: true, infoBoxContent: 'yeah', }; - return SettingsService.update(mockSettings).then(updatedSettings => { + return Settings.update(mockSettings).then(updatedSettings => { expect(updatedSettings).to.be.an('object'); expect(updatedSettings) .to.have.property('moderation') @@ -51,32 +60,20 @@ describe('services.SettingsService', () => { infoBoxEnable: true, infoBoxContent: 'yeah', }; - await SettingsService.update(mockSettings); + await Settings.update(mockSettings); - const settings = await SettingsService.retrieve(); + const settings = await Settings.retrieve(); settings.charCount = 500; - await SettingsService.update(settings.toObject()); + await Settings.update(settings.toObject()); }); }); describe('#get', () => { it('should return the moderation settings', () => { - return SettingsService.retrieve().then(({ moderation }) => { + return Settings.retrieve().then(({ moderation }) => { expect(moderation).not.to.be.null; }); }); }); - - describe('#merge', () => { - it('should merge a settings object and its overrides', () => { - return SettingsService.retrieve().then(settings => { - let ovrSett = { moderation: 'POST' }; - - settings.merge(ovrSett); - - expect(settings).to.have.property('moderation', 'POST'); - }); - }); - }); }); From 15c3a96291ded237262d0b8f82f7ff5122b2ccac Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 7 May 2018 17:16:52 -0600 Subject: [PATCH 003/162] fixed settings --- graph/loaders/settings.js | 16 +++++----------- services/settings.js | 15 +++++---------- test/server/services/settings.js | 19 ++++++++++++++++--- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/graph/loaders/settings.js b/graph/loaders/settings.js index a1329b619..8b07d712b 100644 --- a/graph/loaders/settings.js +++ b/graph/loaders/settings.js @@ -1,4 +1,3 @@ -const Setting = require('../../models/setting'); const Settings = require('../../services/settings'); const DataLoader = require('dataloader'); const { zipObject } = require('lodash'); @@ -15,12 +14,9 @@ class SettingsLoader { async _batchLoadFn(fields) { // Load a settings object with all the requested fields, unless we have the // entire object cached, in which case we'll return the whole cache. - const model = this._cache + const obj = this._cache ? await this._cache - : await Settings.retrieve(...fields); - - // Convert the model into an object for easier manipulation. - const obj = model.toObject(); + : await Settings.select(...fields); // Return the specific fields for each of the fields that were loaded. return fields.map(field => obj[field]); @@ -55,11 +51,9 @@ class SettingsLoader { // Load all the values for the specific fields. const values = await this._loader.loadMany(fields); - // Zip up the fields and values to create an object to return. - const obj = zipObject(fields, values); - - // Return the assembled Settings object. - return new Setting(obj); + // Zip up the fields and values to create an object to return and return the + // assembled Settings object. + return zipObject(fields, values); } } diff --git a/services/settings.js b/services/settings.js index 34b6fead4..def42187a 100644 --- a/services/settings.js +++ b/services/settings.js @@ -12,16 +12,13 @@ async function loadFn(fields = []) { throw new ErrSettingsNotInit(); } - return model; + return model.toObject(); } // batchLoadFn will load a settings object with all the requested fields. async function batchLoadFn(fields) { // Load a settings object with all the requested fields. - const model = await loadFn(fields); - - // Convert the model into an object for easier manipulation. - const obj = model.toObject(); + const obj = await loadFn(fields); // Return the specific fields for each of the fields that were loaded. return fields.map(field => obj[field]); @@ -47,11 +44,9 @@ class Settings { // Load all the values for the specific fields. const values = await batchedSettingsLoader.loadMany(fields); - // Zip up the fields and values to create an object to return. - const obj = zipObject(fields, values); - - // Return the assembled Settings object. - return new Setting(obj); + // Zip up the fields and values to create an object to return and return the + // assembled Settings object. + return zipObject(fields, values); } static async update(settings) { diff --git a/test/server/services/settings.js b/test/server/services/settings.js index 44068e6cd..ca6fdef8d 100644 --- a/test/server/services/settings.js +++ b/test/server/services/settings.js @@ -2,8 +2,13 @@ const Settings = require('../../../services/settings'); const chai = require('chai'); const expect = chai.expect; -describe('services.SettingsService', () => { - beforeEach(() => Settings.init({ moderation: 'PRE', wordlist: ['donut'] })); +describe('services.Settings', () => { + beforeEach(() => + Settings.init({ + moderation: 'PRE', + wordlist: { banned: ['bannedWord'], suspect: [] }, + }) + ); describe('#retrieve()', () => { it('should have a moderation field defined', () => { @@ -35,6 +40,14 @@ describe('services.SettingsService', () => { expect(settings).to.not.have.property('wordlist'); }); }); + it('should have a wordlist field defined and not moderation', () => { + return Settings.select('wordlist').then(settings => { + expect(settings).to.not.have.property('moderation'); + expect(settings).to.have.property('wordlist'); + expect(settings.wordlist).to.have.property('banned'); + expect(settings.wordlist.banned).to.contain('bannedWord'); + }); + }); }); describe('#update()', () => { @@ -65,7 +78,7 @@ describe('services.SettingsService', () => { const settings = await Settings.retrieve(); settings.charCount = 500; - await Settings.update(settings.toObject()); + await Settings.update(settings); }); }); From a7c9f0112590f68c385247446c8b71e6b20e4d51 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 7 May 2018 17:59:43 -0600 Subject: [PATCH 004/162] fixed bug --- graph/resolvers/asset.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/resolvers/asset.js b/graph/resolvers/asset.js index ae5188e6e..3ed5305b7 100644 --- a/graph/resolvers/asset.js +++ b/graph/resolvers/asset.js @@ -72,9 +72,9 @@ const Asset = { // if we have some. let globalSettings = await Settings.select(...fields); if (settings !== null) { - settings = Object.assign({}, globalSettings.toObject(), settings); + settings = Object.assign({}, globalSettings, settings); } else { - settings = globalSettings.toObject(); + settings = globalSettings; } return settings; From 2f94674b78dce39565a5813d0f44cdd508cbceae Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 8 May 2018 11:08:46 -0300 Subject: [PATCH 005/162] Adding CommentDeletedTombstone --- .../src/components/CommentAnimatedEdit.js | 2 +- .../components/CommentDeletedTombstone.css | 5 + .../src/components/CommentDeletedTombstone.js | 8 ++ .../src/components/UserDetailComment.js | 87 +++++++------ .../routes/Moderation/components/Comment.js | 83 ++++++------ .../components/ChangeUsernameDialog.css | 84 ++++++++++++ .../client/components/ChangeUsernameDialog.js | 120 ++++++++++++++++++ 7 files changed, 308 insertions(+), 81 deletions(-) create mode 100644 client/coral-admin/src/components/CommentDeletedTombstone.css create mode 100644 client/coral-admin/src/components/CommentDeletedTombstone.js create mode 100644 plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.css create mode 100644 plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.js diff --git a/client/coral-admin/src/components/CommentAnimatedEdit.js b/client/coral-admin/src/components/CommentAnimatedEdit.js index 12280b204..f43cb9c89 100644 --- a/client/coral-admin/src/components/CommentAnimatedEdit.js +++ b/client/coral-admin/src/components/CommentAnimatedEdit.js @@ -29,7 +29,7 @@ const CommentAnimatedEdit = ({ children, body }) => { CommentAnimatedEdit.propTypes = { children: PropTypes.node, - body: PropTypes.string, + body: PropTypes.string.isRequired, }; export default CommentAnimatedEdit; diff --git a/client/coral-admin/src/components/CommentDeletedTombstone.css b/client/coral-admin/src/components/CommentDeletedTombstone.css new file mode 100644 index 000000000..81f5ddf7a --- /dev/null +++ b/client/coral-admin/src/components/CommentDeletedTombstone.css @@ -0,0 +1,5 @@ +.tombstone { + background-color: #f0f0f0; + padding: 1em; + color: #1a212f; +} \ No newline at end of file diff --git a/client/coral-admin/src/components/CommentDeletedTombstone.js b/client/coral-admin/src/components/CommentDeletedTombstone.js new file mode 100644 index 000000000..db9a609b1 --- /dev/null +++ b/client/coral-admin/src/components/CommentDeletedTombstone.js @@ -0,0 +1,8 @@ +import React from 'react'; +import styles from './CommentDeletedTombstone.css'; + +const CommentDeletedTombstone = () => ( +
The comment was deleted.
+); + +export default CommentDeletedTombstone; diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index f2ff2e932..f7a500203 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -12,6 +12,7 @@ import CommentAnimatedEdit from './CommentAnimatedEdit'; import CommentLabels from '../containers/CommentLabels'; import ApproveButton from './ApproveButton'; import RejectButton from 'coral-admin/src/components/RejectButton'; +import CommentDeletedTombstone from './CommentDeletedTombstone'; import t, { timeago } from 'coral-framework/services/i18n'; @@ -83,49 +84,53 @@ class UserDetailComment extends React.Component { } - -
- -
- - - {/* TODO: translate string */} - Contains Link - - -
- - +
+ +
+ + + {/* TODO: translate string */} + Contains Link + + +
+ + +
-
- + + ) : ( + + )}
@@ -152,7 +157,7 @@ UserDetailComment.propTypes = { comment: PropTypes.shape({ id: PropTypes.string.isRequired, status: PropTypes.string.isRequired, - body: PropTypes.string.isRequired, + body: PropTypes.string, actions: PropTypes.array, created_at: PropTypes.string.isRequired, asset: PropTypes.shape({ diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index 28dcf8ca8..137bd9381 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -13,6 +13,7 @@ import IfHasLink from 'coral-admin/src/components/IfHasLink'; import cn from 'classnames'; import ApproveButton from 'coral-admin/src/components/ApproveButton'; import RejectButton from 'coral-admin/src/components/RejectButton'; +import CommentDeletedTombstone from '../../../components/CommentDeletedTombstone'; import t, { timeago } from 'coral-framework/services/i18n'; @@ -133,48 +134,52 @@ class Comment extends React.Component { )}
- -
-
- -
- - {t('comment.view_context')} - + {comment.body ? ( + + -
- - - {/* TODO: translate string */} - Contains Link - - -
- - +
+ + + {/* TODO: translate string */} + Contains Link + + +
+ + +
+
-
-
-
+ + ) : ( + + )}
{ + this.setState({ + showError: true, + }); + }; + + confirmChanges = async () => { + if (this.formHasError()) { + this.showError(); + return; + } + + if (!this.props.canUsernameBeUpdated) { + this.props.notify( + 'error', + t('talk-plugin-local-auth.change_username.change_username_attempt') + ); + return; + } + + await this.props.saveChanges(); + this.props.closeDialog(); + }; + + formHasError = () => + this.props.formData.confirmNewUsername !== this.props.formData.newUsername; + + render() { + return ( + + + × + +

+ {t('talk-plugin-local-auth.change_username.confirm_username_change')} +

+
+

+ {t('talk-plugin-local-auth.change_username.description')} +

+
+ + {t('talk-plugin-local-auth.change_username.old_username')}:{' '} + {this.props.username} + + + {t('talk-plugin-local-auth.change_username.new_username')}:{' '} + {this.props.formData.newUsername} + +
+
+ + + {t('talk-plugin-local-auth.change_username.bottom_note')} + + +
+
+ + +
+
+
+ ); + } +} + +ChangeUsernameDialog.propTypes = { + saveChanges: PropTypes.func, + closeDialog: PropTypes.func, + showDialog: PropTypes.bool, + onChange: PropTypes.func, + username: PropTypes.string, + formData: PropTypes.object, + canUsernameBeUpdated: PropTypes.bool.isRequired, + notify: PropTypes.func.isRequired, +}; + +export default ChangeUsernameDialog; From e9c18faf32bd9641f30bc42d3801d272455bf2de Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 8 May 2018 11:30:33 -0300 Subject: [PATCH 006/162] Deleted Comment display in Queues --- .../src/components/CommentDeletedTombstone.js | 3 +- .../src/components/UserDetailComment.js | 97 ++++++++-------- .../routes/Moderation/components/Comment.css | 4 + .../routes/Moderation/components/Comment.js | 105 ++++++++++-------- 4 files changed, 120 insertions(+), 89 deletions(-) diff --git a/client/coral-admin/src/components/CommentDeletedTombstone.js b/client/coral-admin/src/components/CommentDeletedTombstone.js index db9a609b1..c04e97b88 100644 --- a/client/coral-admin/src/components/CommentDeletedTombstone.js +++ b/client/coral-admin/src/components/CommentDeletedTombstone.js @@ -1,8 +1,9 @@ import React from 'react'; import styles from './CommentDeletedTombstone.css'; +import t from 'coral-framework/services/i18n'; const CommentDeletedTombstone = () => ( -
The comment was deleted.
+
{t('framework.comment_is_deleted')}
); export default CommentDeletedTombstone; diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index f7a500203..41fb87b28 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -44,6 +44,19 @@ class UserDetailComment extends React.Component { body: comment.body, }; + if (!comment.body) { + return ( +
  • + +
  • + ); + } + return (
  • }
  • - {comment.body ? ( - -
    -
    - +
    + +
    + + + {/* TODO: translate string */} + Contains Link + + + -
    - - - {/* TODO: translate string */} - Contains Link - - -
    - - -
    - - ) : ( - - )} +
    +
    diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.css b/client/coral-admin/src/routes/Moderation/components/Comment.css index 2dc03a503..0f8f91a97 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.css +++ b/client/coral-admin/src/routes/Moderation/components/Comment.css @@ -85,6 +85,10 @@ font-weight: 300; } +.deleted { + background-color: #f0f0f0; +} + .moderateArticle { font-size: 14px; margin: 10px 0; diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index 137bd9381..97cdf9443 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -76,6 +76,27 @@ class Comment extends React.Component { asset: comment.asset, }; + if (!comment.body) { + return ( +
  • + +
  • + ); + } + return (
  • )}
  • - {comment.body ? ( - -
    - - -
    - - - {/* TODO: translate string */} - Contains Link - - -
    - - -
    - + +
    + - - ) : ( - - )} + +
    + + + {/* TODO: translate string */} + Contains Link + + +
    + + +
    + +
    +
    +
    Date: Tue, 8 May 2018 11:33:39 -0300 Subject: [PATCH 007/162] removing extra --- .../components/ChangeUsernameDialog.css | 84 ------------ .../client/components/ChangeUsernameDialog.js | 120 ------------------ 2 files changed, 204 deletions(-) delete mode 100644 plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.css delete mode 100644 plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.js diff --git a/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.css b/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.css deleted file mode 100644 index af681d596..000000000 --- a/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.css +++ /dev/null @@ -1,84 +0,0 @@ -.dialog { - border: none; - box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); - width: 320px; - top: 10px; - font-family: Helvetica, 'Helvetica Neue', Verdana, sans-serif; - font-size: 14px; - border-radius: 4px; - padding: 12px 20px; -} - -.close { - font-size: 20px; - line-height: 14px; - top: 10px; - right: 10px; - position: absolute; - display: block; - font-weight: bold; - color: #363636; - cursor: pointer; - - &:hover { - color: #6b6b6b; - } -} - -.title { - font-size: 1.3em; - margin-bottom: 8px; -} - -.description { - font-size: 1em; - line-height: 20px; - margin: 0; -} - -.item { - display: block; - color: #4C4C4D; - font-size: 1em; - margin-bottom: 2px; -} - -.bottomNote { - font-size: 0.9em; - line-height: 20px; - padding-top: 10px; - display: block; -} - -.bottomActions { - text-align: right; -} - -.usernamesChange { - margin: 18px 0; -} - -.cancel { - border: 1px solid #787d80; - background-color: transparent; - height: 30px; - font-size: 0.9em; - line-height: normal; - - &:hover { - background-color: #eaeaea; - } -} - -.confirmChanges { - background-color: #3498DB; - border-color: #3498DB; - color: white; - height: 30px; - font-size: 0.9em; - - &:hover { - background-color: #3ba3ec; - color: white; - } -} \ No newline at end of file diff --git a/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.js b/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.js deleted file mode 100644 index 097168b06..000000000 --- a/plugins/talk-plugin-local-auth/client/components/ChangeUsernameDialog.js +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cn from 'classnames'; -import styles from './ChangeUsernameDialog.css'; -import InputField from './InputField'; -import { Button, Dialog } from 'plugin-api/beta/client/components/ui'; -import { t } from 'plugin-api/beta/client/services'; - -class ChangeUsernameDialog extends React.Component { - state = { - showError: false, - }; - - showError = () => { - this.setState({ - showError: true, - }); - }; - - confirmChanges = async () => { - if (this.formHasError()) { - this.showError(); - return; - } - - if (!this.props.canUsernameBeUpdated) { - this.props.notify( - 'error', - t('talk-plugin-local-auth.change_username.change_username_attempt') - ); - return; - } - - await this.props.saveChanges(); - this.props.closeDialog(); - }; - - formHasError = () => - this.props.formData.confirmNewUsername !== this.props.formData.newUsername; - - render() { - return ( - - - × - -

    - {t('talk-plugin-local-auth.change_username.confirm_username_change')} -

    -
    -

    - {t('talk-plugin-local-auth.change_username.description')} -

    -
    - - {t('talk-plugin-local-auth.change_username.old_username')}:{' '} - {this.props.username} - - - {t('talk-plugin-local-auth.change_username.new_username')}:{' '} - {this.props.formData.newUsername} - -
    -
    - - - {t('talk-plugin-local-auth.change_username.bottom_note')} - - -
    -
    - - -
    -
    -
    - ); - } -} - -ChangeUsernameDialog.propTypes = { - saveChanges: PropTypes.func, - closeDialog: PropTypes.func, - showDialog: PropTypes.bool, - onChange: PropTypes.func, - username: PropTypes.string, - formData: PropTypes.object, - canUsernameBeUpdated: PropTypes.bool.isRequired, - notify: PropTypes.func.isRequired, -}; - -export default ChangeUsernameDialog; From 231dfe7997545f65425a213ce4c14e404f527b46 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 09:32:14 -0600 Subject: [PATCH 008/162] Fixes for notifications --- .../index.js | 56 +++++++++++++------ .../server/NotificationManager.js | 5 +- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/plugins/talk-plugin-notifications-category-reply/index.js b/plugins/talk-plugin-notifications-category-reply/index.js index cce258daa..627d50ff8 100644 --- a/plugins/talk-plugin-notifications-category-reply/index.js +++ b/plugins/talk-plugin-notifications-category-reply/index.js @@ -1,17 +1,23 @@ const { get, map } = require('lodash'); const path = require('path'); -const handle = async (ctx, comment) => { +const commentAddedHandler = async (ctx, comment) => { // Check to see if this reply is visible. if (!comment.visible) { - ctx.log.info('comment was not visible, not sending notification'); + ctx.log.info( + { commentID: comment.id }, + 'comment was not visible, not sending notification' + ); return; } // Check to see if this is a reply to an existing comment. const parentID = get(comment, 'parent_id', null); - if (parentID === null) { - ctx.log.info('could not get parent comment id'); + if (!parentID) { + ctx.log.info( + { commentID: comment.id }, + 'could not get parent comment id, comment must be a top level comment' + ); return; } @@ -40,42 +46,60 @@ const handle = async (ctx, comment) => { return; } + const parentComment = get(reply, 'data.comment'); + if (!parentComment) { + ctx.log.info({ parentID }, 'could not get parent comment'); + return; + } + // Check if the user has notifications enabled. const enabled = get( - reply, - 'data.comment.user.notificationSettings.onReply', + parentComment, + 'user.notificationSettings.onReply', false ); if (!enabled) { + ctx.log.error( + 'parent comment author does not have notification category enabled' + ); return; } - const userID = get(reply, 'data.comment.user.id', null); - if (!userID) { - ctx.log.info('could not get parent comment user id'); + const parentAuthor = get(parentComment, 'user', null); + if (!parentAuthor) { + ctx.log.info('could not get parent author'); return; } - // Pull out the author of the new comment. + // Pull out the author of the new comment. This was outputted from Mongo, so + // we have to pull it out of the `author_id` field. const authorID = get(comment, 'author_id'); // Check to see if this is yourself replying to yourself, if that's the case // don't send a notification. - if (userID === authorID) { + if (parentAuthor.id === authorID) { ctx.log.info('user id of parent comment is the same as the new comment'); return; } // Check to see if this user is ignoring the user who replied to their // comment. - if (map(get(comment, 'user.ignoredUsers', []), 'id').indexOf(authorID)) { - ctx.log.info('parent user has ignored the author of the new comment'); + const ignoredUsers = map(get(parentAuthor, 'ignoredUsers', []), 'id'); + if (ignoredUsers.includes(authorID)) { + ctx.log.info( + { parentAuthorID: parentAuthor.id, authorID }, + 'parent user has ignored the author of the new comment' + ); return; } // The user does have notifications for replied comments enabled, queue the // notification to be sent. - return { userID, date: comment.created_at, context: comment.id }; + return { + userID: parentAuthor.id, + date: comment.created_at, + context: comment.id, + }; }; const hydrate = async (ctx, category, context) => { @@ -133,7 +157,7 @@ const commentAcceptedHandleAdapter = (ctx, comment) => { } // Delegate to the handle function. - return handle(ctx, comment); + return commentAddedHandler(ctx, comment); }; module.exports = { @@ -155,7 +179,7 @@ module.exports = { translations: path.join(__dirname, 'translations.yml'), notifications: [ { - handle, + handle: commentAddedHandler, category: 'reply', event: 'commentAdded', hydrate, diff --git a/plugins/talk-plugin-notifications/server/NotificationManager.js b/plugins/talk-plugin-notifications/server/NotificationManager.js index eb90dd48b..63af8efdb 100644 --- a/plugins/talk-plugin-notifications/server/NotificationManager.js +++ b/plugins/talk-plugin-notifications/server/NotificationManager.js @@ -32,7 +32,10 @@ const handleHandlers = (ctx, handlers, ...args) => // Attempt to create a notification out of it. const notification = await handle(ctx, ...args); if (!notification) { - ctx.log.info('no notification deemed by event handler'); + ctx.log.info( + { category, event }, + 'no notification deemed by event handler' + ); return; } From 74dbfda63368538ed9b82131ba0af058d67c0fd6 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 11:53:49 -0600 Subject: [PATCH 009/162] Exclude deleted comments --- .../Moderation/containers/Moderation.js | 2 ++ graph/loaders/comments.js | 34 +++++++++++++------ graph/typeDefs.graphql | 11 +++++- services/comments.js | 12 +++++-- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index f35255732..c908404cb 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -432,6 +432,7 @@ const withModQueueQuery = withQuery( ${Object.keys(queueConfig).map( queue => ` ${queue}: comments(query: { + excludeDeleted: true, statuses: ${ queueConfig[queue].statuses ? `[${queueConfig[queue].statuses.join(', ')}],` @@ -458,6 +459,7 @@ const withModQueueQuery = withQuery( ${Object.keys(queueConfig).map( queue => ` ${queue}Count: commentCount(query: { + excludeDeleted: true, statuses: ${ queueConfig[queue].statuses ? `[${queueConfig[queue].statuses.join(', ')}],` diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index 37d40a219..b42a636bc 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -94,6 +94,7 @@ const getCommentCountByQuery = (ctx, options) => { author_id, tags, action_type, + excludeDeleted, } = options; // If user queries for statuses other than NONE and/or ACCEPTED statuses, it needs @@ -120,6 +121,12 @@ const getCommentCountByQuery = (ctx, options) => { query.merge({ author_id }); } + if (excludeDeleted) { + // The null query matches documents that either contain the `deleted_at` + // field whose value is null or that do not contain the `deleted_at` field. + query.merge({ deleted_at: null }); + } + if (ctx.user != null && ctx.user.can(SEARCH_OTHERS_COMMENTS) && action_type) { query.merge({ [`action_counts.${sc(action_type.toLowerCase())}`]: { @@ -328,11 +335,12 @@ const getCommentsByQuery = async ( sortOrder, sortBy, excludeIgnored, + excludeDeleted, tags, action_type, } ) => { - let comments = CommentModel.find(); + const query = CommentModel.find(); // Enforce that the limit must be gte 0 if this option is not true. if (!ALLOW_NO_LIMIT_QUERIES && limit < 0) { @@ -350,11 +358,17 @@ const getCommentsByQuery = async ( } if (statuses) { - comments = comments.where({ status: { $in: statuses } }); + query.merge({ status: { $in: statuses } }); + } + + if (excludeDeleted) { + // The null query matches documents that either contain the `deleted_at` + // field whose value is null or that do not contain the `deleted_at` field. + query.merge({ deleted_at: null }); } if (ctx.user != null && ctx.user.can(SEARCH_OTHERS_COMMENTS) && action_type) { - comments = comments.where({ + query.merge({ [`action_counts.${sc(action_type.toLowerCase())}`]: { $gt: 0, }, @@ -362,7 +376,7 @@ const getCommentsByQuery = async ( } if (ids) { - comments = comments.find({ + query.merge({ id: { $in: ids, }, @@ -370,7 +384,7 @@ const getCommentsByQuery = async ( } if (tags) { - comments = comments.find({ + query.merge({ 'tags.tag.name': { $in: tags, }, @@ -383,17 +397,17 @@ const getCommentsByQuery = async ( (ctx.user.can(SEARCH_OTHERS_COMMENTS) || ctx.user.id === author_id) && author_id != null ) { - comments = comments.where({ author_id }); + query.merge({ author_id }); } if (asset_id) { - comments = comments.where({ asset_id }); + query.merge({ asset_id }); } // We perform the undefined check because, null, is a valid state for the // search to be with, which indicates that it is at depth 0. if (parent_id !== undefined) { - comments = comments.where({ parent_id }); + query.merge({ parent_id }); } if ( @@ -402,12 +416,12 @@ const getCommentsByQuery = async ( ctx.user.ignoresUsers && ctx.user.ignoresUsers.length > 0 ) { - comments = comments.where({ + query.merge({ author_id: { $nin: ctx.user.ignoresUsers }, }); } - return executeWithSort(ctx, comments, { cursor, sortOrder, sortBy, limit }); + return executeWithSort(ctx, query, { cursor, sortOrder, sortBy, limit }); }; /** diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index b5c193e3d..03c97ce58 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -418,6 +418,9 @@ input CommentsQuery { # Exclude comments ignored by the requesting user excludeIgnored: Boolean + + # excludeDeleted when true will exclude deleted comments from the response. + excludeDeleted: Boolean = false } input RepliesQuery { @@ -434,6 +437,9 @@ input RepliesQuery { # Exclude comments ignored by the requesting user excludeIgnored: Boolean + + # excludeDeleted when true will exclude deleted comments from the response. + excludeDeleted: Boolean = false } # CommentCountQuery allows the ability to query comment counts by specific @@ -463,6 +469,9 @@ input CommentCountQuery { # Filter by a specific tag name. tags: [String!] + + # excludeDeleted when true will exclude deleted comments from the count. + excludeDeleted: Boolean = false } # UserCountQuery allows the ability to query user counts by specific @@ -519,7 +528,7 @@ type Comment { replies(query: RepliesQuery = {}): CommentConnection! # replyCount is the number of replies with a depth of 1. Only direct replies - # to this comment are counted. + # to this comment are counted. Deleted comments are included in this count. replyCount: Int # Actions completed on the parent. Requires the `ADMIN` role. diff --git a/services/comments.js b/services/comments.js index 4b436b5f4..19b611089 100644 --- a/services/comments.js +++ b/services/comments.js @@ -39,9 +39,12 @@ module.exports = { const created_at = new Date(); // Check to see if we are replying to a comment, and if that comment is - // visible. + // visible and that it's not deleted. if (parent_id !== null) { - const parent = await CommentModel.findOne({ id: parent_id }); + const parent = await CommentModel.findOne({ + id: parent_id, + deleted_at: null, + }); if (parent === null || !parent.visible) { throw new ErrParentDoesNotVisible(); } @@ -94,6 +97,7 @@ module.exports = { status: { $in: EDITABLE_STATUSES, }, + deleted_at: null, }; // Establish the edit window (if it exists) and add the condition to the @@ -186,8 +190,10 @@ module.exports = { */ pushStatus: async (id, status, assigned_by = null) => { const created_at = new Date(); + + // Update the comment unless the comment was deleted. const originalComment = await CommentModel.findOneAndUpdate( - { id }, + { id, deleted_at: null }, { $push: { status_history: { From e14af46c68c9a7fcad6fcaa58a4ea4aaa651db85 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 12:02:04 -0600 Subject: [PATCH 010/162] prevent actions against deleted comments --- graph/mutators/action.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/graph/mutators/action.js b/graph/mutators/action.js index d759449b0..5557e1a9b 100644 --- a/graph/mutators/action.js +++ b/graph/mutators/action.js @@ -14,8 +14,15 @@ const getActionItem = async (ctx, { item_id, item_type }) => { const { loaders: { Comments, Users } } = ctx; switch (item_type) { - case 'COMMENTS': - return Comments.get.load(item_id); + case 'COMMENTS': { + // Get a comment by ID, unless the comment is deleted, then return null. + const comment = await Comments.get.load(item_id); + if (comment.deleted_at) { + return null; + } + + return comment; + } case 'USERS': return Users.getByID.load(item_id); default: From 7b86ec7d0a5ae915aa1af68929c822140b1a7320 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 12:06:52 -0600 Subject: [PATCH 011/162] Update translations.yml --- plugins/talk-plugin-profile-data/client/translations.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/talk-plugin-profile-data/client/translations.yml b/plugins/talk-plugin-profile-data/client/translations.yml index e139cfb57..d1fdccb73 100644 --- a/plugins/talk-plugin-profile-data/client/translations.yml +++ b/plugins/talk-plugin-profile-data/client/translations.yml @@ -1,7 +1,7 @@ en: download_request: section_title: "Download My Comment History" - you_will_get_a_copy: "You will recieve an email with a link to download your comment history. You can make" + you_will_get_a_copy: "You will receive an email with a link to download your comment history. You can make" download_rate: "one download request every {0} days" most_recent_request: "Your most recent request" request: "Request Comment History" From a1edbcf974e10986ec15613563b7f00080f66143 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 14:01:04 -0600 Subject: [PATCH 012/162] disallow adding a tag to a deleted comment --- services/tags.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/tags.js b/services/tags.js index b4e70cb29..18cf5869f 100644 --- a/services/tags.js +++ b/services/tags.js @@ -163,6 +163,11 @@ class TagsService { }, }; + if (item_type === 'COMMENT') { + // Don't allow adding tags to deleted comments. + query.deleted_at = null; + } + // If ownership verification is required, ensure that the person that is // assigning the tag is the same person that owns the comment. if (ownershipCheck) { From 17a0297911754e2049453aa9a7ad4878f4252094 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 14:02:47 -0600 Subject: [PATCH 013/162] fixed bug.. --- services/tags.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/services/tags.js b/services/tags.js index 18cf5869f..49d271c0b 100644 --- a/services/tags.js +++ b/services/tags.js @@ -13,6 +13,10 @@ const updateModel = async (item_type, query, update) => { switch (item_type) { case 'COMMENTS': Model = Comment; + + // Don't allow adding tags to deleted comments. + query.deleted_at = null; + break; case 'ASSETS': Model = Asset; @@ -163,11 +167,6 @@ class TagsService { }, }; - if (item_type === 'COMMENT') { - // Don't allow adding tags to deleted comments. - query.deleted_at = null; - } - // If ownership verification is required, ensure that the person that is // assigning the tag is the same person that owns the comment. if (ownershipCheck) { From 8c29b0f1d44326ecbb13a735607f0d3197a881b9 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 15:20:56 -0600 Subject: [PATCH 014/162] fix for delete --- graph/mutators/user.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/graph/mutators/user.js b/graph/mutators/user.js index 8b9afc206..7422f2290 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -70,15 +70,25 @@ const setRole = (ctx, id, role) => { /** * transforms a specific action to a removal action on the target model. */ -const actionDecrTransformer = ({ item_id, action_type, group_id }) => ({ - query: { id: item_id }, - update: { +const actionDecrTransformer = ({ item_id, action_type, group_id }) => { + const update = { $inc: { [`action_counts.${action_type.toLowerCase()}`]: -1, - [`action_counts.${action_type.toLowerCase()}_${group_id.toLowerCase()}`]: -1, }, - }, -}); + }; + + if (group_id) { + // If the action had a groupID, also decrement that key. + update.$inc[ + `action_counts.${action_type.toLowerCase()}_${group_id.toLowerCase()}` + ] = -1; + } + + return { + query: { id: item_id }, + update, + }; +}; // delUser will delete a given user with the specified id. const delUser = async (ctx, id) => { From 1815bd4d75275e8008ecb90f58f51820f463692b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 15:30:12 -0600 Subject: [PATCH 015/162] fix translation copy --- .../client/components/DeleteMyAccount.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js b/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js index bbada134f..179fa0193 100644 --- a/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js +++ b/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js @@ -27,7 +27,7 @@ class DeleteMyAccount extends React.Component { const { cancelAccountDeletion, notify } = this.props; try { await cancelAccountDeletion(); - notify('success', t('delete_request.account_deletion_requested')); + notify('success', t('delete_request.account_deletion_cancelled')); } catch (err) { notify('error', getErrorMessages(err)); } From 7348bfc7c9f8c95fb414c6067efc180049eea888 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 8 May 2018 15:46:24 -0600 Subject: [PATCH 016/162] fixed error --- .../client/containers/IgnoreUserConfirmation.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/plugins/talk-plugin-ignore-user/client/containers/IgnoreUserConfirmation.js b/plugins/talk-plugin-ignore-user/client/containers/IgnoreUserConfirmation.js index 5aa834034..efd4cd366 100644 --- a/plugins/talk-plugin-ignore-user/client/containers/IgnoreUserConfirmation.js +++ b/plugins/talk-plugin-ignore-user/client/containers/IgnoreUserConfirmation.js @@ -10,16 +10,22 @@ import { bindActionCreators } from 'redux'; import { closeMenu } from 'plugins/talk-plugin-author-menu/client/actions'; import { notify } from 'plugin-api/beta/client/actions/notification'; import { t } from 'plugin-api/beta/client/services'; +import { getErrorMessages } from 'coral-framework/utils'; class IgnoreUserConfirmationContainer extends React.Component { - ignoreUser = () => { + ignoreUser = async () => { const { ignoreUser, notify, comment, closeMenu } = this.props; - ignoreUser(comment.user.id).then(() => { + + try { + await ignoreUser(comment.user.id); notify( 'success', t('talk-plugin-ignore-user.notify_success', comment.user.username) ); - }); + } catch (err) { + notify('error', getErrorMessages(err)); + } + closeMenu(); }; From b50fde619e436f9db1ea466c09776fae7199fa42 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 8 May 2018 23:46:33 +0200 Subject: [PATCH 017/162] Pass postDontAgree to replies --- .../src/tabs/stream/components/AllCommentsPane.js | 2 +- .../coral-embed-stream/src/tabs/stream/components/Comment.js | 4 +++- .../coral-embed-stream/src/tabs/stream/containers/Stream.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/stream/components/AllCommentsPane.js b/client/coral-embed-stream/src/tabs/stream/components/AllCommentsPane.js index 9aa2b8a4c..eae72c749 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/AllCommentsPane.js +++ b/client/coral-embed-stream/src/tabs/stream/components/AllCommentsPane.js @@ -214,7 +214,7 @@ AllCommentsPane.propTypes = { asset: PropTypes.object, currentUser: PropTypes.object, postFlag: PropTypes.func, - postDontAgree: PropTypes.func, + postDontAgree: PropTypes.func.isRequired, loadNewReplies: PropTypes.func, deleteAction: PropTypes.func, showSignInDialog: PropTypes.func, diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.js b/client/coral-embed-stream/src/tabs/stream/components/Comment.js index 8b1524839..32778f7ea 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.js @@ -184,7 +184,7 @@ export default class Comment extends React.Component { maxCharCount: PropTypes.number, root: PropTypes.object, loadMore: PropTypes.func, - postDontAgree: PropTypes.func, + postDontAgree: PropTypes.func.isRequired, animateEnter: PropTypes.bool, commentClassNames: PropTypes.array, comment: PropTypes.object.isRequired, @@ -410,6 +410,7 @@ export default class Comment extends React.Component { charCountEnable, showSignInDialog, liveUpdates, + postDontAgree, emit, } = this.props; return ( @@ -440,6 +441,7 @@ export default class Comment extends React.Component { key={reply.id} comment={reply} emit={emit} + postDontAgree={postDontAgree} /> ); })} diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 17d3132ea..f1a973f03 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -265,7 +265,7 @@ StreamContainer.propTypes = { commentClassNames: PropTypes.array, setActiveStreamTab: PropTypes.func, postFlag: PropTypes.func, - postDontAgree: PropTypes.func, + postDontAgree: PropTypes.func.isRequired, deleteAction: PropTypes.func, showSignInDialog: PropTypes.func, currentUser: PropTypes.object, From 8be4300ff4449cfdfa6126cdd4a031afb0b0ad17 Mon Sep 17 00:00:00 2001 From: Clint Brown Date: Wed, 9 May 2018 17:41:16 +1000 Subject: [PATCH 018/162] Fix My Profile story title link behavior --- .../coral-embed-stream/src/tabs/profile/components/Comment.css | 1 + client/coral-embed-stream/src/tabs/profile/components/Comment.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/tabs/profile/components/Comment.css b/client/coral-embed-stream/src/tabs/profile/components/Comment.css index 9004a1e6c..c2bf841d5 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/Comment.css +++ b/client/coral-embed-stream/src/tabs/profile/components/Comment.css @@ -31,6 +31,7 @@ font-weight: bold; font-size: 12px; color: #757575; + cursor: pointer; } .commentSummary { diff --git a/client/coral-embed-stream/src/tabs/profile/components/Comment.js b/client/coral-embed-stream/src/tabs/profile/components/Comment.js index 171172dab..84293096b 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/profile/components/Comment.js @@ -76,7 +76,6 @@ class Comment extends React.Component {
    {t('common.story')}:{' '} From 1517611c9ed4a36c47211effdae0f0a2a5894c13 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Wed, 9 May 2018 11:00:02 -0400 Subject: [PATCH 019/162] Update tombstone copy --- locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.yml b/locales/en.yml index e293fb40b..9a26fbdb6 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -274,7 +274,7 @@ en: comment: comment comment_is_ignored: "This comment is hidden because you ignored this user." comment_is_rejected: "You have rejected this comment." - comment_is_deleted: "This comment was deleted." + comment_is_deleted: "This commenter has deleted their account." comment_is_hidden: "This comment is not available." comments: comments configure_stream: "Configure" From 0ded5c72e98799a4cba0959985e7a8e529593776 Mon Sep 17 00:00:00 2001 From: okbel Date: Wed, 9 May 2018 14:23:22 -0300 Subject: [PATCH 020/162] adding media --- .../client/components/DeleteMyAccountDialog.css | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/talk-plugin-profile-data/client/components/DeleteMyAccountDialog.css b/plugins/talk-plugin-profile-data/client/components/DeleteMyAccountDialog.css index 2735474da..26087c534 100644 --- a/plugins/talk-plugin-profile-data/client/components/DeleteMyAccountDialog.css +++ b/plugins/talk-plugin-profile-data/client/components/DeleteMyAccountDialog.css @@ -1,12 +1,19 @@ +@custom-media --small-viewport (min-width: 425px); + .dialog { + width: calc(100% - 50px); border: none; box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); - width: 380px; + top: 10px; font-family: Helvetica, 'Helvetica Neue', Verdana, sans-serif; font-size: 14px; border-radius: 4px; padding: 20px; + + @media (--small-viewport) { + width: 380px; + } } .close { From 4426be5f397d6529f6dd94cb5915bebc5c37041d Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 9 May 2018 13:56:05 -0600 Subject: [PATCH 021/162] improved dev experience, fixed css order --- client/coral-admin/src/components/App.css | 9 +++ client/coral-admin/src/components/App.js | 1 + middleware/staticTemplate.js | 8 ++- package.json | 1 + public/css/dev.css | 5 ++ routes/dev/assets.js | 45 ++++++++----- routes/dev/index.js | 2 - services/assets.js | 6 -- views/admin.ejs | 9 +-- views/dev/article.ejs | 80 ++++++++++------------- views/dev/articles.ejs | 50 ++++++++++---- views/embed/stream.ejs | 1 + views/login.ejs | 1 + views/partials/account.ejs | 3 +- views/partials/custom-css.ejs | 1 + views/partials/dev-nav.ejs | 8 +++ views/partials/dev.ejs | 5 ++ views/partials/head.ejs | 3 - yarn.lock | 15 +++++ 19 files changed, 158 insertions(+), 95 deletions(-) create mode 100644 client/coral-admin/src/components/App.css create mode 100644 public/css/dev.css create mode 100644 views/partials/custom-css.ejs create mode 100644 views/partials/dev-nav.ejs create mode 100644 views/partials/dev.ejs diff --git a/client/coral-admin/src/components/App.css b/client/coral-admin/src/components/App.css new file mode 100644 index 000000000..1404a9293 --- /dev/null +++ b/client/coral-admin/src/components/App.css @@ -0,0 +1,9 @@ +html, body, #root, #root > div { + min-height: 100%; +} + +body { + margin: 0; + background-color: #FAFAFA; + font-family: 'Roboto', sans-serif; +} diff --git a/client/coral-admin/src/components/App.js b/client/coral-admin/src/components/App.js index 52535f91a..a7ab1e336 100644 --- a/client/coral-admin/src/components/App.js +++ b/client/coral-admin/src/components/App.js @@ -1,5 +1,6 @@ import React from 'react'; import ToastContainer from './ToastContainer'; +import './App.css'; import 'material-design-lite'; import AppRouter from '../AppRouter'; diff --git a/middleware/staticTemplate.js b/middleware/staticTemplate.js index f16caaea9..d8137693e 100644 --- a/middleware/staticTemplate.js +++ b/middleware/staticTemplate.js @@ -94,9 +94,13 @@ const createResolveFactory = (() => { module.exports = async (req, res, next) => { try { - // Attach the custom css url. - const { customCssUrl } = await SettingsService.select('customCssUrl'); + // Attach the custom css url and organization name. + const { customCssUrl, organizationName } = await SettingsService.select( + 'customCssUrl', + 'organizationName' + ); res.locals.customCssUrl = customCssUrl; + res.locals.organizationName = organizationName; } catch (err) { console.warn(err); } diff --git a/package.json b/package.json index f6ff46a59..01b671dde 100644 --- a/package.json +++ b/package.json @@ -219,6 +219,7 @@ "babel-plugin-dynamic-import-node": "^1.1.0", "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", "browserstack-local": "^1.3.0", + "casual": "^1.5.19", "chai": "^3.5.0", "chai-as-promised": "^6.0.0", "chai-datetime": "^1.5.0", diff --git a/public/css/dev.css b/public/css/dev.css new file mode 100644 index 000000000..73ccaed2c --- /dev/null +++ b/public/css/dev.css @@ -0,0 +1,5 @@ +.container { + width: auto; + max-width: 680px; + padding: 0 15px; +} diff --git a/routes/dev/assets.js b/routes/dev/assets.js index cb1f27e43..aea0628e3 100644 --- a/routes/dev/assets.js +++ b/routes/dev/assets.js @@ -1,49 +1,58 @@ const express = require('express'); const router = express.Router(); - -const errors = require('../../errors'); -const Assets = require('../../services/assets'); - -const body = - 'Lorem ipsum dolor sponge amet, consectetur adipiscing clam. Ut lobortis sollicitudin pillar a ornare. Curabitur dignissim vestibulum cay non rhoncus. Cras laoreet ante vel nunc hendrerit, shelf imperdiet neque egestas. Suspendisse aliquet iaculis fermentum. Talk volutpat, tellus posuere laoreet consequat, mi lacus laoreet massa, sed vehicula mauris velit non lectus. Integer non trust nec neque congue faucibus porttitor sit amet elkhorn.'; +const casual = require('casual'); +const { ErrNotFound } = require('../../errors'); +const Asset = require('../../models/asset'); router.get('/id/:asset_id', async (req, res, next) => { try { - const asset = await Assets.findById(req.params.asset_id); + const asset = await Asset.findOne({ id: req.params.asset_id }); if (asset === null) { - return next(errors.ErrNotFound); + throw new ErrNotFound(); } res.render('dev/article', { title: asset.title, asset_id: asset.id, asset_url: asset.url, - body: '', - basePath: '/client/embed/stream', }); } catch (err) { return next(err); } }); +router.get('/random', (req, res) => { + const title = casual.title; + + res.redirect(`./title/${title.replace(/ /g, '-')}`); +}); + router.get('/title/:asset_title', (req, res) => { - return res.render('dev/article', { + res.render('dev/article', { title: req.params.asset_title.split('-').join(' '), asset_url: '', asset_id: null, - body: body, - basePath: '/client/embed/stream', }); }); router.get('/', async (req, res, next) => { - let skip = req.query.skip ? parseInt(req.query.skip) : 0; - let limit = req.query.limit ? parseInt(req.query.limit) : 25; - try { - const assets = await Assets.all(skip, limit); + const skip = req.query.skip ? parseInt(req.query.skip) : 0; + const limit = req.query.limit ? parseInt(req.query.limit) : 6; + + const [assets, count] = await Promise.all([ + Asset.find({}) + .sort({ created_at: 1 }) + .limit(limit) + .skip(skip), + Asset.count(), + ]); + res.render('dev/articles', { - assets: assets, + skip, + limit, + count, + assets, }); } catch (err) { return next(err); diff --git a/routes/dev/index.js b/routes/dev/index.js index ac72db254..58daca583 100644 --- a/routes/dev/index.js +++ b/routes/dev/index.js @@ -16,8 +16,6 @@ router.get('/', staticTemplate, async (req, res) => { title: 'Coral Talk', asset_url: '', asset_id: '', - body: '', - basePath: '/static/embed/stream', }); } }); diff --git a/services/assets.js b/services/assets.js index 794145eb3..5f1fa989d 100644 --- a/services/assets.js +++ b/services/assets.js @@ -232,11 +232,5 @@ module.exports = class AssetsService { await AssetModel.remove({ id: srcAssetID, }); - - // That's it! - } - - static all(limit = undefined) { - return AssetModel.find({}).limit(limit); } }; diff --git a/views/admin.ejs b/views/admin.ejs index b8f775d79..89cc150f7 100644 --- a/views/admin.ejs +++ b/views/admin.ejs @@ -3,16 +3,11 @@ Talk - Coral Admin - + <%- include partials/head %> - - - <%- include partials/head %> + <%- include partials/custom-css %>
    diff --git a/views/dev/article.ejs b/views/dev/article.ejs index 8abfc34af..008d3989e 100644 --- a/views/dev/article.ejs +++ b/views/dev/article.ejs @@ -8,53 +8,45 @@ - - <%= title %> + <%- include ../partials/dev %> -
    -

    <%= title %>

    -

    <%= body %>

    -

    Admin - All Assets

    -
    - + -
    + * You can listen to events using the example below. + * The argument passed is the event emitter from + * https://github.com/asyncly/EventEmitter2 + * + * events: function(events) { + * events.onAny(function(eventName, data) { + * console.log(eventName, data); + * }); + * }, + */ + plugins_config: { + /** + * You can disable rendering slot components of a plugin by doing: + * + * 'talk-plugin-love': { + * disable_components: true, + * }, + */ + test: 'data', + debug: false + } + }) + +
    diff --git a/views/dev/articles.ejs b/views/dev/articles.ejs index 0ce684029..3a67b53c5 100644 --- a/views/dev/articles.ejs +++ b/views/dev/articles.ejs @@ -1,14 +1,40 @@ - -

    - Asset list -

    -<% assets.forEach(function (asset) { %> - <%= asset.url %>
    -<% }) %> -

    - (For dev use only. FYI, you can: ?skip=100&limit=25) -

    - - + + All Assets + <%- include ../partials/dev %> + + + <%- include ../partials/dev-nav %> +
    +
    +

    All Assets

    + <%= skip + 1 %> - <%= skip + assets.length %> of <%= count %> +
    +
    + <% if (skip === 0) { %> Create a random article<% } %> + <% assets.forEach(function (asset) { %> + +
    +
    <%= asset.title %>
    + Created <%= asset.created_at.toLocaleString('en-US') %> +
    + <%= asset.url %> +
    + <% }) %> +
    + <% if (count !== assets.length) { %> + + <% } %> +
    + diff --git a/views/embed/stream.ejs b/views/embed/stream.ejs index 2027f6069..a1c708b55 100644 --- a/views/embed/stream.ejs +++ b/views/embed/stream.ejs @@ -5,6 +5,7 @@ <%- include ../partials/head %> + <%- include ../partials/custom-css %>
    diff --git a/views/login.ejs b/views/login.ejs index c5a86c2fb..671b25c83 100644 --- a/views/login.ejs +++ b/views/login.ejs @@ -6,6 +6,7 @@ <%- include partials/head %> + <%- include partials/custom-css %>
    diff --git a/views/partials/account.ejs b/views/partials/account.ejs index 79857baf0..ff2166816 100644 --- a/views/partials/account.ejs +++ b/views/partials/account.ejs @@ -1,3 +1,4 @@ -<%- include ./head %> +<%- include head %> +<%- include custom-css %> diff --git a/views/partials/custom-css.ejs b/views/partials/custom-css.ejs new file mode 100644 index 000000000..d453c37e1 --- /dev/null +++ b/views/partials/custom-css.ejs @@ -0,0 +1 @@ +<% if (locals.customCssUrl) { %><% } %> diff --git a/views/partials/dev-nav.ejs b/views/partials/dev-nav.ejs new file mode 100644 index 000000000..0d6d067d6 --- /dev/null +++ b/views/partials/dev-nav.ejs @@ -0,0 +1,8 @@ + diff --git a/views/partials/dev.ejs b/views/partials/dev.ejs new file mode 100644 index 000000000..691b548e5 --- /dev/null +++ b/views/partials/dev.ejs @@ -0,0 +1,5 @@ + + + + + diff --git a/views/partials/head.ejs b/views/partials/head.ejs index e848b3909..dc3df7103 100644 --- a/views/partials/head.ejs +++ b/views/partials/head.ejs @@ -18,9 +18,6 @@ -<%_ if (locals.customCssUrl) { _%> - -<%_ } _%> <%- include data %> diff --git a/yarn.lock b/yarn.lock index 5cf363b6e..f0da6d006 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1828,6 +1828,13 @@ caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" +casual@^1.5.19: + version "1.5.19" + resolved "https://registry.yarnpkg.com/casual/-/casual-1.5.19.tgz#66fac46f7ae463f468f5913eb139f9c41c58bbf2" + dependencies: + mersenne-twister "^1.0.1" + moment "^2.15.2" + center-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" @@ -7154,6 +7161,10 @@ merge@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" +mersenne-twister@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a" + metascraper-author@^3.9.2: version "3.9.2" resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-3.9.2.tgz#ff2020ac428f59a875d655df3b0d4bea171fde19" @@ -7436,6 +7447,10 @@ moment@^2.10.3: version "2.19.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.1.tgz#56da1a2d1cbf01d38b7e1afc31c10bcfa1929167" +moment@^2.15.2: + version "2.22.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad" + mongodb-core@2.1.17: version "2.1.17" resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.17.tgz#a418b337a14a14990fb510b923dee6a813173df8" From 55a9c1e6b4a64e5a85556b48c7da4072b636edf9 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 9 May 2018 14:05:16 -0600 Subject: [PATCH 022/162] added link to graphiql --- public/css/dev.css | 4 ++++ views/partials/dev-nav.ejs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/public/css/dev.css b/public/css/dev.css index 73ccaed2c..2fd6c1982 100644 --- a/public/css/dev.css +++ b/public/css/dev.css @@ -3,3 +3,7 @@ max-width: 680px; padding: 0 15px; } + +.graphiql em { + font-family: georgia; +} diff --git a/views/partials/dev-nav.ejs b/views/partials/dev-nav.ejs index 0d6d067d6..088c52c20 100644 --- a/views/partials/dev-nav.ejs +++ b/views/partials/dev-nav.ejs @@ -2,7 +2,7 @@ <%= organizationName %> Organization
    Admin - Development + GraphiQL All Assets
    From 302cab8ca02150f1329cd217b65b0d503cdaa6dc Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 9 May 2018 14:12:27 -0600 Subject: [PATCH 023/162] added header to graphiql --- views/api/graphiql.ejs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/views/api/graphiql.ejs b/views/api/graphiql.ejs index b6d609de6..8007569b3 100644 --- a/views/api/graphiql.ejs +++ b/views/api/graphiql.ejs @@ -5,13 +5,17 @@ GraphiQL + <%- include ../partials/dev %> @@ -20,6 +24,8 @@ + <%- include ../partials/dev-nav %> +
    - \ No newline at end of file + From 969206f9057ffac2c9b68e110014128b018846c2 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 9 May 2018 14:14:48 -0600 Subject: [PATCH 024/162] copy adjustment --- views/dev/articles.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/dev/articles.ejs b/views/dev/articles.ejs index 3a67b53c5..1b07ed116 100644 --- a/views/dev/articles.ejs +++ b/views/dev/articles.ejs @@ -8,7 +8,7 @@

    All Assets

    - <%= skip + 1 %> - <%= skip + assets.length %> of <%= count %> + <%= skip + 1 %> - <%= skip + assets.length %> of <%= count %> Assets
    <% if (skip === 0) { %> Create a random article<% } %> From 51525de679c3957d583d6b85ad8bf02398556ff5 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 9 May 2018 14:17:43 -0600 Subject: [PATCH 025/162] fixed set-role description --- bin/cli-users | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/cli-users b/bin/cli-users index a01980a20..bf34cce97 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -328,7 +328,7 @@ program .action(searchUsers); program - .command('set-role ') + .command('set-role ') .description('sets the role on a user') .action(setUserRole); From 9af3772087198b9645d5fcfb2733c5f01966b419 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 9 May 2018 14:28:04 -0600 Subject: [PATCH 026/162] Added json output for assets list - Fixes #1597 --- bin/cli-assets | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/bin/cli-assets b/bin/cli-assets index 210b9f22e..62c996412 100755 --- a/bin/cli-assets +++ b/bin/cli-assets @@ -23,23 +23,33 @@ util.onshutdown([() => mongoose.disconnect()]); /** * Lists all the assets registered in the database. */ -async function listAssets() { +async function listAssets(opts) { try { let assets = await AssetModel.find({}).sort({ created_at: 1 }); - let table = new Table({ - head: ['ID', 'Title', 'URL'], - }); + switch (opts.format) { + case 'json': { + console.log(JSON.stringify(assets, null, 2)); + break; + } + default: { + let table = new Table({ + head: ['ID', 'Title', 'URL'], + }); - assets.forEach(asset => { - table.push([ - asset.id, - asset.title ? asset.title : '', - asset.url ? asset.url : '', - ]); - }); + assets.forEach(asset => { + table.push([ + asset.id, + asset.title ? asset.title : '', + asset.url ? asset.url : '', + ]); + }); + + console.log(table.toString()); + break; + } + } - console.log(table.toString()); util.shutdown(); } catch (e) { console.error(e); @@ -202,6 +212,12 @@ async function rewrite(search, replace, options) { program .command('list') + .option( + '--format ', + 'Specify the output format [table]', + /^(table|json)$/i, + 'table' + ) .description('list all the assets in the database') .action(listAssets); From ca3d56def87d1e478fb44a3d29ce82d1dd47ad1d Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 9 May 2018 14:40:24 -0600 Subject: [PATCH 027/162] asset refresh patched --- bin/cli-assets | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/bin/cli-assets b/bin/cli-assets index 62c996412..5c1afb741 100755 --- a/bin/cli-assets +++ b/bin/cli-assets @@ -59,12 +59,13 @@ async function listAssets(opts) { async function refreshAssets(ageString) { try { - const now = new Date().getTime(); - const ageMs = parseDuration(ageString); - const age = new Date(now - ageMs); + const query = AssetModel.find({}, { id: 1 }); + if (ageString) { + // An age was specified, so filter only those assets. + const ageMs = parseDuration(ageString); + const age = new Date(Date.now() - ageMs); - let assets = await AssetModel.find( - { + query.merge({ $or: [ { scraped: { @@ -75,16 +76,28 @@ async function refreshAssets(ageString) { scraped: null, }, ], - }, - { id: 1 } - ); + }); + } // Create a graph context. const ctx = Context.forSystem(); + // Load the assets. + const cursor = query.cursor(); + // Queue all the assets for scraping. - await Promise.all(assets.map(({ id }) => scraper.create(ctx, id))); - console.log('Assets were queued to be scraped'); + const promises = []; + + let asset = await cursor.next(); + while (asset) { + promises.push(scraper.create(ctx, asset.id)); + asset = await cursor.next(); + } + + await Promise.all(promises); + + console.log(`${promises.length} Assets were queued to be scraped.`); + util.shutdown(); } catch (e) { console.error(e); @@ -222,7 +235,7 @@ program .action(listAssets); program - .command('refresh ') + .command('refresh [age]') .description('queues the assets that exceed the age requested') .action(refreshAssets); From a092a197cffeb3a8a1591a2af4332effaadb7e3f Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 9 May 2018 23:45:53 +0200 Subject: [PATCH 028/162] Check correct email --- plugins/talk-plugin-local-auth/client/components/Profile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/talk-plugin-local-auth/client/components/Profile.js b/plugins/talk-plugin-local-auth/client/components/Profile.js index 2b2d4be11..c3b09aab2 100644 --- a/plugins/talk-plugin-local-auth/client/components/Profile.js +++ b/plugins/talk-plugin-local-auth/client/components/Profile.js @@ -114,11 +114,11 @@ class Profile extends React.Component { isSaveEnabled = () => { const { formData } = this.state; - const { emailAddress, username } = this.props; + const { root: { me: { username, email } } } = this.props; const formHasErrors = !!Object.keys(this.state.errors).length; const validUsername = formData.newUsername && formData.newUsername !== username; - const validEmail = formData.newEmail && formData.newEmail !== emailAddress; + const validEmail = formData.newEmail && formData.newEmail !== email; return !formHasErrors && (validUsername || validEmail); }; From 2554b261100ab80d0f38e8c83bdfb78cded75dd6 Mon Sep 17 00:00:00 2001 From: Andrew Losowsky Date: Thu, 10 May 2018 10:21:44 -0400 Subject: [PATCH 029/162] Corrections and clarity Fixed the incorrect thresholds and made the description clearer --- docs/source/03-07-product-guide-trust.md | 44 ++++++++++++++---------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/docs/source/03-07-product-guide-trust.md b/docs/source/03-07-product-guide-trust.md index c27c59dcc..961cf843d 100644 --- a/docs/source/03-07-product-guide-trust.md +++ b/docs/source/03-07-product-guide-trust.md @@ -3,47 +3,53 @@ title: Trust permalink: /trust/ --- -Trust is a set of components within Talk that incorporate automated moderation -features based on a user's previous behavior. +Trust is a set of components within Talk that incorporate basic automated moderation features based on a user's previous behavior. ## User Karma Score -Using Trust’s calculations, Talk will automatically pre-moderate comments of -users who have a negative karma score. All users start out with a `0` neutral -karma score. If they have a comment approved by a moderator, their score -increases by `1`; if they have a comment rejected by a moderator, it decreases -by `1`. When a commenter is labeled as Unreliable, their comments must be -moderated before they are posted. +Using Trust’s calculations, Talk will automatically hold back, move to the Reported queue, and tag with a 'History' marker, any comments by users who have an Unreliable karma score. (This is for sites who practice post-moderation. If you set pre-moderation of all comments sitewide, this feature has limited use.) -When a commenter has one comment rejected, their next comment must be moderated -once in order to post freely again. If they instead get rejected again, then -they must have two of their comments approved in order to get added back to the -queue. +All users start out with a Neutral karma score (`0`). If they have a comment approved by a moderator, their score increases by `1`; if they have a comment rejected by a moderator, it decreases by `1`. When a commenter's score is labeled as Unreliable, their comments must be approved from the Reported queue before they are posted. Commenters are shown a message stating that a moderator will review their comment shortly. Here are the default thresholds: ```text --2 and lower: Unreliable --1 to +2: Neutral -+3 and higher: Reliable +-1 and lower: Unreliable +0 to +1: Neutral ++1 and higher: Reliable (we don't do anything with this label right now) ``` -You can configure your own Trust thresholds by using [TRUST_THRESHOLD](/talk/advanced-configuration/#trust-thresholds) in your -configuration. +So in this case, when a new commenter has their first comment rejected, their user karma score becomes `-1`, which triggers the Unreliable threshhold, and they must then have a comment approved by a moderator in order to post freely again. Until that occurs, all of their comments will be held back temporarily in the Reported queue, marked with a `History` tag. + +If their next comment is also rejected, their user karma score is now `-2`, and they must have two comments approved in order to reach a Neutral score, and post without pre-approval. + +We strongly recommend not telling your community how this system works, or where the threshholds lie. Firstly, they might try to game the system to meet approval, and secondly, it makes it harder for you to change the threshhold in the future. We suggest using language such as "We hold back comments for approval for a variety of reasons, including content, account history, and more." + +If you see that a high proportion of first-time commenters on your site are abusive, you might want to change the threshhold to `0`, at least temporarily. You can configure your own Trust thresholds by using [TRUST_THRESHOLD](/talk/advanced-configuration/#trust-thresholds) in your site configuration. ## Reliable and Unreliable Flaggers Trust also calculates how reliable users are in terms of the comments they report. This information is displayed to moderators in the User History drawer, -which is accessed by clicking on a user’s name in the Admin. +which is accessed by clicking on a user’s name in the Admin. Currently, no other action is taken based on this score. If a user's reports mostly match what moderators reject, their Report status will display to moderators as Reliable in the user information drawer. If a user's reports mostly differ from what moderators reject, their Report status will show as Unreliable. -If we don't have enough reports to make a call, or the reports even out, their +If Talk doesn't have enough reports to make a call, or the reports even out, their status is Neutral. +Here are the default thresholds: + +```text +-1 and lower: Unreliable +0 to +1: Neutral ++2 and higher: Reliable +``` +You can configure your own Trust thresholds by using [TRUST_THRESHOLD](/talk/advanced-configuration/#trust-thresholds) in your +configuration. + Note: Report Karma doesn't include reports of "I don't agree with this comment". From 2e37f78705dbaafd27202ea054dde01773f0fd57 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 10 May 2018 13:31:00 -0600 Subject: [PATCH 030/162] Email Attach Fixes --- client/coral-framework/components/Popup.js | 16 ++++++++++----- .../talk-plugin-local-auth/client/actions.js | 9 +++++++++ .../components/AddEmailAddressDialog.js | 20 ++++++++++++++++--- .../client/components/AddEmailContent.js | 9 +++------ .../client/constants.js | 4 ++++ .../containers/AddEmailAddressDialog.js | 16 +++++++++++---- .../talk-plugin-local-auth/client/index.js | 4 +++- .../talk-plugin-local-auth/client/reducer.js | 20 +++++++++++++++++++ plugins/talk-plugin-local-auth/index.js | 2 +- .../server/translations.yml | 9 --------- .../{client => }/translations.yml | 10 +++++++++- 11 files changed, 89 insertions(+), 30 deletions(-) create mode 100644 plugins/talk-plugin-local-auth/client/actions.js create mode 100644 plugins/talk-plugin-local-auth/client/constants.js create mode 100644 plugins/talk-plugin-local-auth/client/reducer.js delete mode 100644 plugins/talk-plugin-local-auth/server/translations.yml rename plugins/talk-plugin-local-auth/{client => }/translations.yml (91%) diff --git a/client/coral-framework/components/Popup.js b/client/coral-framework/components/Popup.js index 53d8f7cd8..339ddb806 100644 --- a/client/coral-framework/components/Popup.js +++ b/client/coral-framework/components/Popup.js @@ -37,7 +37,8 @@ export default class Popup extends Component { this.onBlur(); }; - // Use `onunload` instead of `onbeforeunload` which is not supported in IOS Safari. + // Use `onunload` instead of `onbeforeunload` which is not supported in iOS + // Safari. this.ref.onunload = () => { this.onUnload(); @@ -46,10 +47,15 @@ export default class Popup extends Component { } this.resetCallbackInterval = setInterval(() => { - if (this.ref && this.ref.onload === null) { - clearInterval(this.resetCallbackInterval); - this.resetCallbackInterval = null; - this.setCallbacks(); + try { + if (this.ref && this.ref.onload === null) { + clearInterval(this.resetCallbackInterval); + this.resetCallbackInterval = null; + this.setCallbacks(); + } + } catch (err) { + // We could be getting a security exception here if the login page + // gets redirected to another domain to authenticate. } }, 50); diff --git a/plugins/talk-plugin-local-auth/client/actions.js b/plugins/talk-plugin-local-auth/client/actions.js new file mode 100644 index 000000000..9f2c9cfed --- /dev/null +++ b/plugins/talk-plugin-local-auth/client/actions.js @@ -0,0 +1,9 @@ +import * as actions from './constants'; + +export const startAttach = () => ({ + type: actions.STARTED_ATTACH, +}); + +export const finishAttach = () => ({ + type: actions.FINISH_ATTACH, +}); diff --git a/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js b/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js index dc7e0d974..76131c895 100644 --- a/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js +++ b/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js @@ -45,6 +45,10 @@ class AddEmailAddressDialog extends React.Component { ), }; + componentDidMount() { + this.props.startAttach(); + } + onChange = e => { const { name, value } = e.target; this.setState( @@ -99,7 +103,13 @@ class AddEmailAddressDialog extends React.Component { }); }; - confirmChanges = async () => { + finish = () => { + this.props.finishAttach(); + }; + + confirmChanges = async e => { + e.preventDefault(); + if (!this.validate()) { this.showErrors(); return; @@ -113,6 +123,8 @@ class AddEmailAddressDialog extends React.Component { email: emailAddress, password: confirmPassword, }); + + // TODO: translate this.props.notify('success', 'Email Added!'); this.goToNextStep(); } catch (err) { @@ -143,13 +155,13 @@ class AddEmailAddressDialog extends React.Component { )} {step === 1 && !settings.requireEmailConfirmation && ( - {}} /> + )} {step === 1 && settings.requireEmailConfirmation && ( {}} + done={this.finish} /> )} @@ -161,6 +173,8 @@ AddEmailAddressDialog.propTypes = { attachLocalAuth: PropTypes.func.isRequired, notify: PropTypes.func.isRequired, root: PropTypes.object.isRequired, + startAttach: PropTypes.func.isRequired, + finishAttach: PropTypes.func.isRequired, }; export default AddEmailAddressDialog; diff --git a/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js b/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js index 8d3fc308f..de296c812 100644 --- a/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js +++ b/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js @@ -41,7 +41,7 @@ const AddEmailContent = ({ -
    +
    diff --git a/plugins/talk-plugin-local-auth/client/constants.js b/plugins/talk-plugin-local-auth/client/constants.js new file mode 100644 index 000000000..86f5242f7 --- /dev/null +++ b/plugins/talk-plugin-local-auth/client/constants.js @@ -0,0 +1,4 @@ +const prefix = 'TALK_LOCAL_AUTH'; + +export const STARTED_ATTACH = `${prefix}_STARTED_ATTACH`; +export const FINISH_ATTACH = `${prefix}_FINISH_ATTACH`; diff --git a/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js b/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js index 841dc8420..4cc80d022 100644 --- a/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js +++ b/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js @@ -3,10 +3,15 @@ import { bindActionCreators } from 'redux'; import { connect, withFragments, excludeIf } from 'plugin-api/beta/client/hocs'; import AddEmailAddressDialog from '../components/AddEmailAddressDialog'; import { notify } from 'coral-framework/actions/notification'; - import { withAttachLocalAuth } from '../hocs'; +import { startAttach, finishAttach } from '../actions'; -const mapDispatchToProps = dispatch => bindActionCreators({ notify }, dispatch); +const mapStateToProps = ({ talkPluginLocalAuth: state }) => ({ + inProgress: state.inProgress, +}); + +const mapDispatchToProps = dispatch => + bindActionCreators({ notify, startAttach, finishAttach }, dispatch); const withData = withFragments({ root: gql` @@ -23,8 +28,11 @@ const withData = withFragments({ }); export default compose( - connect(null, mapDispatchToProps), + connect(mapStateToProps, mapDispatchToProps), withAttachLocalAuth, withData, - excludeIf(({ root: { me } }) => !me || me.email) + excludeIf( + ({ root: { me }, inProgress }) => + !((me && !me.email) || (me && me.email && inProgress)) + ) )(AddEmailAddressDialog); diff --git a/plugins/talk-plugin-local-auth/client/index.js b/plugins/talk-plugin-local-auth/client/index.js index 9c89e0490..a4dc01ac2 100644 --- a/plugins/talk-plugin-local-auth/client/index.js +++ b/plugins/talk-plugin-local-auth/client/index.js @@ -1,10 +1,12 @@ import ChangePassword from './containers/ChangePassword'; import AddEmailAddressDialog from './containers/AddEmailAddressDialog'; import Profile from './containers/Profile'; -import translations from './translations.yml'; +import translations from '../translations.yml'; import graphql from './graphql'; +import reducer from './reducer'; export default { + reducer, translations, slots: { profileHeader: [Profile], diff --git a/plugins/talk-plugin-local-auth/client/reducer.js b/plugins/talk-plugin-local-auth/client/reducer.js new file mode 100644 index 000000000..403a84814 --- /dev/null +++ b/plugins/talk-plugin-local-auth/client/reducer.js @@ -0,0 +1,20 @@ +import * as actions from './constants'; + +const initialState = { + inProgress: false, +}; + +export default function reducer(state = initialState, action) { + switch (action.type) { + case actions.STARTED_ATTACH: + return { + inProgress: true, + }; + case actions.FINISH_ATTACH: + return { + inProgress: false, + }; + default: + return state; + } +} diff --git a/plugins/talk-plugin-local-auth/index.js b/plugins/talk-plugin-local-auth/index.js index 7e4ee6a3c..fabe1dd2b 100644 --- a/plugins/talk-plugin-local-auth/index.js +++ b/plugins/talk-plugin-local-auth/index.js @@ -4,7 +4,7 @@ const mutators = require('./server/mutators'); const path = require('path'); module.exports = { - translations: path.join(__dirname, 'server', 'translations.yml'), + translations: path.join(__dirname, 'translations.yml'), typeDefs, mutators, resolvers, diff --git a/plugins/talk-plugin-local-auth/server/translations.yml b/plugins/talk-plugin-local-auth/server/translations.yml deleted file mode 100644 index d81de6c6d..000000000 --- a/plugins/talk-plugin-local-auth/server/translations.yml +++ /dev/null @@ -1,9 +0,0 @@ -en: - email: - email_change_original: - subject: Email change - body: Your email address has been changed from {0} to {1}. If you did not initiate this change, please contact {2}. # TODO: update translation - error: - NO_LOCAL_PROFILE: No existing email address is associated with this account. - LOCAL_PROFILE: An email address is already associated with this account. - INCORRECT_PASSWORD: Provided password was incorrect. diff --git a/plugins/talk-plugin-local-auth/client/translations.yml b/plugins/talk-plugin-local-auth/translations.yml similarity index 91% rename from plugins/talk-plugin-local-auth/client/translations.yml rename to plugins/talk-plugin-local-auth/translations.yml index fcb80782a..7180268ea 100644 --- a/plugins/talk-plugin-local-auth/client/translations.yml +++ b/plugins/talk-plugin-local-auth/translations.yml @@ -1,4 +1,12 @@ en: + email: + email_change_original: + subject: Email change + body: Your email address has been changed from {0} to {1}. If you did not initiate this change, please contact {2}. # TODO: update translation + error: + NO_LOCAL_PROFILE: No existing email address is associated with this account. + LOCAL_PROFILE: An email address is already associated with this account. + INCORRECT_PASSWORD: Provided password was incorrect. talk-plugin-local-auth: change_password: change_password: "Change Password" @@ -43,7 +51,7 @@ en: email_does_not_match: "Email Address does not match" insert_password: "Insert Password:" required_field: "This field is required" - done: "done" + done: "Done" content: title: "Add an Email Address" description: "For your added security, we require users to add an email address to their accounts. Your email address will be used to:" From e0cada5bde50f7b61a3e8bfd13b4040042eb92a9 Mon Sep 17 00:00:00 2001 From: Andrew Losowsky Date: Thu, 10 May 2018 13:44:03 -0700 Subject: [PATCH 031/162] Update 03-07-product-guide-trust.md --- docs/source/03-07-product-guide-trust.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/03-07-product-guide-trust.md b/docs/source/03-07-product-guide-trust.md index 961cf843d..72248e7d1 100644 --- a/docs/source/03-07-product-guide-trust.md +++ b/docs/source/03-07-product-guide-trust.md @@ -16,7 +16,7 @@ Here are the default thresholds: ```text -1 and lower: Unreliable 0 to +1: Neutral -+1 and higher: Reliable (we don't do anything with this label right now) ++2 and higher: Reliable (we don't do anything with this label right now) ``` So in this case, when a new commenter has their first comment rejected, their user karma score becomes `-1`, which triggers the Unreliable threshhold, and they must then have a comment approved by a moderator in order to post freely again. Until that occurs, all of their comments will be held back temporarily in the Reported queue, marked with a `History` tag. From 631206f3603431d3456f445aff22f418738f48df Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 11 May 2018 00:30:12 +0200 Subject: [PATCH 032/162] Disable autofocus on IOS Safari, prevent zoom on narrow screens --- plugins/talk-plugin-rich-text/client/components/Editor.css | 7 +++++++ plugins/talk-plugin-rich-text/client/components/Editor.js | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/talk-plugin-rich-text/client/components/Editor.css b/plugins/talk-plugin-rich-text/client/components/Editor.css index 2259d01a3..be2646121 100644 --- a/plugins/talk-plugin-rich-text/client/components/Editor.css +++ b/plugins/talk-plugin-rich-text/client/components/Editor.css @@ -1,5 +1,12 @@ +@custom-media --narrow-viewport (max-width: 420px); + .commentContent { composes: content from "./CommentContent.css"; + + /* Prevent zoom on narrow viewports */ + @media (--narrow-viewport) { + font-size: 16px; + } } .placeholder { diff --git a/plugins/talk-plugin-rich-text/client/components/Editor.js b/plugins/talk-plugin-rich-text/client/components/Editor.js index e09931d38..089900e1a 100644 --- a/plugins/talk-plugin-rich-text/client/components/Editor.js +++ b/plugins/talk-plugin-rich-text/client/components/Editor.js @@ -8,6 +8,7 @@ import RTE from './rte/RTE'; import { Icon } from 'plugin-api/beta/client/components/ui'; import { Bold, Italic, Blockquote } from './rte/features'; import { t } from 'plugin-api/beta/client/services'; +import bowser from 'bowser'; class Editor extends React.Component { ref = null; @@ -40,7 +41,7 @@ class Editor extends React.Component { } }); } - if (this.props.isReply) { + if (this.props.isReply && !bowser.ios) { this.ref.focus(); } } From 33be00a5404cded4b4b5b8b9d1f0ba37533a96f8 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 10 May 2018 16:44:19 -0600 Subject: [PATCH 033/162] Added support for external signins on Admin --- .../coral-admin/src/components/External.css | 16 ++++ client/coral-admin/src/components/External.js | 24 +++++ client/coral-admin/src/components/SignIn.js | 90 ++++++++++--------- client/coral-admin/src/containers/SignIn.js | 4 +- .../coral-framework/services/postMessage.js | 6 +- client/coral-framework/utils/index.js | 20 +++++ plugin-api/beta/client/utils/index.js | 1 + .../client/actions.js | 4 +- .../talk-plugin-google-auth/client/actions.js | 4 +- 9 files changed, 121 insertions(+), 48 deletions(-) create mode 100644 client/coral-admin/src/components/External.css create mode 100644 client/coral-admin/src/components/External.js diff --git a/client/coral-admin/src/components/External.css b/client/coral-admin/src/components/External.css new file mode 100644 index 000000000..ff23c53cc --- /dev/null +++ b/client/coral-admin/src/components/External.css @@ -0,0 +1,16 @@ +.external { + margin-bottom: 20px; +} + +.separator h5 { + text-align: center; + font-size: 1.2em; +} + +.slot > * { +margin-bottom: 8px; + +&:last-child { + margin-bottom: 0px; +} +} diff --git a/client/coral-admin/src/components/External.js b/client/coral-admin/src/components/External.js new file mode 100644 index 000000000..0f133d3b9 --- /dev/null +++ b/client/coral-admin/src/components/External.js @@ -0,0 +1,24 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './External.css'; +import Slot from 'coral-framework/components/Slot'; +import IfSlotIsNotEmpty from 'coral-framework/components/IfSlotIsNotEmpty'; + +const External = ({ slot }) => ( + +
    +
    + +
    +
    +
    Or
    +
    +
    +
    +); + +External.propTypes = { + slot: PropTypes.string.isRequired, +}; + +export default External; diff --git a/client/coral-admin/src/components/SignIn.js b/client/coral-admin/src/components/SignIn.js index 5e1de2033..ddd69d3ac 100644 --- a/client/coral-admin/src/components/SignIn.js +++ b/client/coral-admin/src/components/SignIn.js @@ -4,6 +4,7 @@ import styles from './SignIn.css'; import { Button, TextField, Alert } from 'coral-ui'; import cn from 'classnames'; import Recaptcha from 'coral-framework/components/Recaptcha'; +import External from './External'; class SignIn extends React.Component { recaptcha = null; @@ -33,48 +34,55 @@ class SignIn extends React.Component { render() { const { email, password, errorMessage, requireRecaptcha } = this.props; return ( -
    - {errorMessage && {errorMessage}} - - - {requireRecaptcha && ( -
    - -
    - )} - -

    - Forgot your password?{' '} - + + + {errorMessage && {errorMessage}} + + + {requireRecaptcha && ( +

    + )} + +

    + {/* TODO: translate */} + Forgot your password?{' '} + + Request a new one. + +

    + +
    ); } } diff --git a/client/coral-admin/src/containers/SignIn.js b/client/coral-admin/src/containers/SignIn.js index 523d81091..1af857294 100644 --- a/client/coral-admin/src/containers/SignIn.js +++ b/client/coral-admin/src/containers/SignIn.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { withSignIn } from 'coral-framework/hocs'; +import { withSignIn, withPopupAuthHandler } from 'coral-framework/hocs'; import { compose } from 'recompose'; import SignIn from '../components/SignIn'; @@ -55,4 +55,4 @@ SignInContainer.propTypes = { requireRecaptcha: PropTypes.bool.isRequired, }; -export default compose(withSignIn)(SignInContainer); +export default compose(withSignIn, withPopupAuthHandler)(SignInContainer); diff --git a/client/coral-framework/services/postMessage.js b/client/coral-framework/services/postMessage.js index ef9311e64..6bc18eccf 100644 --- a/client/coral-framework/services/postMessage.js +++ b/client/coral-framework/services/postMessage.js @@ -56,10 +56,10 @@ export function createPostMessage(origin, scope = 'client') { // Send the message. target.postMessage(msg, origin); }, - subscribe: (handler, target = window) => { + subscribe(handler, target = window) { // If this handler is already attached to the target, detach it. if (has(listeners, [target, handler])) { - this.unsubscribeFromMessages(handler, target); + this.unsubscribe(handler, target); } // Wrap the listener with a origin check. @@ -71,7 +71,7 @@ export function createPostMessage(origin, scope = 'client') { // Attach the listener to the target. target.addEventListener('message', listener); }, - unsubscribe: (handler, target = window) => { + unsubscribe(handler, target = window) { if (!has(listeners, [target, handler])) { return; } diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js index e30c12e06..6351c4484 100644 --- a/client/coral-framework/utils/index.js +++ b/client/coral-framework/utils/index.js @@ -273,3 +273,23 @@ export function translateError(error) { } return error.toString(); } + +/** + * handlePopupAuth will optionally open a popup with the requested uri if the + * window is not already a popup. + * + * @param {String} uri the url to open the window? to + * @param {String} title the title of the new window? to open + * @param {String} features the features to use when opening a window? + */ +export function handlePopupAuth( + uri, + title = 'Login', // TODO: translate + features = 'menubar=0,resizable=0,width=500,height=550,top=200,left=500' +) { + if (window.opener) { + window.location = uri; + } else { + window.open(uri, title, features); + } +} diff --git a/plugin-api/beta/client/utils/index.js b/plugin-api/beta/client/utils/index.js index aeb9841f9..0d1893066 100644 --- a/plugin-api/beta/client/utils/index.js +++ b/plugin-api/beta/client/utils/index.js @@ -9,4 +9,5 @@ export { getDefinitionName, getShallowChanges, createDefaultResponseFragments, + handlePopupAuth, } from 'coral-framework/utils'; diff --git a/plugins/talk-plugin-facebook-auth/client/actions.js b/plugins/talk-plugin-facebook-auth/client/actions.js index 79f2d2a6a..2f92ecfb6 100644 --- a/plugins/talk-plugin-facebook-auth/client/actions.js +++ b/plugins/talk-plugin-facebook-auth/client/actions.js @@ -1,3 +1,5 @@ +import { handlePopupAuth } from 'plugin-api/beta/client/utils'; + export const loginWithFacebook = () => (dispatch, _, { rest }) => { - window.location = `${rest.uri}/auth/facebook`; + handlePopupAuth(`${rest.uri}/auth/facebook`); }; diff --git a/plugins/talk-plugin-google-auth/client/actions.js b/plugins/talk-plugin-google-auth/client/actions.js index 8b49bf39e..1856ddb54 100644 --- a/plugins/talk-plugin-google-auth/client/actions.js +++ b/plugins/talk-plugin-google-auth/client/actions.js @@ -1,3 +1,5 @@ +import { handlePopupAuth } from 'plugin-api/beta/client/utils'; + export const loginWithGoogle = () => (dispatch, _, { rest }) => { - window.location = `${rest.uri}/auth/google`; + handlePopupAuth(`${rest.uri}/auth/google`); }; From 2d1b675234578b5d7b11d9df2ddc406d37d480fa Mon Sep 17 00:00:00 2001 From: Mendel Konikov Date: Thu, 10 May 2018 21:01:26 -0400 Subject: [PATCH 034/162] Update download page copy --- plugins/talk-plugin-profile-data/translations.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/talk-plugin-profile-data/translations.yml b/plugins/talk-plugin-profile-data/translations.yml index 39b810cca..51555d305 100644 --- a/plugins/talk-plugin-profile-data/translations.yml +++ b/plugins/talk-plugin-profile-data/translations.yml @@ -7,7 +7,7 @@ en: date: "When you wrote the comment" url: "The permalink URL for the comment" body: "The comment text" - asset_url: "The URL on the article or story where the comment appears" + asset_url: "The URL of the article or story where the comment appears" confirm: "Download My Comment History" email: download: @@ -17,7 +17,7 @@ en: delete: subject: "Your account for {0} is scheduled to be deleted" body: | - A request to delete your account was received. Your account is scheduled for deletion on {1}. + A request to delete your account was received. Your account is scheduled for deletion on {1}. After that time all of your comments will be removed from the site, all of your comments will be removed from our database, and your username and email address will be removed from our system. From b73620bcb0de6ef33f10b8491e486fe9be6cfe73 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 20 Apr 2018 10:00:52 +0200 Subject: [PATCH 035/162] WIP --- .gitignore | 1 + .../plugin/talk-plugin-global-switchoff.md | 25 ++++++++ .../client/.eslintrc.json | 23 ++++++++ .../client/components/GlobalSwitchoff.js | 59 +++++++++++++++++++ .../client/components/styles.css | 0 .../client/index.js | 9 +++ .../client/translations.yml | 8 +++ plugins/talk-plugin-global-switchoff/index.js | 11 ++++ .../server/index.js | 1 + .../server/resolvers.js | 10 ++++ .../server/typeDefs.graphql | 10 ++++ 11 files changed, 157 insertions(+) create mode 100644 docs/source/plugin/talk-plugin-global-switchoff.md create mode 100644 plugins/talk-plugin-global-switchoff/client/.eslintrc.json create mode 100644 plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js create mode 100644 plugins/talk-plugin-global-switchoff/client/components/styles.css create mode 100644 plugins/talk-plugin-global-switchoff/client/index.js create mode 100644 plugins/talk-plugin-global-switchoff/client/translations.yml create mode 100644 plugins/talk-plugin-global-switchoff/index.js create mode 100644 plugins/talk-plugin-global-switchoff/server/index.js create mode 100644 plugins/talk-plugin-global-switchoff/server/resolvers.js create mode 100644 plugins/talk-plugin-global-switchoff/server/typeDefs.graphql diff --git a/.gitignore b/.gitignore index 24022b9d7..ce5146929 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ plugins/* !plugins/talk-plugin-facebook-auth !plugins/talk-plugin-featured-comments !plugins/talk-plugin-flag-details +!plugins/talk-plugin-global-switchoff !plugins/talk-plugin-google-auth !plugins/talk-plugin-ignore-user !plugins/talk-plugin-like diff --git a/docs/source/plugin/talk-plugin-global-switchoff.md b/docs/source/plugin/talk-plugin-global-switchoff.md new file mode 100644 index 000000000..317fb4b3b --- /dev/null +++ b/docs/source/plugin/talk-plugin-global-switchoff.md @@ -0,0 +1,25 @@ +--- +title: talk-plugin-global-switchoff +permalink: /plugin/talk-plugin-global-switchoff/ +layout: plugin +plugin: + name: talk-plugin-global-switchoff + provides: + - Client + - Server +--- + +Add a switch to the settings page that disables commenting globally. + +## Installation + +TBD + +Add `"talk-plugin-global-switchoff"` to the `plugins.json` in your Talk installation. +This plugin provides a server and a client side implementation. + +## Server implementation + +### How does this work? + +TODO diff --git a/plugins/talk-plugin-global-switchoff/client/.eslintrc.json b/plugins/talk-plugin-global-switchoff/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/plugins/talk-plugin-global-switchoff/client/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} diff --git a/plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js b/plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js new file mode 100644 index 000000000..a5150ed10 --- /dev/null +++ b/plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { gql } from 'react-apollo'; +import ConfigureCard from 'coral-framework/components/ConfigureCard'; +import MarkdownEditor from 'coral-framework/components/MarkdownEditor'; +import t from 'coral-framework/services/i18n'; +import { withFragments } from 'plugin-api/beta/client/hocs'; +import cn from 'classnames'; +import styles from './styles.css'; + +const plugin = 'talk-plugin-global-switchoff'; + +class GlobalSwitchoff extends React.Component { + updateGlobalSwitchoffEnable = () => { + const updater = { + globalSwitchoffEnable: { + $set: !this.props.settings.globalSwitchoffEnable, + }, + }; + this.props.updatePending({ updater }); + }; + + updateGlobalSwitchoffMessage = () => {}; + + render() { + const { settings } = this.props; + return ( + +

    {t(plugin + '.setting_desc')}

    +
    + +
    +
    + ); + } +} + +// export default GlobalSwitchoff; + +export default withFragments({ + settings: gql` + fragment TalkPlugin_GlobalSwitchoff_settings on Settings { + globalSwitchoffEnable + globalSwitchoffMessage + } + `, +})(GlobalSwitchoff); diff --git a/plugins/talk-plugin-global-switchoff/client/components/styles.css b/plugins/talk-plugin-global-switchoff/client/components/styles.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/talk-plugin-global-switchoff/client/index.js b/plugins/talk-plugin-global-switchoff/client/index.js new file mode 100644 index 000000000..de8b516e7 --- /dev/null +++ b/plugins/talk-plugin-global-switchoff/client/index.js @@ -0,0 +1,9 @@ +import GlobalSwitchoff from './components/GlobalSwitchoff'; +import translations from './translations.yml'; + +export default { + translations, + slots: { + adminStreamSettings: [GlobalSwitchoff], + }, +}; diff --git a/plugins/talk-plugin-global-switchoff/client/translations.yml b/plugins/talk-plugin-global-switchoff/client/translations.yml new file mode 100644 index 000000000..c135337f4 --- /dev/null +++ b/plugins/talk-plugin-global-switchoff/client/translations.yml @@ -0,0 +1,8 @@ +en: + talk-plugin-global-switchoff: + setting_title: 'Deactivate commenting globally' + setting_desc: 'Write a message that will be displayed while commenting is deactivated.' +de: + talk-plugin-global-switchoff: + setting_title: 'Kommentieren global deaktivieren' + setting_desc: 'Verfassen Sie eine Nachricht, die angezeigt wird, solange das Kommentieren deaktiviert ist.' diff --git a/plugins/talk-plugin-global-switchoff/index.js b/plugins/talk-plugin-global-switchoff/index.js new file mode 100644 index 000000000..dfd1f0d04 --- /dev/null +++ b/plugins/talk-plugin-global-switchoff/index.js @@ -0,0 +1,11 @@ +const { readFileSync } = require('fs'); +const path = require('path'); +const resolvers = require('./server/resolvers'); + +module.exports = { + typeDefs: readFileSync( + path.join(__dirname, 'server/typeDefs.graphql'), + 'utf8' + ), + resolvers, +}; diff --git a/plugins/talk-plugin-global-switchoff/server/index.js b/plugins/talk-plugin-global-switchoff/server/index.js new file mode 100644 index 000000000..f053ebf79 --- /dev/null +++ b/plugins/talk-plugin-global-switchoff/server/index.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/plugins/talk-plugin-global-switchoff/server/resolvers.js b/plugins/talk-plugin-global-switchoff/server/resolvers.js new file mode 100644 index 000000000..4e249ff3a --- /dev/null +++ b/plugins/talk-plugin-global-switchoff/server/resolvers.js @@ -0,0 +1,10 @@ +const { get } = require('lodash'); + +module.exports = { + Settings: { + globalSwitchoffEnable: settings => + get(settings, 'globalSwitchoffEnable', false), + globalSwitchoffMessage: settings => + get(settings, 'globalSwitchoffMessage', ''), + }, +}; diff --git a/plugins/talk-plugin-global-switchoff/server/typeDefs.graphql b/plugins/talk-plugin-global-switchoff/server/typeDefs.graphql new file mode 100644 index 000000000..efb4b80f6 --- /dev/null +++ b/plugins/talk-plugin-global-switchoff/server/typeDefs.graphql @@ -0,0 +1,10 @@ + +type Settings { + globalSwitchoffEnable: Boolean + globalSwitchoffMessage: String +} + +input UpdateSettingsInput { + globalSwitchoffEnable: Boolean + globalSwitchoffMessage: String +} From bbd3050b95d24228a6bf50898b126b99a7a49e11 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 23 Apr 2018 12:04:53 +0200 Subject: [PATCH 036/162] Small refactoring --- .../client/components/GlobalSwitchoff.js | 13 +------------ .../client/containers/GlobalSwitchoff.js | 12 ++++++++++++ .../talk-plugin-global-switchoff/client/index.js | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 plugins/talk-plugin-global-switchoff/client/containers/GlobalSwitchoff.js diff --git a/plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js b/plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js index a5150ed10..d676e7531 100644 --- a/plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js +++ b/plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js @@ -1,9 +1,7 @@ import React from 'react'; -import { gql } from 'react-apollo'; import ConfigureCard from 'coral-framework/components/ConfigureCard'; import MarkdownEditor from 'coral-framework/components/MarkdownEditor'; import t from 'coral-framework/services/i18n'; -import { withFragments } from 'plugin-api/beta/client/hocs'; import cn from 'classnames'; import styles from './styles.css'; @@ -47,13 +45,4 @@ class GlobalSwitchoff extends React.Component { } } -// export default GlobalSwitchoff; - -export default withFragments({ - settings: gql` - fragment TalkPlugin_GlobalSwitchoff_settings on Settings { - globalSwitchoffEnable - globalSwitchoffMessage - } - `, -})(GlobalSwitchoff); +export default GlobalSwitchoff; diff --git a/plugins/talk-plugin-global-switchoff/client/containers/GlobalSwitchoff.js b/plugins/talk-plugin-global-switchoff/client/containers/GlobalSwitchoff.js new file mode 100644 index 000000000..b935bcb5a --- /dev/null +++ b/plugins/talk-plugin-global-switchoff/client/containers/GlobalSwitchoff.js @@ -0,0 +1,12 @@ +import { gql } from 'react-apollo'; +import GlobalSwitchoff from '../components/GlobalSwitchoff'; +import { withFragments } from 'plugin-api/beta/client/hocs'; + +export default withFragments({ + settings: gql` + fragment TalkPlugin_GlobalSwitchoff_settings on Settings { + globalSwitchoffEnable + globalSwitchoffMessage + } + `, +})(GlobalSwitchoff); diff --git a/plugins/talk-plugin-global-switchoff/client/index.js b/plugins/talk-plugin-global-switchoff/client/index.js index de8b516e7..c9994ce5e 100644 --- a/plugins/talk-plugin-global-switchoff/client/index.js +++ b/plugins/talk-plugin-global-switchoff/client/index.js @@ -1,4 +1,4 @@ -import GlobalSwitchoff from './components/GlobalSwitchoff'; +import GlobalSwitchoff from './containers/GlobalSwitchoff'; import translations from './translations.yml'; export default { From 499030028c152969430e76039e297d01899f2eb5 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 23 Apr 2018 15:35:02 +0200 Subject: [PATCH 037/162] Add hooks to RootMutation to save our setting in the database. --- .../{client => }/.eslintrc.json | 0 plugins/talk-plugin-global-switchoff/index.js | 2 ++ .../talk-plugin-global-switchoff/server/hooks.js | 13 +++++++++++++ 3 files changed, 15 insertions(+) rename plugins/talk-plugin-global-switchoff/{client => }/.eslintrc.json (100%) create mode 100644 plugins/talk-plugin-global-switchoff/server/hooks.js diff --git a/plugins/talk-plugin-global-switchoff/client/.eslintrc.json b/plugins/talk-plugin-global-switchoff/.eslintrc.json similarity index 100% rename from plugins/talk-plugin-global-switchoff/client/.eslintrc.json rename to plugins/talk-plugin-global-switchoff/.eslintrc.json diff --git a/plugins/talk-plugin-global-switchoff/index.js b/plugins/talk-plugin-global-switchoff/index.js index dfd1f0d04..a318f8a11 100644 --- a/plugins/talk-plugin-global-switchoff/index.js +++ b/plugins/talk-plugin-global-switchoff/index.js @@ -1,5 +1,6 @@ const { readFileSync } = require('fs'); const path = require('path'); +const hooks = require('./server/hooks'); const resolvers = require('./server/resolvers'); module.exports = { @@ -7,5 +8,6 @@ module.exports = { path.join(__dirname, 'server/typeDefs.graphql'), 'utf8' ), + hooks, resolvers, }; diff --git a/plugins/talk-plugin-global-switchoff/server/hooks.js b/plugins/talk-plugin-global-switchoff/server/hooks.js new file mode 100644 index 000000000..be0498fb6 --- /dev/null +++ b/plugins/talk-plugin-global-switchoff/server/hooks.js @@ -0,0 +1,13 @@ +module.exports = { + RootMutation: { + updateSettings: { + async pre(_, { input }) { + input.metadata = { + ...input.metadata, + globalSwitchoffEnable: input.globalSwitchoffEnable, + }; + delete input.globalSwitchoffEnable; + }, + }, + }, +}; From fab15daacd008c2d3275e26040683b66b2a76be8 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 23 Apr 2018 17:06:46 +0200 Subject: [PATCH 038/162] Fix: use the correct keys to get the settings --- plugins/talk-plugin-global-switchoff/server/resolvers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/talk-plugin-global-switchoff/server/resolvers.js b/plugins/talk-plugin-global-switchoff/server/resolvers.js index 4e249ff3a..f28e765fc 100644 --- a/plugins/talk-plugin-global-switchoff/server/resolvers.js +++ b/plugins/talk-plugin-global-switchoff/server/resolvers.js @@ -3,8 +3,8 @@ const { get } = require('lodash'); module.exports = { Settings: { globalSwitchoffEnable: settings => - get(settings, 'globalSwitchoffEnable', false), + get(settings, 'metadata.globalSwitchoffEnable', false), globalSwitchoffMessage: settings => - get(settings, 'globalSwitchoffMessage', ''), + get(settings, 'metadata.globalSwitchoffMessage', ''), }, }; From 48fc3ab5db24e2a05d6faf06ef09edbdbb3baf48 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Thu, 26 Apr 2018 13:12:27 +0200 Subject: [PATCH 039/162] Move "global switchoff" code from plugin to core --- .gitignore | 1 - .../Configure/components/StreamSettings.js | 33 +++++++++++++ .../Configure/containers/StreamSettings.js | 2 + .../src/tabs/stream/components/Stream.js | 11 ++++- .../src/tabs/stream/containers/Stream.js | 2 + errors.js | 19 ++++++++ graph/typeDefs.graphql | 14 ++++++ locales/de.yml | 3 ++ locales/en.yml | 3 ++ models/schema/setting.js | 8 ++++ .../.eslintrc.json | 23 --------- .../client/components/GlobalSwitchoff.js | 48 ------------------- .../client/components/styles.css | 0 .../client/containers/GlobalSwitchoff.js | 12 ----- .../client/index.js | 9 ---- .../client/translations.yml | 8 ---- plugins/talk-plugin-global-switchoff/index.js | 13 ----- .../server/hooks.js | 13 ----- .../server/index.js | 1 - .../server/resolvers.js | 10 ---- .../server/typeDefs.graphql | 10 ---- services/moderation/index.js | 2 + .../moderation/phases/commentingDisabled.js | 9 ++++ services/moderation/phases/index.js | 1 + 24 files changed, 105 insertions(+), 150 deletions(-) delete mode 100644 plugins/talk-plugin-global-switchoff/.eslintrc.json delete mode 100644 plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js delete mode 100644 plugins/talk-plugin-global-switchoff/client/components/styles.css delete mode 100644 plugins/talk-plugin-global-switchoff/client/containers/GlobalSwitchoff.js delete mode 100644 plugins/talk-plugin-global-switchoff/client/index.js delete mode 100644 plugins/talk-plugin-global-switchoff/client/translations.yml delete mode 100644 plugins/talk-plugin-global-switchoff/index.js delete mode 100644 plugins/talk-plugin-global-switchoff/server/hooks.js delete mode 100644 plugins/talk-plugin-global-switchoff/server/index.js delete mode 100644 plugins/talk-plugin-global-switchoff/server/resolvers.js delete mode 100644 plugins/talk-plugin-global-switchoff/server/typeDefs.graphql create mode 100644 services/moderation/phases/commentingDisabled.js diff --git a/.gitignore b/.gitignore index ce5146929..24022b9d7 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,6 @@ plugins/* !plugins/talk-plugin-facebook-auth !plugins/talk-plugin-featured-comments !plugins/talk-plugin-flag-details -!plugins/talk-plugin-global-switchoff !plugins/talk-plugin-google-auth !plugins/talk-plugin-ignore-user !plugins/talk-plugin-like diff --git a/client/coral-admin/src/routes/Configure/components/StreamSettings.js b/client/coral-admin/src/routes/Configure/components/StreamSettings.js index e6424848f..ce69da129 100644 --- a/client/coral-admin/src/routes/Configure/components/StreamSettings.js +++ b/client/coral-admin/src/routes/Configure/components/StreamSettings.js @@ -82,6 +82,20 @@ class StreamSettings extends React.Component { this.props.updatePending({ updater }); }; + updateGlobalSwitchoffEnable = () => { + const updater = { + globalSwitchoffEnable: { + $set: !this.props.settings.globalSwitchoffEnable, + }, + }; + this.props.updatePending({ updater }); + }; + + updateGlobalSwitchoffMessage = value => { + const updater = { globalSwitchoffMessage: { $set: value } }; + this.props.updatePending({ updater }); + }; + updateAutoClose = () => { const updater = { autoCloseStream: { $set: !this.props.settings.autoCloseStream }, @@ -192,6 +206,25 @@ class StreamSettings extends React.Component {   {t('configure.edit_comment_timeframe_text_post')} + +

    {t('configure.global_switchoff_desc')}

    +
    + +
    +
    ) : ( -

    {asset.settings.closedMessage}

    +
    + {asset.isClosed ? ( +

    {asset.settings.closedMessage}

    + ) : ( + + )} +
    )} diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index f1a973f03..99878a9b9 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -434,6 +434,8 @@ const fragments = { questionBoxIcon closedTimeout closedMessage + globalSwitchoffEnable + globalSwitchoffMessage charCountEnable charCount requireEmailConfirmation diff --git a/errors.js b/errors.js index da1287dd3..39cb6b8ff 100644 --- a/errors.js +++ b/errors.js @@ -161,6 +161,24 @@ class ErrAssetCommentingClosed extends TalkError { } } +// ErrCommentingDisabled is returned when a comment or action is attempted while +// commenting has been disabled site-wide. +class ErrCommentingDisabled extends TalkError { + constructor(message = null) { + super( + 'asset commenting is closed', + { + status: 400, + translation_key: 'COMMENTING_DISABLED', + }, + { + // Include the closedMessage in the metadata piece of the error. + message, + } + ); + } +} + /** * ErrAuthentication is returned when there is an error authenticating and the * message is provided. @@ -387,6 +405,7 @@ module.exports = { ErrAuthentication, ErrCannotIgnoreStaff, ErrCommentTooShort, + ErrCommentingDisabled, ErrContainsProfanity, ErrEditWindowHasEnded, ErrEmailAlreadyVerified, diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 03c97ce58..dda86291f 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -837,6 +837,13 @@ type Settings { # closed. closedMessage: String + # globalSwitchoffEnable will disable commenting site-wide. + globalSwitchoffEnable: Boolean + + # globalSwitchoffMessage will be shown above the comment stream while + # commenting is disabled site-wide. + globalSwitchoffMessage: String + # editCommentWindowLength is the length of time (in milliseconds) after a # comment is posted that it can still be edited by the author. editCommentWindowLength: Int @@ -1300,6 +1307,13 @@ input UpdateSettingsInput { # closed. closedMessage: String + # globalSwitchoffEnable will disable commenting site-wide. + globalSwitchoffEnable: Boolean + + # globalSwitchoffMessage will be shown above the comment stream while + # commenting is disabled site-wide. + globalSwitchoffMessage: String + # charCountEnable is true when the character count restriction is enabled. charCountEnable: Boolean diff --git a/locales/de.yml b/locales/de.yml index d2b247fbc..00cbb007c 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -136,6 +136,8 @@ de: enable_premod_links_description: "Moderatoren müssen jeden Kommentar, der einen Link enthält, freigeben, bevor er veröffentlicht wird." enable_questionbox: "Stellen Sie den Lesern eine Frage" enable_questionbox_description: "Diese Frage erscheint am Anfang des Kommentarbereichs. Regen Sie eine Diskussion an." + global_switchoff_title: "Kommentieren global deaktivieren" + global_switchoff_desc: "Verfassen Sie eine Nachricht, die angezeigt wird, solange das Kommentieren deaktiviert ist." hours: Stunden include_comment_stream: "Einleitung zum Kommentarbereich" include_comment_stream_desc: "Verfassen Sie eine Einleitung, die über jedem Kommentarbereich erscheint. Nützlich z.B. für Community-Richtlinien." @@ -223,6 +225,7 @@ de: LOGIN_MAXIMUM_EXCEEDED: "Sie haben zu häufig erfolglos versucht, sich anzumelden. Bitte warten Sie." PASSWORD_REQUIRED: "Passwort ist erforderlich" COMMENTING_CLOSED: "Kommentarbereich ist bereits geschlossen" + COMMENTING_DISABLED: "Die Kommentarfunktion ist derzeit abgeschaltet" NOT_FOUND: "Ressource nicht gefunden" ALREADY_EXISTS: "Ressource existiert bereits" INVALID_ASSET_URL: "Asset-URL ist ungültig" diff --git a/locales/en.yml b/locales/en.yml index e293fb40b..eae0e43c9 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -140,6 +140,8 @@ en: enable_premod_links_description: "Moderators must approve any comment containing a link before it is published." enable_questionbox: "Ask Readers a Question" enable_questionbox_description: "This question will appear at the top of this comment stream. Ask readers about a certain issue in the article or pose discussion questions etc." + global_switchoff_title: "Deactivate commenting site-wide" + global_switchoff_desc: "Write a message that will be displayed while commenting is deactivated." hours: Hours include_comment_stream: "Include Comment Stream Description for Readers" include_comment_stream_desc: "Write a message to be added to the top of your comment stream. Pose a topic include community guidelines etc." @@ -247,6 +249,7 @@ en: LOGIN_MAXIMUM_EXCEEDED: "You have made too many unsuccessful password attempts. Please wait." PASSWORD_REQUIRED: "Must input a password" COMMENTING_CLOSED: "Commenting is already closed" + COMMENTING_DISABLED: "Commenting is currently disabled on this site" NOT_FOUND: "Resource not found" ALREADY_EXISTS: "Resource already exists" INVALID_ASSET_URL: "Assert URL is invalid" diff --git a/models/schema/setting.js b/models/schema/setting.js index eb9dc76d1..a896c797e 100644 --- a/models/schema/setting.js +++ b/models/schema/setting.js @@ -66,6 +66,14 @@ const Setting = new Schema( type: String, default: 'Expired', }, + globalSwitchoffEnable: { + type: Boolean, + default: false, + }, + globalSwitchoffMessage: { + type: String, + default: '', + }, wordlist: { banned: { type: Array, diff --git a/plugins/talk-plugin-global-switchoff/.eslintrc.json b/plugins/talk-plugin-global-switchoff/.eslintrc.json deleted file mode 100644 index 9fe56bd14..000000000 --- a/plugins/talk-plugin-global-switchoff/.eslintrc.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "env": { - "browser": true, - "es6": true, - "mocha": true - }, - "parserOptions": { - "sourceType": "module", - "ecmaFeatures": { - "experimentalObjectRestSpread": true, - "jsx": true - } - }, - "parser": "babel-eslint", - "plugins": [ - "react" - ], - "rules": { - "react/jsx-uses-react": "error", - "react/jsx-uses-vars": "error", - "no-console": ["warn", { "allow": ["warn", "error"] }] - } -} diff --git a/plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js b/plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js deleted file mode 100644 index d676e7531..000000000 --- a/plugins/talk-plugin-global-switchoff/client/components/GlobalSwitchoff.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import ConfigureCard from 'coral-framework/components/ConfigureCard'; -import MarkdownEditor from 'coral-framework/components/MarkdownEditor'; -import t from 'coral-framework/services/i18n'; -import cn from 'classnames'; -import styles from './styles.css'; - -const plugin = 'talk-plugin-global-switchoff'; - -class GlobalSwitchoff extends React.Component { - updateGlobalSwitchoffEnable = () => { - const updater = { - globalSwitchoffEnable: { - $set: !this.props.settings.globalSwitchoffEnable, - }, - }; - this.props.updatePending({ updater }); - }; - - updateGlobalSwitchoffMessage = () => {}; - - render() { - const { settings } = this.props; - return ( - -

    {t(plugin + '.setting_desc')}

    -
    - -
    -
    - ); - } -} - -export default GlobalSwitchoff; diff --git a/plugins/talk-plugin-global-switchoff/client/components/styles.css b/plugins/talk-plugin-global-switchoff/client/components/styles.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/plugins/talk-plugin-global-switchoff/client/containers/GlobalSwitchoff.js b/plugins/talk-plugin-global-switchoff/client/containers/GlobalSwitchoff.js deleted file mode 100644 index b935bcb5a..000000000 --- a/plugins/talk-plugin-global-switchoff/client/containers/GlobalSwitchoff.js +++ /dev/null @@ -1,12 +0,0 @@ -import { gql } from 'react-apollo'; -import GlobalSwitchoff from '../components/GlobalSwitchoff'; -import { withFragments } from 'plugin-api/beta/client/hocs'; - -export default withFragments({ - settings: gql` - fragment TalkPlugin_GlobalSwitchoff_settings on Settings { - globalSwitchoffEnable - globalSwitchoffMessage - } - `, -})(GlobalSwitchoff); diff --git a/plugins/talk-plugin-global-switchoff/client/index.js b/plugins/talk-plugin-global-switchoff/client/index.js deleted file mode 100644 index c9994ce5e..000000000 --- a/plugins/talk-plugin-global-switchoff/client/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import GlobalSwitchoff from './containers/GlobalSwitchoff'; -import translations from './translations.yml'; - -export default { - translations, - slots: { - adminStreamSettings: [GlobalSwitchoff], - }, -}; diff --git a/plugins/talk-plugin-global-switchoff/client/translations.yml b/plugins/talk-plugin-global-switchoff/client/translations.yml deleted file mode 100644 index c135337f4..000000000 --- a/plugins/talk-plugin-global-switchoff/client/translations.yml +++ /dev/null @@ -1,8 +0,0 @@ -en: - talk-plugin-global-switchoff: - setting_title: 'Deactivate commenting globally' - setting_desc: 'Write a message that will be displayed while commenting is deactivated.' -de: - talk-plugin-global-switchoff: - setting_title: 'Kommentieren global deaktivieren' - setting_desc: 'Verfassen Sie eine Nachricht, die angezeigt wird, solange das Kommentieren deaktiviert ist.' diff --git a/plugins/talk-plugin-global-switchoff/index.js b/plugins/talk-plugin-global-switchoff/index.js deleted file mode 100644 index a318f8a11..000000000 --- a/plugins/talk-plugin-global-switchoff/index.js +++ /dev/null @@ -1,13 +0,0 @@ -const { readFileSync } = require('fs'); -const path = require('path'); -const hooks = require('./server/hooks'); -const resolvers = require('./server/resolvers'); - -module.exports = { - typeDefs: readFileSync( - path.join(__dirname, 'server/typeDefs.graphql'), - 'utf8' - ), - hooks, - resolvers, -}; diff --git a/plugins/talk-plugin-global-switchoff/server/hooks.js b/plugins/talk-plugin-global-switchoff/server/hooks.js deleted file mode 100644 index be0498fb6..000000000 --- a/plugins/talk-plugin-global-switchoff/server/hooks.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - RootMutation: { - updateSettings: { - async pre(_, { input }) { - input.metadata = { - ...input.metadata, - globalSwitchoffEnable: input.globalSwitchoffEnable, - }; - delete input.globalSwitchoffEnable; - }, - }, - }, -}; diff --git a/plugins/talk-plugin-global-switchoff/server/index.js b/plugins/talk-plugin-global-switchoff/server/index.js deleted file mode 100644 index f053ebf79..000000000 --- a/plugins/talk-plugin-global-switchoff/server/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; diff --git a/plugins/talk-plugin-global-switchoff/server/resolvers.js b/plugins/talk-plugin-global-switchoff/server/resolvers.js deleted file mode 100644 index f28e765fc..000000000 --- a/plugins/talk-plugin-global-switchoff/server/resolvers.js +++ /dev/null @@ -1,10 +0,0 @@ -const { get } = require('lodash'); - -module.exports = { - Settings: { - globalSwitchoffEnable: settings => - get(settings, 'metadata.globalSwitchoffEnable', false), - globalSwitchoffMessage: settings => - get(settings, 'metadata.globalSwitchoffMessage', ''), - }, -}; diff --git a/plugins/talk-plugin-global-switchoff/server/typeDefs.graphql b/plugins/talk-plugin-global-switchoff/server/typeDefs.graphql deleted file mode 100644 index efb4b80f6..000000000 --- a/plugins/talk-plugin-global-switchoff/server/typeDefs.graphql +++ /dev/null @@ -1,10 +0,0 @@ - -type Settings { - globalSwitchoffEnable: Boolean - globalSwitchoffMessage: String -} - -input UpdateSettingsInput { - globalSwitchoffEnable: Boolean - globalSwitchoffMessage: String -} diff --git a/services/moderation/index.js b/services/moderation/index.js index 27346e620..cc3a74a73 100644 --- a/services/moderation/index.js +++ b/services/moderation/index.js @@ -6,6 +6,7 @@ const { wordlist, commentLength, assetClosed, + commentingDisabled, karma, staff, links, @@ -36,6 +37,7 @@ const applyStatus = status => () => ({ status }); const phases = [ commentLength, assetClosed, + commentingDisabled, wordlist, staff, links, diff --git a/services/moderation/phases/commentingDisabled.js b/services/moderation/phases/commentingDisabled.js new file mode 100644 index 000000000..8c62d5d98 --- /dev/null +++ b/services/moderation/phases/commentingDisabled.js @@ -0,0 +1,9 @@ +const { ErrCommentingDisabled } = require('../../../errors'); + +// This phase checks to see if commenting is site-wide disabled. +module.exports = (ctx, comment, { asset }) => { + // Check to see if the asset has closed commenting... + if (asset.settings.globalSwitchoffEnable) { + throw new ErrCommentingDisabled(asset.settings.globalSwitchoffMessage); + } +}; diff --git a/services/moderation/phases/index.js b/services/moderation/phases/index.js index a1c2e7bf5..425c57197 100644 --- a/services/moderation/phases/index.js +++ b/services/moderation/phases/index.js @@ -1,6 +1,7 @@ module.exports.wordlist = require('./wordlist'); module.exports.commentLength = require('./commentLength'); module.exports.assetClosed = require('./assetClosed'); +module.exports.commentingDisabled = require('./commentingDisabled'); module.exports.karma = require('./karma'); module.exports.staff = require('./staff'); module.exports.links = require('./links'); From f28a3f2f4e99012171b969d9ad3cfe5ec90fdfef Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Thu, 26 Apr 2018 13:49:38 +0200 Subject: [PATCH 040/162] Add test case: err if trying to comment while it's disabled --- test/server/graph/mutations/createComment.js | 48 ++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/server/graph/mutations/createComment.js b/test/server/graph/mutations/createComment.js index fbbbf4d38..373045efc 100644 --- a/test/server/graph/mutations/createComment.js +++ b/test/server/graph/mutations/createComment.js @@ -179,6 +179,54 @@ describe('graph.mutations.createComment', () => { }); }); + describe('assets while commenting is disabled', () => { + [ + { + disabled: false, + error: null, + }, + { + disabled: true, + error: 'COMMENTING_DISABLED', + }, + ].forEach(({ disabled, error }) => { + describe(`commentingDisabled=${disabled}`, () => { + beforeEach(() => + AssetModel.create({ + id: '123', + settings: { globalSwitchoffEnable: disabled }, + }) + ); + + it( + error ? 'does not create the comment' : 'creates the comment', + () => { + const context = new Context({ user: new UserModel({}) }); + + return graphql(schema, query, {}, context).then( + ({ data, errors }) => { + expect(errors).to.be.undefined; + if (error) { + expect(data.createComment).to.have.property('comment').null; + expect(data.createComment).to.have.property('errors').not + .null; + expect(data.createComment.errors[0]).to.have.property( + 'translation_key', + error + ); + } else { + expect(data.createComment).to.have.property('comment').not + .null; + expect(data.createComment).to.have.property('errors').null; + } + } + ); + } + ); + }); + }); + }); + describe('comments made with different asset moderation settings', () => { [ { moderation: 'PRE', status: 'PREMOD' }, From d7313570019385e2922ddfdbfcac74ad9cf54447 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 11 May 2018 12:20:26 +0200 Subject: [PATCH 041/162] Remove plugin docs for global-switchoff. Not a plugin anymore. --- .../plugin/talk-plugin-global-switchoff.md | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 docs/source/plugin/talk-plugin-global-switchoff.md diff --git a/docs/source/plugin/talk-plugin-global-switchoff.md b/docs/source/plugin/talk-plugin-global-switchoff.md deleted file mode 100644 index 317fb4b3b..000000000 --- a/docs/source/plugin/talk-plugin-global-switchoff.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: talk-plugin-global-switchoff -permalink: /plugin/talk-plugin-global-switchoff/ -layout: plugin -plugin: - name: talk-plugin-global-switchoff - provides: - - Client - - Server ---- - -Add a switch to the settings page that disables commenting globally. - -## Installation - -TBD - -Add `"talk-plugin-global-switchoff"` to the `plugins.json` in your Talk installation. -This plugin provides a server and a client side implementation. - -## Server implementation - -### How does this work? - -TODO From bae20c38ec9fcc27d3d90144d06081ceea9049d9 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 11 May 2018 12:21:13 +0200 Subject: [PATCH 042/162] Rename globalSwitchoff to disableCommenting. --- .../Configure/components/StreamSettings.js | 26 +++++++++---------- .../Configure/containers/StreamSettings.js | 4 +-- .../src/tabs/stream/components/Stream.js | 4 +-- .../src/tabs/stream/containers/Stream.js | 4 +-- graph/typeDefs.graphql | 16 ++++++------ locales/de.yml | 4 +-- locales/en.yml | 4 +-- models/schema/setting.js | 4 +-- .../moderation/phases/commentingDisabled.js | 4 +-- test/server/graph/mutations/createComment.js | 2 +- 10 files changed, 36 insertions(+), 36 deletions(-) diff --git a/client/coral-admin/src/routes/Configure/components/StreamSettings.js b/client/coral-admin/src/routes/Configure/components/StreamSettings.js index ce69da129..1f661fd72 100644 --- a/client/coral-admin/src/routes/Configure/components/StreamSettings.js +++ b/client/coral-admin/src/routes/Configure/components/StreamSettings.js @@ -82,17 +82,17 @@ class StreamSettings extends React.Component { this.props.updatePending({ updater }); }; - updateGlobalSwitchoffEnable = () => { + updateDisableCommenting = () => { const updater = { - globalSwitchoffEnable: { - $set: !this.props.settings.globalSwitchoffEnable, + disableCommenting: { + $set: !this.props.settings.disableCommenting, }, }; this.props.updatePending({ updater }); }; - updateGlobalSwitchoffMessage = value => { - const updater = { globalSwitchoffMessage: { $set: value } }; + updateDisableCommentingMessage = value => { + const updater = { disableCommentingMessage: { $set: value } }; this.props.updatePending({ updater }); }; @@ -207,21 +207,21 @@ class StreamSettings extends React.Component { {t('configure.edit_comment_timeframe_text_post')}
    -

    {t('configure.global_switchoff_desc')}

    +

    {t('configure.disable_commenting_desc')}

    diff --git a/client/coral-admin/src/routes/Configure/containers/StreamSettings.js b/client/coral-admin/src/routes/Configure/containers/StreamSettings.js index 6eaff0d9a..faffb1aec 100644 --- a/client/coral-admin/src/routes/Configure/containers/StreamSettings.js +++ b/client/coral-admin/src/routes/Configure/containers/StreamSettings.js @@ -39,8 +39,8 @@ export default compose( autoCloseStream closedTimeout closedMessage - globalSwitchoffEnable - globalSwitchoffMessage + disableCommenting + disableCommentingMessage ${getSlotFragmentSpreads(slots, 'settings')} } `, diff --git a/client/coral-embed-stream/src/tabs/stream/components/Stream.js b/client/coral-embed-stream/src/tabs/stream/components/Stream.js index fbe49a069..2b7396720 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Stream.js @@ -216,7 +216,7 @@ class Stream extends React.Component { currentUser, } = this.props; const { keepCommentBox } = this.state; - const open = !(asset.isClosed || asset.settings.globalSwitchoffEnable); + const open = !(asset.isClosed || asset.settings.disableCommenting); const banned = get(currentUser, 'status.banned.status'); const suspensionUntil = get(currentUser, 'status.suspension.until'); @@ -298,7 +298,7 @@ class Stream extends React.Component { {asset.isClosed ? (

    {asset.settings.closedMessage}

    ) : ( - + )}
    )} diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 99878a9b9..d6ca27558 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -434,8 +434,8 @@ const fragments = { questionBoxIcon closedTimeout closedMessage - globalSwitchoffEnable - globalSwitchoffMessage + disableCommenting + disableCommentingMessage charCountEnable charCount requireEmailConfirmation diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index dda86291f..07947af6e 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -837,12 +837,12 @@ type Settings { # closed. closedMessage: String - # globalSwitchoffEnable will disable commenting site-wide. - globalSwitchoffEnable: Boolean + # disableCommenting will disable commenting site-wide. + disableCommenting: Boolean - # globalSwitchoffMessage will be shown above the comment stream while + # disableCommentingMessage will be shown above the comment stream while # commenting is disabled site-wide. - globalSwitchoffMessage: String + disableCommentingMessage: String # editCommentWindowLength is the length of time (in milliseconds) after a # comment is posted that it can still be edited by the author. @@ -1307,12 +1307,12 @@ input UpdateSettingsInput { # closed. closedMessage: String - # globalSwitchoffEnable will disable commenting site-wide. - globalSwitchoffEnable: Boolean + # disableCommenting will disable commenting site-wide. + disableCommenting: Boolean - # globalSwitchoffMessage will be shown above the comment stream while + # disableCommentingMessage will be shown above the comment stream while # commenting is disabled site-wide. - globalSwitchoffMessage: String + disableCommentingMessage: String # charCountEnable is true when the character count restriction is enabled. charCountEnable: Boolean diff --git a/locales/de.yml b/locales/de.yml index 00cbb007c..15404856c 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -121,6 +121,8 @@ de: custom_css_url_desc: "URL eines CSS-Stylesheets zum Überschreiben des Standard-Designs" days: Tage description: "Als Administrator können Sie die Einstellungen für den Kommentarbereich dieses Artikels anpassen:" + disable_commenting_title: "Kommentieren global deaktivieren" + disable_commenting_desc: "Verfassen Sie eine Nachricht, die angezeigt wird, solange das Kommentieren deaktiviert ist." domain_list_text: "Geben Sie Domains an, für die diese Talk-Instanz freigegeben werden soll, z.B. für lokale Test- oder Produktionsumgebungen (Bsp.: localhost:3000 staging.domain.com domain.com)." domain_list_title: "Zugelassene Domains" edit_comment_timeframe_heading: "Zeitlimit zur Bearbeitung von Kommentaren" @@ -136,8 +138,6 @@ de: enable_premod_links_description: "Moderatoren müssen jeden Kommentar, der einen Link enthält, freigeben, bevor er veröffentlicht wird." enable_questionbox: "Stellen Sie den Lesern eine Frage" enable_questionbox_description: "Diese Frage erscheint am Anfang des Kommentarbereichs. Regen Sie eine Diskussion an." - global_switchoff_title: "Kommentieren global deaktivieren" - global_switchoff_desc: "Verfassen Sie eine Nachricht, die angezeigt wird, solange das Kommentieren deaktiviert ist." hours: Stunden include_comment_stream: "Einleitung zum Kommentarbereich" include_comment_stream_desc: "Verfassen Sie eine Einleitung, die über jedem Kommentarbereich erscheint. Nützlich z.B. für Community-Richtlinien." diff --git a/locales/en.yml b/locales/en.yml index eae0e43c9..283ec4301 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -124,6 +124,8 @@ en: custom_css_url_desc: "URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external." days: Days description: "Change the comment settings on this story." + disable_commenting_title: "Deactivate commenting site-wide" + disable_commenting_desc: "Write a message that will be displayed while commenting is deactivated." domain_list_text: "Enter the domains you would like to permit for Talk e.g. your local staging and production environments (ex. localhost:3000 staging.domain.com domain.com)." domain_list_title: "Permitted Domains" edit_info: "Edit Info" @@ -140,8 +142,6 @@ en: enable_premod_links_description: "Moderators must approve any comment containing a link before it is published." enable_questionbox: "Ask Readers a Question" enable_questionbox_description: "This question will appear at the top of this comment stream. Ask readers about a certain issue in the article or pose discussion questions etc." - global_switchoff_title: "Deactivate commenting site-wide" - global_switchoff_desc: "Write a message that will be displayed while commenting is deactivated." hours: Hours include_comment_stream: "Include Comment Stream Description for Readers" include_comment_stream_desc: "Write a message to be added to the top of your comment stream. Pose a topic include community guidelines etc." diff --git a/models/schema/setting.js b/models/schema/setting.js index a896c797e..d75bed29e 100644 --- a/models/schema/setting.js +++ b/models/schema/setting.js @@ -66,11 +66,11 @@ const Setting = new Schema( type: String, default: 'Expired', }, - globalSwitchoffEnable: { + disableCommenting: { type: Boolean, default: false, }, - globalSwitchoffMessage: { + disableCommentingMessage: { type: String, default: '', }, diff --git a/services/moderation/phases/commentingDisabled.js b/services/moderation/phases/commentingDisabled.js index 8c62d5d98..060da473c 100644 --- a/services/moderation/phases/commentingDisabled.js +++ b/services/moderation/phases/commentingDisabled.js @@ -3,7 +3,7 @@ const { ErrCommentingDisabled } = require('../../../errors'); // This phase checks to see if commenting is site-wide disabled. module.exports = (ctx, comment, { asset }) => { // Check to see if the asset has closed commenting... - if (asset.settings.globalSwitchoffEnable) { - throw new ErrCommentingDisabled(asset.settings.globalSwitchoffMessage); + if (asset.settings.disableCommenting) { + throw new ErrCommentingDisabled(asset.settings.disableCommentingMessage); } }; diff --git a/test/server/graph/mutations/createComment.js b/test/server/graph/mutations/createComment.js index 373045efc..662404bf2 100644 --- a/test/server/graph/mutations/createComment.js +++ b/test/server/graph/mutations/createComment.js @@ -194,7 +194,7 @@ describe('graph.mutations.createComment', () => { beforeEach(() => AssetModel.create({ id: '123', - settings: { globalSwitchoffEnable: disabled }, + settings: { disableCommenting: disabled }, }) ); From 1236e7a5048385f1c9dc7e8ddd42713fb53fc9ed Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 11 May 2018 12:52:44 +0200 Subject: [PATCH 043/162] Disable/hide the reply buttons when commenting is disabled. --- .../coral-embed-stream/src/tabs/stream/components/Stream.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/tabs/stream/components/Stream.js b/client/coral-embed-stream/src/tabs/stream/components/Stream.js index 2b7396720..a07f8ff8b 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Stream.js @@ -182,7 +182,9 @@ class Stream extends React.Component { setActiveReplyBox={setActiveReplyBox} activeReplyBox={activeReplyBox} notify={notify} - disableReply={asset.isClosed} + disableReply={ + asset.isClosed || asset.settings.disableCommenting + } postComment={postComment} currentUser={currentUser} postFlag={postFlag} From f57f8397feddc79c2ceb3da240fd2b18b6f2c29b Mon Sep 17 00:00:00 2001 From: Mendel Konikov Date: Fri, 11 May 2018 11:01:15 -0400 Subject: [PATCH 044/162] Capitalize "Information" --- locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.yml b/locales/en.yml index 9a26fbdb6..36cf956b4 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -162,7 +162,7 @@ en: suspect_word_title: "Suspect words list" suspect_word_text: "Comments which contain these words or phrases (not case-sensitive) will be highlighted in the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list." tech_settings: "Tech Settings" - organization_information: "Organization information" + organization_information: "Organization Information" organization_info_copy: "We use this information in email notifications generated by Talk. This connects the messages to your organization, and provides a way for users to contact you if they have an issue with their account." organization_info_copy_2: "We recommend creating a generic email account (eg. community@yournewsroom.com) for this purpose. This means it can remain consistent over time, and doesn't expose a name that users could target if their account were blocked." organization_details: "Organization Details" From fc5621a06042de9b0ca42d3da720e4cfcbde1477 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 11 May 2018 17:12:28 +0200 Subject: [PATCH 045/162] Add reference to story --- plugins/talk-plugin-rich-text/client/components/Editor.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/talk-plugin-rich-text/client/components/Editor.js b/plugins/talk-plugin-rich-text/client/components/Editor.js index 089900e1a..99d2be617 100644 --- a/plugins/talk-plugin-rich-text/client/components/Editor.js +++ b/plugins/talk-plugin-rich-text/client/components/Editor.js @@ -41,6 +41,8 @@ class Editor extends React.Component { } }); } + + // Skip IOS due to a bug, see https://www.pivotaltracker.com/story/show/157434928 if (this.props.isReply && !bowser.ios) { this.ref.focus(); } From 29ffe287cdb7afdd6f9c954bccc6b2cefcc92153 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 11 May 2018 09:21:55 -0600 Subject: [PATCH 046/162] Delete package-lock.json --- package-lock.json | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index d6105c594..000000000 --- a/package-lock.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "talk", - "version": "4.3.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "exenv": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", - "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" - }, - "react-side-effect": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-1.1.5.tgz", - "integrity": "sha512-Z2ZJE4p/jIfvUpiUMRydEVpQRf2f8GMHczT6qLcARmX7QRb28JDBTpnM2g/i5y/p7ZDEXYGHWg0RbhikE+hJRw==", - "requires": { - "exenv": "1.2.2", - "shallowequal": "1.0.2" - } - }, - "shallowequal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.0.2.tgz", - "integrity": "sha512-zlVXeVUKvo+HEv1e2KQF/csyeMKx2oHvatQ9l6XjCUj3agvC8XGf6R9HvIPDSmp8FNPvx7b5kaEJTRi7CqxtEw==" - } - } -} From 42d4abf8426b499bc9722c7ac1a2ea5f29b7fc2c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 11 May 2018 09:33:18 -0600 Subject: [PATCH 047/162] review updates --- plugins/talk-plugin-local-auth/client/actions.js | 2 +- .../client/components/AddEmailAddressDialog.js | 6 ++++-- plugins/talk-plugin-local-auth/client/constants.js | 2 +- plugins/talk-plugin-local-auth/client/reducer.js | 2 +- plugins/talk-plugin-local-auth/translations.yml | 1 + 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/talk-plugin-local-auth/client/actions.js b/plugins/talk-plugin-local-auth/client/actions.js index 9f2c9cfed..972e1f6fd 100644 --- a/plugins/talk-plugin-local-auth/client/actions.js +++ b/plugins/talk-plugin-local-auth/client/actions.js @@ -1,7 +1,7 @@ import * as actions from './constants'; export const startAttach = () => ({ - type: actions.STARTED_ATTACH, + type: actions.START_ATTACH, }); export const finishAttach = () => ({ diff --git a/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js b/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js index 76131c895..a5bfd7096 100644 --- a/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js +++ b/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js @@ -124,8 +124,10 @@ class AddEmailAddressDialog extends React.Component { password: confirmPassword, }); - // TODO: translate - this.props.notify('success', 'Email Added!'); + this.props.notify( + 'success', + t('talk-plugin-local-auth.add_email.added.alert') + ); this.goToNextStep(); } catch (err) { this.props.notify('error', getErrorMessages(err)); diff --git a/plugins/talk-plugin-local-auth/client/constants.js b/plugins/talk-plugin-local-auth/client/constants.js index 86f5242f7..d410a10fa 100644 --- a/plugins/talk-plugin-local-auth/client/constants.js +++ b/plugins/talk-plugin-local-auth/client/constants.js @@ -1,4 +1,4 @@ const prefix = 'TALK_LOCAL_AUTH'; -export const STARTED_ATTACH = `${prefix}_STARTED_ATTACH`; +export const START_ATTACH = `${prefix}_START_ATTACH`; export const FINISH_ATTACH = `${prefix}_FINISH_ATTACH`; diff --git a/plugins/talk-plugin-local-auth/client/reducer.js b/plugins/talk-plugin-local-auth/client/reducer.js index 403a84814..11666e505 100644 --- a/plugins/talk-plugin-local-auth/client/reducer.js +++ b/plugins/talk-plugin-local-auth/client/reducer.js @@ -6,7 +6,7 @@ const initialState = { export default function reducer(state = initialState, action) { switch (action.type) { - case actions.STARTED_ATTACH: + case actions.START_ATTACH: return { inProgress: true, }; diff --git a/plugins/talk-plugin-local-auth/translations.yml b/plugins/talk-plugin-local-auth/translations.yml index 7180268ea..5fd2264e6 100644 --- a/plugins/talk-plugin-local-auth/translations.yml +++ b/plugins/talk-plugin-local-auth/translations.yml @@ -67,6 +67,7 @@ en: subtitle: "Need to change your email address?" description_2: "You can change your account settings by visiting" path: "My Profile > Settings" + alert: "Email Added!" es: talk-plugin-local-auth: change_password: From faa0ee8ceb2f6ef1504906e4e49060bffe1ce465 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 11 May 2018 10:22:45 -0600 Subject: [PATCH 048/162] added global wrap --- client/coral-admin/src/components/App.css | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/client/coral-admin/src/components/App.css b/client/coral-admin/src/components/App.css index 1404a9293..39c7cf942 100644 --- a/client/coral-admin/src/components/App.css +++ b/client/coral-admin/src/components/App.css @@ -1,9 +1,11 @@ -html, body, #root, #root > div { - min-height: 100%; -} +:global { + html, body, #root, #root > div { + min-height: 100%; + } -body { - margin: 0; - background-color: #FAFAFA; - font-family: 'Roboto', sans-serif; + body { + margin: 0; + background-color: #FAFAFA; + font-family: 'Roboto', sans-serif; + } } From 622b79b4910a74bf2d3303d7050d73018bdec9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Curcio?= Date: Fri, 11 May 2018 13:22:48 -0300 Subject: [PATCH 049/162] Removing unnecessary functions --- .../src/tabs/profile/components/Comment.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/profile/components/Comment.js b/client/coral-embed-stream/src/tabs/profile/components/Comment.js index 84293096b..c9bdcb0ac 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/profile/components/Comment.js @@ -11,16 +11,6 @@ import { getTotalReactionsCount } from 'coral-framework/utils'; import t from 'coral-framework/services/i18n'; class Comment extends React.Component { - goToStory = () => { - this.props.navigate(this.props.comment.asset.url); - }; - - goToConversation = () => { - this.props.navigate( - `${this.props.comment.asset.url}?commentId=${this.props.comment.id}` - ); - }; - render() { const { comment, root } = this.props; const reactionCount = getTotalReactionsCount(comment.action_summaries); @@ -76,7 +66,7 @@ class Comment extends React.Component {
    {t('common.story')}:{' '} {comment.asset.title ? comment.asset.title : comment.asset.url} @@ -86,7 +76,7 @@ class Comment extends React.Component {
    • - + {t('view_conversation')} From a571cf830d554a686c761d85d3384f021ee2b0a0 Mon Sep 17 00:00:00 2001 From: okbel Date: Fri, 11 May 2018 13:32:55 -0300 Subject: [PATCH 050/162] Adding target --- .../src/tabs/profile/components/Comment.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/tabs/profile/components/Comment.js b/client/coral-embed-stream/src/tabs/profile/components/Comment.js index c9bdcb0ac..badeb4262 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/profile/components/Comment.js @@ -67,6 +67,7 @@ class Comment extends React.Component { {t('common.story')}:{' '} {comment.asset.title ? comment.asset.title : comment.asset.url} @@ -76,7 +77,13 @@ class Comment extends React.Component {
      • - + {t('view_conversation')} From f018ccf33ee894ed75c097070b623cfb71de038c Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 11 May 2018 18:38:41 +0200 Subject: [PATCH 051/162] Prevent showing add email address dialog when usernam is not set --- .../client/containers/AddEmailAddressDialog.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js b/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js index 4cc80d022..d8ccd18b0 100644 --- a/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js +++ b/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js @@ -19,6 +19,13 @@ const withData = withFragments({ me { id email + state { + status { + username { + status + } + } + } } settings { requireEmailConfirmation @@ -33,6 +40,7 @@ export default compose( withData, excludeIf( ({ root: { me }, inProgress }) => + me.state.status.username.status === 'UNSET' || !((me && !me.email) || (me && me.email && inProgress)) ) )(AddEmailAddressDialog); From 6cf4203c9b7ca9db28ec253ef469ddeb45398cea Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 11 May 2018 11:38:54 -0600 Subject: [PATCH 052/162] Update translations.yml --- plugins/talk-plugin-local-auth/translations.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/talk-plugin-local-auth/translations.yml b/plugins/talk-plugin-local-auth/translations.yml index 5fd2264e6..7f67e33bd 100644 --- a/plugins/talk-plugin-local-auth/translations.yml +++ b/plugins/talk-plugin-local-auth/translations.yml @@ -2,7 +2,7 @@ en: email: email_change_original: subject: Email change - body: Your email address has been changed from {0} to {1}. If you did not initiate this change, please contact {2}. # TODO: update translation + body: Your email address has been changed from {0} to {1}. If you did not request this change, please contact {2}. error: NO_LOCAL_PROFILE: No existing email address is associated with this account. LOCAL_PROFILE: An email address is already associated with this account. From f0dcd844a787d5309b90509806389ddfccba4455 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 11 May 2018 20:44:45 +0200 Subject: [PATCH 053/162] Fix excludeIf condition --- .../client/containers/AddEmailAddressDialog.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js b/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js index d8ccd18b0..44ee17df7 100644 --- a/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js +++ b/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js @@ -40,7 +40,8 @@ export default compose( withData, excludeIf( ({ root: { me }, inProgress }) => + !me || me.state.status.username.status === 'UNSET' || - !((me && !me.email) || (me && me.email && inProgress)) + (me.email && !inProgress) ) )(AddEmailAddressDialog); From 7d8d5a6115ef0b77103c39143368f6655f7f46ee Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 11 May 2018 20:46:32 +0200 Subject: [PATCH 054/162] Use lodash get --- .../client/containers/AddEmailAddressDialog.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js b/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js index 44ee17df7..f6de3b39c 100644 --- a/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js +++ b/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js @@ -5,6 +5,7 @@ import AddEmailAddressDialog from '../components/AddEmailAddressDialog'; import { notify } from 'coral-framework/actions/notification'; import { withAttachLocalAuth } from '../hocs'; import { startAttach, finishAttach } from '../actions'; +import get from 'lodash/get'; const mapStateToProps = ({ talkPluginLocalAuth: state }) => ({ inProgress: state.inProgress, @@ -41,7 +42,7 @@ export default compose( excludeIf( ({ root: { me }, inProgress }) => !me || - me.state.status.username.status === 'UNSET' || + get(me, 'state.status.username.status') === 'UNSET' || (me.email && !inProgress) ) )(AddEmailAddressDialog); From bde1dff33b4868de902891e07c45c54635574f39 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 11 May 2018 14:45:03 -0600 Subject: [PATCH 055/162] changed to seperate slot --- client/coral-admin/src/components/SignIn.js | 2 +- plugins/talk-plugin-facebook-auth/client/index.js | 1 + plugins/talk-plugin-google-auth/client/index.js | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/components/SignIn.js b/client/coral-admin/src/components/SignIn.js index ddd69d3ac..a067ace1d 100644 --- a/client/coral-admin/src/components/SignIn.js +++ b/client/coral-admin/src/components/SignIn.js @@ -35,7 +35,7 @@ class SignIn extends React.Component { const { email, password, errorMessage, requireRecaptcha } = this.props; return (
        - +
        {errorMessage && {errorMessage}} Date: Mon, 14 May 2018 13:57:46 +0200 Subject: [PATCH 056/162] Add new slot 'commentTombstone'. With this slot plugins can override the default tombstone for deleted comments. --- .../src/tabs/stream/components/Comment.js | 14 +++++++++++++- .../src/tabs/stream/components/CommentTombstone.js | 1 + .../src/tabs/stream/containers/Comment.js | 1 + docs/source/api/slots.md | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.js b/client/coral-embed-stream/src/tabs/stream/components/Comment.js index 32778f7ea..3c840734e 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.js @@ -745,10 +745,22 @@ export default class Comment extends React.Component { const id = `c_${comment.id}`; + // props that are passed down the slots. + const slotPassthrough = { + action: 'deleted', + comment, + }; + return (
        {isCommentDeleted(comment) ? ( - + ) : (
        {this.renderComment()} diff --git a/client/coral-embed-stream/src/tabs/stream/components/CommentTombstone.js b/client/coral-embed-stream/src/tabs/stream/components/CommentTombstone.js index 95184dffd..e08f45d95 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/CommentTombstone.js +++ b/client/coral-embed-stream/src/tabs/stream/components/CommentTombstone.js @@ -39,6 +39,7 @@ class CommentTombstone extends React.Component { CommentTombstone.propTypes = { action: PropTypes.string, + comment: PropTypes.object, onUndo: PropTypes.func, }; diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Comment.js b/client/coral-embed-stream/src/tabs/stream/containers/Comment.js index dca7fadaa..028d6a478 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Comment.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Comment.js @@ -24,6 +24,7 @@ const slots = [ 'commentAuthorName', 'commentAuthorTags', 'commentTimestamp', + 'commentTombstone', 'commentContent', ]; diff --git a/docs/source/api/slots.md b/docs/source/api/slots.md index a2e7cc04d..662a025e0 100644 --- a/docs/source/api/slots.md +++ b/docs/source/api/slots.md @@ -99,6 +99,7 @@ You won't have to use this to build plugins, but it's helpful to find where to e * `commentReactions` * `commentActions` * `commentInputArea` +* `commentTombstone` * `draftArea` * `streamSettings` From 8e392ef32d454c4aadafb9598010a69da3253235 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 14 May 2018 15:01:51 +0200 Subject: [PATCH 057/162] Remove superfluous className --- client/coral-embed-stream/src/tabs/stream/components/Comment.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.js b/client/coral-embed-stream/src/tabs/stream/components/Comment.js index 3c840734e..63740764f 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.js @@ -756,7 +756,6 @@ export default class Comment extends React.Component { {isCommentDeleted(comment) ? ( Date: Mon, 14 May 2018 13:31:24 -0300 Subject: [PATCH 058/162] sepatate state --- .../components/ChangeEmailContentDialog.js | 92 +++++++++++++++++-- .../client/components/Profile.js | 7 +- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js b/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js index f29dd8d86..b0fdeb14d 100644 --- a/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js +++ b/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js @@ -4,10 +4,77 @@ import styles from './ChangeEmailContentDialog.css'; import InputField from './InputField'; import { Button } from 'plugin-api/beta/client/components/ui'; import { t } from 'plugin-api/beta/client/services'; +import validate from 'coral-framework/helpers/validate'; +import errorMsj from 'coral-framework/helpers/error'; + +const initialState = { + showError: false, + formData: {}, + errors: {}, +}; class ChangeEmailContentDialog extends React.Component { - state = { - showError: false, + state = initialState; + + clearForm = () => { + this.setState(initialState); + }; + + addError = err => { + this.setState(({ errors }) => ({ + errors: { ...errors, ...err }, + })); + }; + + removeError = errKey => { + this.setState(state => { + const { [errKey]: _, ...errors } = state.errors; + return { + errors, + }; + }); + }; + + fieldValidation = (value, type, name) => { + if (!value.length) { + this.addError({ + [name]: t('talk-plugin-local-auth.change_password.required_field'), + }); + } else if (!validate[type](value)) { + this.addError({ [name]: errorMsj[type] }); + } else { + this.removeError(name); + } + }; + + onChange = e => { + const { name, value, type, dataset } = e.target; + const validationType = dataset.validationType || type; + + this.setState( + state => ({ + formData: { + ...state.formData, + [name]: value, + }, + }), + () => { + this.fieldValidation(value, validationType, name); + } + ); + }; + + hasError = err => { + return Object.keys(this.state.errors).indexOf(err) !== -1; + }; + + isSaveEnabled = () => { + const formHasErrors = !!Object.keys(this.state.errors).length; + return !formHasErrors; + }; + + getError = errorKey => { + return this.state.errors[errorKey]; }; showError = () => { @@ -16,24 +83,31 @@ class ChangeEmailContentDialog extends React.Component { }); }; + cancel = () => { + this.clearForm(); + this.disableEditing(); + }; + confirmChanges = async e => { e.preventDefault(); + const { confirmPassword = '' } = this.state.formData; + if (this.formHasError()) { this.showError(); return; } - await this.props.save(); + await this.props.save(confirmPassword); this.props.next(); }; - formHasError = () => this.props.hasError('confirmPassword'); + formHasError = () => this.hasError('confirmPassword'); render() { return (
        - + ×

        @@ -59,17 +133,17 @@ class ChangeEmailContentDialog extends React.Component { label={t('talk-plugin-local-auth.change_email.enter_password')} name="confirmPassword" type="password" - onChange={this.props.onChange} + onChange={this.onChange} defaultValue="" - hasError={this.props.hasError('confirmPassword')} - errorMsg={this.props.getError('confirmPassword')} + hasError={this.hasError('confirmPassword')} + errorMsg={this.getError('confirmPassword')} showError={this.state.showError} columnDisplay />
        diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js index e47a162ef..c22843c8f 100644 --- a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js @@ -204,7 +204,7 @@ class ModerationQueue extends React.Component { } componentDidUpdate(prev) { - const { commentCount, selectedCommentId } = this.props; + const { selectedCommentId, hasNextPage } = this.props; const switchedToMultiMode = prev.singleView && !this.props.singleView; const switchedMode = prev.singleView !== this.props.singleView; @@ -212,7 +212,6 @@ class ModerationQueue extends React.Component { prev.selectedCommentId !== selectedCommentId && selectedCommentId; const moderatedLastComment = prev.comments.length > 0 && this.getCommentCountWithoutDagling() === 0; - const hasMoreComment = commentCount > 0; if (switchedToMultiMode) { // Reflow virtual list. @@ -223,7 +222,7 @@ class ModerationQueue extends React.Component { this.scrollToSelectedComment(); } - if (moderatedLastComment && hasMoreComment) { + if (moderatedLastComment && hasNextPage) { this.props.loadMore(); } } @@ -240,10 +239,7 @@ class ModerationQueue extends React.Component { const index = view.findIndex( ({ id }) => id === this.props.selectedCommentId ); - if ( - index === view.length - 1 && - this.getCommentCountWithoutDagling() !== this.props.commentCount - ) { + if (index === view.length - 1 && this.props.hasNextPage) { await this.props.loadMore(); this.selectDown(); return; @@ -467,7 +463,6 @@ ModerationQueue.propTypes = { acceptComment: PropTypes.func.isRequired, commentBelongToQueue: PropTypes.func.isRequired, cleanUpQueue: PropTypes.func.isRequired, - commentCount: PropTypes.number.isRequired, loadMore: PropTypes.func.isRequired, singleView: PropTypes.bool, isLoadingMore: PropTypes.bool, diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index c908404cb..eae2f875f 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -314,11 +314,11 @@ class ModerationContainer extends Component { const currentQueueConfig = Object.assign({}, this.props.queueConfig); - if (premodEnabled && root.newCount === 0) { + if (premodEnabled && root.new.nodes.length === 0) { delete currentQueueConfig.new; } - if (!premodEnabled && root.premodCount === 0) { + if (!premodEnabled && root.premod.nodes.length === 0) { delete currentQueueConfig.premod; } @@ -456,7 +456,11 @@ const withModQueueQuery = withQuery( } ` )} - ${Object.keys(queueConfig).map( + ${ + '' + /* + TODO: eventually we'll reintroduce counting.. + Object.keys(queueConfig).map( queue => ` ${queue}Count: commentCount(query: { excludeDeleted: true, @@ -478,7 +482,8 @@ const withModQueueQuery = withQuery( asset_id: $asset_id, }) ` - )} + )*/ + } asset(id: $asset_id) @skip(if: $allAssets) { id title From 12e848aace2fd03e79006c67f86ca6e07bc4919d Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 14 May 2018 19:27:18 +0200 Subject: [PATCH 060/162] Exclude deleted when loading more --- .../coral-admin/src/routes/Moderation/containers/Moderation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index eae2f875f..e3f35081c 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -402,7 +402,7 @@ const COMMENT_RESET_SUBSCRIPTION = gql` const LOAD_MORE_QUERY = gql` query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Cursor, $sortOrder: SORT_ORDER, $asset_id: ID, $tags:[String!], $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) { - comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sortOrder: $sortOrder, action_type: $action_type, tags: $tags}) { + comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sortOrder: $sortOrder, action_type: $action_type, tags: $tags, excludeDeleted: true}) { nodes { ...${getDefinitionName(Comment.fragments.comment)} } From ab7e5bc858b0bec3865bff1688aec972a3cb8016 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 14 May 2018 20:05:18 +0200 Subject: [PATCH 061/162] No indicator on new queue --- .../coral-admin/src/routes/Moderation/components/Moderation.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/components/Moderation.js b/client/coral-admin/src/routes/Moderation/components/Moderation.js index 0c5ae4c7c..7ea7856ca 100644 --- a/client/coral-admin/src/routes/Moderation/components/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/components/Moderation.js @@ -140,8 +140,7 @@ class Moderation extends Component { name: queueConfig[queue].name, icon: queueConfig[queue].icon, indicator: - ['new', 'premod', 'reported'].includes(queue) && - root[queue].nodes.length > 0, + ['premod', 'reported'].includes(queue) && root[queue].nodes.length > 0, // TODO: Eventually we'll reintroduce counting // count: root[`${props.queue}Count`] })); From 5a190839d1490123afa5e059c68f5cf748482909 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 14 May 2018 20:44:30 +0200 Subject: [PATCH 062/162] Show username already exists error --- client/coral-framework/hocs/withSetUsername.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-framework/hocs/withSetUsername.js b/client/coral-framework/hocs/withSetUsername.js index 05323efa1..3a8f65a22 100644 --- a/client/coral-framework/hocs/withSetUsername.js +++ b/client/coral-framework/hocs/withSetUsername.js @@ -59,6 +59,7 @@ const withSetUsername = hoistStatics(WrappedComponent => { } const changeSet = { success: false, loading: false, error }; this.setState(changeSet); + throw error; } }; From beef4e8b3b91a495715d3363b9832e4606d61cce Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 14 May 2018 21:28:48 +0200 Subject: [PATCH 063/162] Remove ModerationIndicator from container --- client/coral-admin/src/containers/Header.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/coral-admin/src/containers/Header.js b/client/coral-admin/src/containers/Header.js index d3be66764..c1abac35b 100644 --- a/client/coral-admin/src/containers/Header.js +++ b/client/coral-admin/src/containers/Header.js @@ -2,16 +2,13 @@ import { gql } from 'react-apollo'; import withQuery from 'coral-framework/hocs/withQuery'; import Header from '../components/Header'; import CommunityIndicator from '../routes/Community/containers/Indicator'; -import ModerationIndicator from '../routes/Moderation/containers/Indicator'; import { getDefinitionName } from 'coral-framework/utils'; export default withQuery( gql` query TalkAdmin_Header($nullID: ID) { - ...${getDefinitionName(ModerationIndicator.fragments.root)} ...${getDefinitionName(CommunityIndicator.fragments.root)} } - ${ModerationIndicator.fragments.root} ${CommunityIndicator.fragments.root} `, { From b5adea4b2a0ebc0e16cf08888b6222f5108713ef Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 14 May 2018 21:30:09 +0200 Subject: [PATCH 064/162] Remove unused variable --- client/coral-admin/src/containers/Header.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Header.js b/client/coral-admin/src/containers/Header.js index c1abac35b..49aff5aa8 100644 --- a/client/coral-admin/src/containers/Header.js +++ b/client/coral-admin/src/containers/Header.js @@ -2,6 +2,8 @@ import { gql } from 'react-apollo'; import withQuery from 'coral-framework/hocs/withQuery'; import Header from '../components/Header'; import CommunityIndicator from '../routes/Community/containers/Indicator'; +// TODO: eventually we will readd modqueue counts +// import ModerationIndicator from '../routes/Moderation/containers/Indicator'; import { getDefinitionName } from 'coral-framework/utils'; export default withQuery( @@ -13,7 +15,7 @@ export default withQuery( `, { options: { - variables: { nullID: null }, + // variables: { nullID: null }, }, } )(Header); From 08d523a4d61cdc3335f83b6754d958475c9ac741 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 14 May 2018 21:38:30 +0200 Subject: [PATCH 065/162] Remove unused variable 2 --- client/coral-admin/src/containers/Header.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Header.js b/client/coral-admin/src/containers/Header.js index 49aff5aa8..4b8760cc7 100644 --- a/client/coral-admin/src/containers/Header.js +++ b/client/coral-admin/src/containers/Header.js @@ -8,7 +8,7 @@ import { getDefinitionName } from 'coral-framework/utils'; export default withQuery( gql` - query TalkAdmin_Header($nullID: ID) { + query TalkAdmin_Header { ...${getDefinitionName(CommunityIndicator.fragments.root)} } ${CommunityIndicator.fragments.root} From 8d44525c16adcf173da456c173c84698daf0031d Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 14 May 2018 14:37:27 -0600 Subject: [PATCH 066/162] Added support for new indexes --- graph/loaders/settings.js | 12 ++ graph/resolvers/comment.js | 4 +- models/schema/action.js | 19 +-- models/schema/comment.js | 159 +++++++------------- models/schema/setting.js | 2 + models/schema/user.js | 47 +++--- plugin-api/beta/server/getReactionConfig.js | 18 ++- services/settings.js | 10 +- 8 files changed, 118 insertions(+), 153 deletions(-) diff --git a/graph/loaders/settings.js b/graph/loaders/settings.js index 8b07d712b..7010d7533 100644 --- a/graph/loaders/settings.js +++ b/graph/loaders/settings.js @@ -55,6 +55,18 @@ class SettingsLoader { // assembled Settings object. return zipObject(fields, values); } + + /** + * get, like select, will retrieve the settings, but get will only return a + * single setting. + * + * @param {String} field the field to get + */ + async get(field) { + const value = await this._loader.load(field); + + return value; + } } module.exports = () => ({ Settings: new SettingsLoader() }); diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js index c40aa49e0..acd55cb1a 100644 --- a/graph/resolvers/comment.js +++ b/graph/resolvers/comment.js @@ -57,8 +57,8 @@ const Comment = { asset({ asset_id }, _, { loaders: { Assets } }) { return Assets.getByID.load(asset_id); }, - async editing(comment, _, { loaders: { Settings } }) { - const { editCommentWindowLength } = await Settings.select( + editing: async (comment, _, { loaders: { Settings } }) => { + const editCommentWindowLength = await Settings.get( 'editCommentWindowLength' ); diff --git a/models/schema/action.js b/models/schema/action.js index 1df7bd793..d2047faac 100644 --- a/models/schema/action.js +++ b/models/schema/action.js @@ -9,7 +9,8 @@ const Action = new Schema( id: { type: String, default: uuid.v4, - unique: true, + unique: 1, + index: 1, }, action_type: { type: String, @@ -19,7 +20,10 @@ const Action = new Schema( type: String, enum: ITEM_TYPES, }, - item_id: String, + item_id: { + type: String, + index: 1, + }, user_id: String, // The element that summaries will additionally group on in addtion to their action_type, item_type, and @@ -37,15 +41,4 @@ const Action = new Schema( } ); -// Create an index on the `item_id` field so that queries looking for -// actions based on the item id can resolve faster. -Action.index( - { - item_id: 1, - }, - { - background: true, - } -); - module.exports = Action; diff --git a/models/schema/comment.js b/models/schema/comment.js index a211884ff..7ae51ff41 100644 --- a/models/schema/comment.js +++ b/models/schema/comment.js @@ -55,12 +55,16 @@ const Comment = new Schema( type: String, default: uuid.v4, unique: true, + index: true, }, body: { type: String, }, body_history: [BodyHistoryItemSchema], - asset_id: String, + asset_id: { + type: String, + index: true, + }, author_id: String, status_history: [Status], status: { @@ -90,7 +94,6 @@ const Comment = new Schema( // deleted_at stores the date that the given comment was deleted. deleted_at: { type: Date, - default: null, }, // Additional metadata stored on the field. @@ -110,95 +113,67 @@ const Comment = new Schema( } ); -// Add the indexes for the id of the comment. Comment.index( { - id: 1, - }, - { - unique: true, - background: false, - } -); - -Comment.index( - { - status: 1, - created_at: 1, - }, - { - background: true, - } -); - -Comment.index( - { - status: 1, - created_at: 1, - asset_id: 1, - }, - { - background: true, - } -); - -// Create a sparse index to search across. -Comment.index( - { - created_at: 1, - 'action_counts.flag': 1, - status: 1, - }, - { - background: true, - sparse: true, - } -); - -// Create a sparse index to search across. -Comment.index( - { - 'action_counts.flag': 1, - status: 1, - }, - { - background: true, - sparse: true, - } -); - -// Add an index that is optimized for finding flagged comments. -Comment.index( - { - asset_id: 1, - created_at: 1, - 'action_counts.flag': 1, - }, - { - background: true, - } -); - -// Add an index for the reply sort. -Comment.index( - { - asset_id: 1, + deleted_at: 1, created_at: -1, - reply_count: -1, }, - { - background: true, - } + { partialFilterExpression: { deleted_at: null } } ); -// Add an index that is optimized for finding a user's comments. Comment.index( { - author_id: 1, + deleted_at: 1, + status: 1, + created_at: -1, + }, + { partialFilterExpression: { deleted_at: null } } +); + +Comment.index({ + asset_id: 1, + created_at: -1, +}); + +Comment.index({ + asset_id: 1, + created_at: 1, +}); + +Comment.index({ + author_id: 1, + created_at: -1, +}); + +Comment.index({ + asset_id: 1, + status: 1, +}); + +Comment.index({ + asset_id: 1, + parent_id: 1, + reply_count: -1, + created_at: -1, +}); + +Comment.index({ + asset_id: 1, + reply_count: -1, + created_at: -1, +}); + +Comment.index( + { + 'action_counts.flag': 1, + status: 1, created_at: -1, }, { - background: true, + partialFilterExpression: { + 'action_counts.flag': { $exists: true, $gt: 0 }, + deleted_at: null, + }, } ); @@ -210,34 +185,10 @@ Comment.index( status: 1, }, { - background: true, - } -); - -// Optimize for tag searches/counts. -Comment.index( - { - 'tags.tag.name': 1, - status: 1, - }, - { - background: true, sparse: true, } ); -// Add an index that is optimized for sorting based on the created_at timestamp -// but also good at locating comments that have a specific asset id. -Comment.index( - { - asset_id: 1, - created_at: 1, - }, - { - background: true, - } -); - Comment.virtual('edited').get(function() { return this.body_history.length > 1; }); diff --git a/models/schema/setting.js b/models/schema/setting.js index eb9dc76d1..4e6a2c6ab 100644 --- a/models/schema/setting.js +++ b/models/schema/setting.js @@ -12,6 +12,8 @@ const Setting = new Schema( id: { type: String, default: '1', + unique: 1, + index: true, }, moderation: { type: String, diff --git a/models/schema/user.js b/models/schema/user.js index ec9c018cc..a4ccfd185 100644 --- a/models/schema/user.js +++ b/models/schema/user.js @@ -58,6 +58,7 @@ const User = new Schema( default: uuid.v4, unique: true, required: true, + index: true, }, // This is sourced from the social provider or set manually during user setup @@ -107,6 +108,7 @@ const User = new Schema( status: { type: String, enum: USER_STATUS_USERNAME, + index: true, }, // History stores the history of username status changes. @@ -135,6 +137,7 @@ const User = new Schema( type: Boolean, required: true, default: false, + index: true, }, history: [ { @@ -226,41 +229,26 @@ User.index( } ); -User.index( - { - lowercaseUsername: 1, - 'profiles.id': 1, - created_at: -1, - }, - { - background: true, - } -); +User.index({ + lowercaseUsername: 1, + 'profiles.id': 1, + created_at: -1, +}); // This query is executed often, to count the number of flagged accounts with // usernames. -User.index( - { - 'action_counts.flag': 1, - 'status.username.status': 1, - }, - { - background: true, - } -); +User.index({ + 'action_counts.flag': 1, + 'status.username.status': 1, +}); // Sorting users by created at is the default people search. -User.index( - { - created_at: -1, - }, - { - background: true, - } -); +User.index({ + created_at: -1, +}); /** - * returns true if a commenter is staff + * returns true if a commenter is staff. */ User.method('isStaff', function() { return this.role !== 'COMMENTER'; @@ -330,6 +318,9 @@ User.virtual('hasVerifiedEmail').get(function() { }); }); +/** + * system returns true when the user is a system user. + */ User.virtual('system') .get(function() { return this._system; diff --git a/plugin-api/beta/server/getReactionConfig.js b/plugin-api/beta/server/getReactionConfig.js index 5aa3063b3..f1f25b261 100644 --- a/plugin-api/beta/server/getReactionConfig.js +++ b/plugin-api/beta/server/getReactionConfig.js @@ -11,10 +11,22 @@ function getReactionConfig(reaction) { if (CREATE_MONGO_INDEXES) { // Create the index on the comment model based on the reaction config. - Comment.collection.createIndex( + Comment.collection.ensureIndex( { - created_at: 1, - [`action_counts.${sc(reaction)}`]: 1, + asset_id: 1, + [`action_counts.${sc(reaction)}`]: -1, + created_at: -1, + }, + { + background: true, + } + ); + + Comment.collection.ensureIndex( + { + asset_id: 1, + [`action_counts.${sc(reaction)}`]: -1, + created_at: -1, }, { background: true, diff --git a/services/settings.js b/services/settings.js index def42187a..e009c707b 100644 --- a/services/settings.js +++ b/services/settings.js @@ -1,13 +1,17 @@ const Setting = require('../models/setting'); const { ErrSettingsNotInit } = require('../errors'); const { dotize } = require('./utils'); -const { isEmpty, zipObject, uniq } = require('lodash'); +const { isEmpty, zipObject } = require('lodash'); const DataLoader = require('dataloader'); const selector = { id: '1' }; -async function loadFn(fields = []) { - const model = await Setting.findOne(selector).select(uniq(fields)); +async function loadFn(/* fields = [] */) { + // Originally, we used the projection operation, turns out this isn't that + // fast. We should utilize the redis cache instead here. + // const model = await Setting.findOne(selector).select(uniq(fields)); + + const model = await Setting.findOne(selector); if (!model) { throw new ErrSettingsNotInit(); } From 50acf9ca5043c4e08739de2f32d3ff3aa930c869 Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 15 May 2018 11:47:07 -0300 Subject: [PATCH 067/162] Removing --- .../client/components/ChangeEmailContentDialog.js | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js b/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js index b0fdeb14d..cd6738d02 100644 --- a/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js +++ b/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js @@ -85,7 +85,6 @@ class ChangeEmailContentDialog extends React.Component { cancel = () => { this.clearForm(); - this.disableEditing(); }; confirmChanges = async e => { From ef5e5ddbcf41a0a9d17739c95e4758bf2d8261d9 Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Tue, 15 May 2018 11:55:02 -0400 Subject: [PATCH 068/162] Set is_test to false Akismet is no longer in test mode. --- plugins/talk-plugin-akismet/server/hooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/talk-plugin-akismet/server/hooks.js b/plugins/talk-plugin-akismet/server/hooks.js index 80a233584..b06557783 100644 --- a/plugins/talk-plugin-akismet/server/hooks.js +++ b/plugins/talk-plugin-akismet/server/hooks.js @@ -71,7 +71,7 @@ module.exports = { permalink: asset.url, comment_type: 'comment', comment_content: input.body, - is_test: true, + is_test: false, }); debug(`comment analyzed as ${spam ? 'being' : 'not being'} spam`); From 40a431b374e883d44777a6c248077c117f914bf9 Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 15 May 2018 13:31:29 -0300 Subject: [PATCH 069/162] Ready --- .../components/ChangeEmailContentDialog.js | 19 +++++++------------ .../client/components/Profile.js | 1 + 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js b/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js index cd6738d02..f671c065b 100644 --- a/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js +++ b/plugins/talk-plugin-local-auth/client/components/ChangeEmailContentDialog.js @@ -9,7 +9,9 @@ import errorMsj from 'coral-framework/helpers/error'; const initialState = { showError: false, - formData: {}, + formData: { + confirmPassword: '', + }, errors: {}, }; @@ -68,11 +70,6 @@ class ChangeEmailContentDialog extends React.Component { return Object.keys(this.state.errors).indexOf(err) !== -1; }; - isSaveEnabled = () => { - const formHasErrors = !!Object.keys(this.state.errors).length; - return !formHasErrors; - }; - getError = errorKey => { return this.state.errors[errorKey]; }; @@ -85,6 +82,7 @@ class ChangeEmailContentDialog extends React.Component { cancel = () => { this.clearForm(); + this.props.closeDialog(); }; confirmChanges = async e => { @@ -133,7 +131,7 @@ class ChangeEmailContentDialog extends React.Component { name="confirmPassword" type="password" onChange={this.onChange} - defaultValue="" + value={this.state.formData.confirmPassword} hasError={this.hasError('confirmPassword')} errorMsg={this.getError('confirmPassword')} showError={this.state.showError} @@ -159,14 +157,11 @@ class ChangeEmailContentDialog extends React.Component { } ChangeEmailContentDialog.propTypes = { - save: PropTypes.func, next: PropTypes.func, - cancel: PropTypes.func, - onChange: PropTypes.func, + save: PropTypes.func, formData: PropTypes.object, email: PropTypes.string, - hasError: PropTypes.func, - getError: PropTypes.func, + closeDialog: PropTypes.func, }; export default ChangeEmailContentDialog; diff --git a/plugins/talk-plugin-local-auth/client/components/Profile.js b/plugins/talk-plugin-local-auth/client/components/Profile.js index 7c4c8e493..8484fa2b8 100644 --- a/plugins/talk-plugin-local-auth/client/components/Profile.js +++ b/plugins/talk-plugin-local-auth/client/components/Profile.js @@ -205,6 +205,7 @@ class Profile extends React.Component { formData={this.state.formData} email={email} enable={formData.newEmail && email !== formData.newEmail} + closeDialog={this.closeDialog} /> From ee0790dbe8bd19a7ed1c6410717776cb207baafa Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 15 May 2018 11:25:58 -0600 Subject: [PATCH 070/162] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 01b671dde..97f4e373d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "talk", - "version": "4.4.0", + "version": "4.4.1", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", "private": true, From 0b407d516b30efbb2937814aca879645c167394b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 15 May 2018 15:51:14 -0600 Subject: [PATCH 071/162] copy updates --- .../client/components/Profile.js | 18 ++++++++++++++---- .../talk-plugin-local-auth/translations.yml | 6 ++++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/plugins/talk-plugin-local-auth/client/components/Profile.js b/plugins/talk-plugin-local-auth/client/components/Profile.js index 8484fa2b8..caa09543b 100644 --- a/plugins/talk-plugin-local-auth/client/components/Profile.js +++ b/plugins/talk-plugin-local-auth/client/components/Profile.js @@ -223,11 +223,21 @@ class Profile extends React.Component { disabled={!usernameCanBeUpdated} columnDisplay > - - {t( - 'talk-plugin-local-auth.change_username.change_username_note' +
        + + {t( + 'talk-plugin-local-auth.change_username.change_username_note' + )} + + {!usernameCanBeUpdated && ( + + {' '} + {t( + 'talk-plugin-local-auth.change_username.is_not_eligible' + )} + )} - +
        Date: Wed, 16 May 2018 11:51:28 -0300 Subject: [PATCH 072/162] displaying multiple provider ids --- .../coral-admin/src/components/UserDetail.js | 19 ++++++++++++++++--- .../coral-admin/src/containers/UserDetail.js | 1 + .../src/routes/Community/components/People.js | 9 ++++++++- locales/en.yml | 1 + 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 5dabc5b6f..de69f8a93 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -185,11 +185,24 @@ class UserDetail extends React.Component { {new Date(user.created_at).toLocaleString()}

      • - {user.profiles.map(({ id }) => ( +
      • + + + {t('user_detail.email')}: + + {user.email}{' '} + +
      • + + {user.profiles.map(({ provider, id }) => (
      • - + - {t('user_detail.email')}: + {capitalize(provider)} {t('user_detail.id')}: {id}{' '} - {user.profiles.map(({ id }) => id)} + {user.email + ? user.email + : user.profiles.map(({ id }, i) => { + if (i === user.profiles.length - 1) { + return id; + } + return `${id}, `; + })} diff --git a/locales/en.yml b/locales/en.yml index 36cf956b4..0c25c2c94 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -467,6 +467,7 @@ en: all: "All" rejected: "Rejected" user_history: "User History" + id: "ID" user_history: user_banned: "User banned" ban_removed: "Ban removed" From 2524004e2a5e50cf7eeb6a5ab90d5c6482ea3926 Mon Sep 17 00:00:00 2001 From: okbel Date: Wed, 16 May 2018 11:55:05 -0300 Subject: [PATCH 073/162] Styling --- client/coral-admin/src/components/UserDetail.css | 4 ++++ client/coral-admin/src/components/UserDetail.js | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/components/UserDetail.css b/client/coral-admin/src/components/UserDetail.css index 58faee5c4..389971b32 100644 --- a/client/coral-admin/src/components/UserDetail.css +++ b/client/coral-admin/src/components/UserDetail.css @@ -143,3 +143,7 @@ border-color: #E45241; color: white; } + +.userDetailItem { + padding: 2px 0; +} diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index de69f8a93..a61ee81ec 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -177,7 +177,7 @@ class UserDetail extends React.Component {
          -
        • +
        • {t('user_detail.member_since')}: @@ -185,7 +185,7 @@ class UserDetail extends React.Component { {new Date(user.created_at).toLocaleString()}
        • -
        • +
        • {t('user_detail.email')}: @@ -199,7 +199,7 @@ class UserDetail extends React.Component {
        • {user.profiles.map(({ provider, id }) => ( -
        • +
        • {capitalize(provider)} {t('user_detail.id')}: From 2eac8f48d623d717c4e5331ef79fe1b8d9061dfd Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 16 May 2018 21:37:35 +0200 Subject: [PATCH 074/162] Workaround IOS focus bug when clicking on buttons --- .../client/components/rte/components/Button.js | 11 ++--------- .../client/components/rte/factories/createToggle.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js b/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js index f99648cb7..fd5d451a1 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js @@ -7,21 +7,17 @@ class Button extends React.Component { render() { const { className, - title, - onClick, children, active, activeClassName, - disabled, + ...rest } = this.props; return ( @@ -32,11 +28,8 @@ class Button extends React.Component { Button.propTypes = { className: PropTypes.string, activeClassName: PropTypes.string, - title: PropTypes.string, - onClick: PropTypes.func, children: PropTypes.node, active: PropTypes.bool, - disabled: PropTypes.bool, }; export default Button; diff --git a/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js b/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js index 028757222..59b75fdf2 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Button from '../components/Button'; +import bowser from 'bowser'; /** * createToggle creates a button that can be active, inactive or disabled @@ -32,7 +33,17 @@ const createToggle = ( this.execCommand(); }; + // Detect whether there was focus on the RTE before the click. + hadFocusBeforeClick = false; + handleMouseDown = () => (this.hadFocusBeforeClick = this.props.api.focused); + handleClick = () => { + // Skip IOS when the focus was not there before. + // IOS fails to focus to the RTE correctly and scrolls to nirvana. + // See https://www.pivotaltracker.com/story/show/157607216 + if (!this.hadFocusBeforeClick && bowser.ios) { + return; + } this.props.api.focus(); this.formatToggle(); this.props.api.focus(); @@ -62,6 +73,7 @@ const createToggle = (
        diff --git a/client/coral-framework/utils/user.js b/client/coral-framework/utils/user.js index 610557542..693652994 100644 --- a/client/coral-framework/utils/user.js +++ b/client/coral-framework/utils/user.js @@ -49,3 +49,18 @@ export const canUsernameBeUpdated = status => { moment(created_at).isAfter(oldestEditTime) ); }; + +/** + * getKarma + * retrieves karma value as string + */ + +export const getKarma = score => { + if (score === 0) { + return 'neutral'; + } else if (score) { + return 'good'; + } else { + return 'bad'; + } +}; diff --git a/locales/en.yml b/locales/en.yml index f60219472..ab7be8e9d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -470,6 +470,10 @@ en: all: "All" rejected: "Rejected" user_history: "User History" + karma: "Karma" + learn_more: "Learn More" + user_karma_score: "User Karma Score" + karma_docs_link: "https://docs.coralproject.net/talk/trust/#user-karma-score" user_history: user_banned: "User banned" ban_removed: "Ban removed" From 887f7e17f49304a0e6445c07abaa55a53c1ed7c8 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 17 May 2018 11:38:00 -0600 Subject: [PATCH 080/162] server side --- client/coral-admin/src/components/UserDetail.js | 7 +++++-- client/coral-admin/src/containers/UserDetail.js | 2 ++ client/coral-framework/utils/user.js | 6 +++--- graph/typeDefs.graphql | 8 ++++++++ services/karma.js | 9 +++++++++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 723a71eef..070f22ba4 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -245,9 +245,12 @@ class UserDetail extends React.Component {
      - {100} + {user.reliable.commenterScore}
    diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 360819dd6..810a336c8 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -185,6 +185,8 @@ export const withUserDetailQuery = withQuery( } reliable { flagger + commenter + commenterScore } state { status { diff --git a/client/coral-framework/utils/user.js b/client/coral-framework/utils/user.js index 693652994..e591fa558 100644 --- a/client/coral-framework/utils/user.js +++ b/client/coral-framework/utils/user.js @@ -55,10 +55,10 @@ export const canUsernameBeUpdated = status => { * retrieves karma value as string */ -export const getKarma = score => { - if (score === 0) { +export const getKarma = reliability => { + if (reliability === null) { return 'neutral'; - } else if (score) { + } else if (reliability) { return 'good'; } else { return 'bad'; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 07947af6e..1c323e6f4 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -20,9 +20,17 @@ type Reliability { # `null` if the reliability cannot be determined. flagger: Boolean + # flaggerScore will contains the number of agreed flags vs disagred flag + # count. + flaggerScore: Int! + # Commenter will be `true` when the commenter is reliable, `false` if not, or # `null` if the reliability cannot be determined. commenter: Boolean + + # commenterScore the number of approved comments (not untouched) subtracted by + # the number of rejected comments. + commenterScore: Int! } ################################################################################ diff --git a/services/karma.js b/services/karma.js index 58d9da3a0..c39c93a32 100644 --- a/services/karma.js +++ b/services/karma.js @@ -1,6 +1,7 @@ const debug = require('debug')('talk:services:karma'); const UserModel = require('../models/user'); const { TRUST_THRESHOLDS } = require('../config'); +const { get } = require('lodash'); /** * This will create an object with the property name of the action type as the @@ -83,9 +84,17 @@ class KarmaModel { return KarmaService.isReliable('flag', this.model); } + get flaggerScore() { + return get(this.model, 'flag.karma', 0); + } + get commenter() { return KarmaService.isReliable('comment', this.model); } + + get commenterScore() { + return get(this.model, 'comment.karma', 0); + } } /** From 086c43a0034a039e58ae95dada8c03c822188ec3 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 17 May 2018 13:18:57 -0600 Subject: [PATCH 081/162] Update .nsprc --- .nsprc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.nsprc b/.nsprc index 530a17a9f..8962db719 100644 --- a/.nsprc +++ b/.nsprc @@ -7,6 +7,7 @@ "https://nodesecurity.io/advisories/594", "https://nodesecurity.io/advisories/603", "https://nodesecurity.io/advisories/611", - "https://nodesecurity.io/advisories/612" + "https://nodesecurity.io/advisories/612", + "https://nodesecurity.io/advisories/654" ] } From 417befd52a6172094fa00815e4096fdd99220f85 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 17 May 2018 21:34:37 +0200 Subject: [PATCH 082/162] Don't close popup when there is an error --- client/coral-auth-callback/src/index.js | 50 +++++++++++++------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/client/coral-auth-callback/src/index.js b/client/coral-auth-callback/src/index.js index 41a83dcd2..7bf75e825 100644 --- a/client/coral-auth-callback/src/index.js +++ b/client/coral-auth-callback/src/index.js @@ -3,32 +3,36 @@ import { getStaticConfiguration } from 'coral-framework/services/staticConfigura import { createPostMessage } from 'coral-framework/services/postMessage'; document.addEventListener('DOMContentLoaded', () => { - try { - const staticConfig = getStaticConfiguration(); - const { STATIC_ORIGIN: origin } = staticConfig; - const postMessage = createPostMessage(origin); + const staticConfig = getStaticConfiguration(); + const { STATIC_ORIGIN: origin } = staticConfig; + const postMessage = createPostMessage(origin); - // Get the auth element and parse it as JSON by decoding it. - const auth = document.getElementById('auth'); - const doc = document.implementation.createHTMLDocument(''); - doc.body.innerHTML = auth.innerText; + // Get the auth element and parse it as JSON by decoding it. + const auth = document.getElementById('auth'); + const doc = document.implementation.createHTMLDocument(''); + doc.body.innerHTML = auth.innerText; - // Auth state is contained within the node. - const { err, data } = JSON.parse(doc.body.textContent); - if (err) { - // TODO: send back the error message. - console.error(err); + // Auth state is contained within the node. + const { err, data } = JSON.parse(doc.body.textContent); + if (err) { + const errDiv = document.createElement('div'); + if (err.message) { + errDiv.innerText = `${err.name}: ${err.message}`; } else { - // The data will contain a user and a token. - const { user, token } = data; - - // Send the state back. - postMessage.post(HANDLE_SUCCESSFUL_LOGIN, { user, token }); + errDiv.innerText = JSON.stringify(err); } - } finally { - // Always close the window. - setTimeout(() => { - window.close(); - }, 50); + document.body.appendChild(errDiv); + throw err; } + + // The data will contain a user and a token. + const { user, token } = data; + + // Send the state back. + postMessage.post(HANDLE_SUCCESSFUL_LOGIN, { user, token }); + + // Close the window when all went well. + setTimeout(() => { + window.close(); + }, 50); }); From 95072be97e27945e071e62c9658b1d81520ef204 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 17 May 2018 15:59:49 -0600 Subject: [PATCH 083/162] switched ejs for nunjucks --- app.js | 23 +++- package.json | 1 + .../server/router.js | 2 +- .../views/unsubscribe-notifications.ejs | 64 --------- .../views/unsubscribe-notifications.njk | 61 +++++++++ .../talk-plugin-profile-data/server/router.js | 2 +- .../server/views/download.ejs | 56 -------- .../server/views/download.njk | 56 ++++++++ routes/account/index.js | 4 +- routes/admin/index.js | 2 +- routes/api/v1/graph.js | 2 +- routes/dev/assets.js | 6 +- routes/dev/index.js | 2 +- routes/embed/index.js | 2 +- routes/login/index.js | 2 +- services/passport.js | 6 +- views/account/email/confirm.ejs | 63 --------- views/account/email/confirm.njk | 63 +++++++++ views/account/password/reset.ejs | 85 ------------ views/account/password/reset.njk | 85 ++++++++++++ views/admin.ejs | 17 --- views/admin.njk | 14 ++ views/api/graphiql.ejs | 125 ----------------- views/api/graphiql.njk | 126 ++++++++++++++++++ views/auth-callback.ejs | 10 -- views/auth-callback.njk | 10 ++ views/dev/article.ejs | 52 -------- views/dev/article.njk | 55 ++++++++ views/dev/articles.ejs | 40 ------ views/dev/articles.njk | 37 +++++ views/embed/stream.ejs | 14 -- views/embed/stream.njk | 16 +++ views/login.ejs | 16 --- views/login.njk | 16 +++ views/partials/account.ejs | 4 - views/partials/custom-css.ejs | 1 - views/partials/custom-css.njk | 3 + views/partials/data.ejs | 3 - views/partials/data.njk | 3 + views/partials/dev-nav.ejs | 8 -- views/partials/favicon.njk | 14 ++ views/partials/head.ejs | 23 ---- views/partials/nav.njk | 8 ++ views/templates/account.njk | 6 + views/templates/base.njk | 39 ++++++ .../dev.ejs => templates/development.njk} | 16 ++- yarn.lock | 6 + 47 files changed, 670 insertions(+), 599 deletions(-) delete mode 100644 plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.ejs create mode 100644 plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.njk delete mode 100644 plugins/talk-plugin-profile-data/server/views/download.ejs create mode 100644 plugins/talk-plugin-profile-data/server/views/download.njk delete mode 100644 views/account/email/confirm.ejs create mode 100644 views/account/email/confirm.njk delete mode 100644 views/account/password/reset.ejs create mode 100644 views/account/password/reset.njk delete mode 100644 views/admin.ejs create mode 100644 views/admin.njk delete mode 100644 views/api/graphiql.ejs create mode 100644 views/api/graphiql.njk delete mode 100644 views/auth-callback.ejs create mode 100644 views/auth-callback.njk delete mode 100644 views/dev/article.ejs create mode 100644 views/dev/article.njk delete mode 100644 views/dev/articles.ejs create mode 100644 views/dev/articles.njk delete mode 100644 views/embed/stream.ejs create mode 100644 views/embed/stream.njk delete mode 100644 views/login.ejs create mode 100644 views/login.njk delete mode 100644 views/partials/account.ejs delete mode 100644 views/partials/custom-css.ejs create mode 100644 views/partials/custom-css.njk delete mode 100644 views/partials/data.ejs create mode 100644 views/partials/data.njk delete mode 100644 views/partials/dev-nav.ejs create mode 100644 views/partials/favicon.njk delete mode 100644 views/partials/head.ejs create mode 100644 views/partials/nav.njk create mode 100644 views/templates/account.njk create mode 100644 views/templates/base.njk rename views/{partials/dev.ejs => templates/development.njk} (54%) diff --git a/app.js b/app.js index c5507da2e..b3400b939 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,6 @@ const express = require('express'); +const nunjucks = require('nunjucks'); +const cons = require('consolidate'); const trace = require('./middleware/trace'); const logging = require('./middleware/logging'); const path = require('path'); @@ -72,7 +74,26 @@ app.use( // VIEW CONFIGURATION //============================================================================== -app.set('views', path.join(__dirname, 'views')); +// configure the default views directory. +const views = path.join(__dirname, 'views'); +app.set('views', views); + +// reconfigure nunjucks. +cons.requires.nunjucks = nunjucks.configure(views, { + autoescape: true, + trimBlocks: true, + lstripBlocks: true, + watch: process.env.NODE_ENV === 'development', +}); + +// assign the nunjucks engine to .njk files. +app.engine('njk', cons.nunjucks); + +// assign the ejs engine to .ejs and .html files. +app.engine('ejs', cons.ejs); +app.engine('html', cons.ejs); + +// set .ejs as the default extension. app.set('view engine', 'ejs'); //============================================================================== diff --git a/package.json b/package.json index 97f4e373d..e8ff0182d 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "common-tags": "^1.4.0", "compression": "1.7.1", "compression-webpack-plugin": "^1.0.0", + "consolidate": "0.14.0", "cookie-parser": "^1.4.3", "copy-webpack-plugin": "^4.0.0", "cross-spawn": "^5.1.0", diff --git a/plugins/talk-plugin-notifications/server/router.js b/plugins/talk-plugin-notifications/server/router.js index 68919a936..9185c5aba 100644 --- a/plugins/talk-plugin-notifications/server/router.js +++ b/plugins/talk-plugin-notifications/server/router.js @@ -4,7 +4,7 @@ const { get, isEmpty, reduce } = require('lodash'); module.exports = router => { router.get('/account/unsubscribe-notifications', (req, res) => { - res.render(path.join(__dirname, 'views/unsubscribe-notifications')); + res.render(path.join(__dirname, 'views/unsubscribe-notifications.njk')); }); /** diff --git a/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.ejs b/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.ejs deleted file mode 100644 index 129c3115c..000000000 --- a/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.ejs +++ /dev/null @@ -1,64 +0,0 @@ - - - - - <%= t('talk-plugin-notifications.unsubscribe_page.unsubscribe') %> - <%- include(root + '/partials/head') %> - - - - -
    -
    <%= t('talk-plugin-notifications.unsubscribe_page.token_invalid') %>
    - - - <%= t('talk-plugin-notifications.unsubscribe_page.click_to_confirm') %> - - -
    - - - - \ No newline at end of file diff --git a/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.njk b/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.njk new file mode 100644 index 000000000..f8191d525 --- /dev/null +++ b/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.njk @@ -0,0 +1,61 @@ +{% extends "templates/account.njk" %} + +{% block title %}{{ t('talk-plugin-notifications.unsubscribe_page.unsubscribe') }}{% endblock %} + +{% block html %} +
    +
    {{ t('talk-plugin-notifications.unsubscribe_page.token_invalid') }}
    + +
    + {{ t('talk-plugin-notifications.unsubscribe_page.click_to_confirm') }} + +
    +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/plugins/talk-plugin-profile-data/server/router.js b/plugins/talk-plugin-profile-data/server/router.js index 6d8f47d03..6bd165e64 100644 --- a/plugins/talk-plugin-profile-data/server/router.js +++ b/plugins/talk-plugin-profile-data/server/router.js @@ -102,7 +102,7 @@ async function loadComments(ctx, userID, archive, latestContentDate) { module.exports = router => { // /account/download will render the download page. router.get('/account/download', (req, res) => { - res.render(path.join(__dirname, 'views', 'download')); + res.render(path.join(__dirname, 'views', 'download.njk')); }); // /api/v1/account/download will send back a zipped archive of the users diff --git a/plugins/talk-plugin-profile-data/server/views/download.ejs b/plugins/talk-plugin-profile-data/server/views/download.ejs deleted file mode 100644 index 260badf3a..000000000 --- a/plugins/talk-plugin-profile-data/server/views/download.ejs +++ /dev/null @@ -1,56 +0,0 @@ - - - - <%= t('download_landing.download_your_account') %> - <%- include(root + '/partials/account') %> - - -
    -
    -

    <%= t('download_landing.download_your_account') %>

    -

    <%= t('download_landing.download_details') %>

    -

    <%= t('download_landing.all_information_included') %>

    -
      -
    • <%= t('download_landing.information_included.date') %>
    • -
    • <%= t('download_landing.information_included.url') %>
    • -
    • <%= t('download_landing.information_included.body') %>
    • -
    • <%= t('download_landing.information_included.asset_url') %>
    • -
    -
    -
    - -
    -
    -
    - - - - diff --git a/plugins/talk-plugin-profile-data/server/views/download.njk b/plugins/talk-plugin-profile-data/server/views/download.njk new file mode 100644 index 000000000..5527473bc --- /dev/null +++ b/plugins/talk-plugin-profile-data/server/views/download.njk @@ -0,0 +1,56 @@ +{% extends "templates/account.njk" %} + +{% block title %}{{ t('download_landing.download_your_account') }}{% endblock %} + +{% block html %} +
    +
    +

    {{ t('download_landing.download_your_account') }}

    +

    {{ t('download_landing.download_details') }}

    +

    {{ t('download_landing.all_information_included') }}

    +
      +
    • {{ t('download_landing.information_included.date') }}
    • +
    • {{ t('download_landing.information_included.url') }}
    • +
    • {{ t('download_landing.information_included.body') }}
    • +
    • {{ t('download_landing.information_included.asset_url') }}
    • +
    +
    +
    + +
    +
    +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/routes/account/index.js b/routes/account/index.js index 70e62accc..37b26e344 100644 --- a/routes/account/index.js +++ b/routes/account/index.js @@ -2,11 +2,11 @@ const express = require('express'); const router = express.Router(); router.get('/email/confirm', (req, res) => { - res.render('account/email/confirm'); + res.render('account/email/confirm.njk'); }); router.get('/password/reset', (req, res) => { - res.render('account/password/reset'); + res.render('account/password/reset.njk'); }); module.exports = router; diff --git a/routes/admin/index.js b/routes/admin/index.js index d00ef8642..1a9993cfd 100644 --- a/routes/admin/index.js +++ b/routes/admin/index.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); router.get('*', (req, res) => { - res.render('admin'); + res.render('admin.njk'); }); module.exports = router; diff --git a/routes/api/v1/graph.js b/routes/api/v1/graph.js index 40e87415b..0660755ea 100644 --- a/routes/api/v1/graph.js +++ b/routes/api/v1/graph.js @@ -10,7 +10,7 @@ router.use('/ql', apollo.graphqlExpress(createGraphOptions)); if (process.env.NODE_ENV !== 'production') { // Interactive graphiql interface. router.use('/iql', staticTemplate, (req, res) => { - res.render('api/graphiql', { + res.render('api/graphiql.njk', { endpointURL: 'api/v1/graph/ql', }); }); diff --git a/routes/dev/assets.js b/routes/dev/assets.js index aea0628e3..df59f21a0 100644 --- a/routes/dev/assets.js +++ b/routes/dev/assets.js @@ -11,7 +11,7 @@ router.get('/id/:asset_id', async (req, res, next) => { throw new ErrNotFound(); } - res.render('dev/article', { + res.render('dev/article.njk', { title: asset.title, asset_id: asset.id, asset_url: asset.url, @@ -28,7 +28,7 @@ router.get('/random', (req, res) => { }); router.get('/title/:asset_title', (req, res) => { - res.render('dev/article', { + res.render('dev/article.njk', { title: req.params.asset_title.split('-').join(' '), asset_url: '', asset_id: null, @@ -48,7 +48,7 @@ router.get('/', async (req, res, next) => { Asset.count(), ]); - res.render('dev/articles', { + res.render('dev/articles.njk', { skip, limit, count, diff --git a/routes/dev/index.js b/routes/dev/index.js index 58daca583..580e702fd 100644 --- a/routes/dev/index.js +++ b/routes/dev/index.js @@ -12,7 +12,7 @@ router.get('/', staticTemplate, async (req, res) => { await SetupService.isAvailable(); return res.redirect(url.resolve(MOUNT_PATH, 'admin/install')); } catch (e) { - return res.render('dev/article', { + return res.render('dev/article.njk', { title: 'Coral Talk', asset_url: '', asset_id: '', diff --git a/routes/embed/index.js b/routes/embed/index.js index 695ffe609..7456a5b71 100644 --- a/routes/embed/index.js +++ b/routes/embed/index.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); router.use('/stream', (req, res) => { - res.render('embed/stream'); + res.render('embed/stream.njk'); }); module.exports = router; diff --git a/routes/login/index.js b/routes/login/index.js index b69cb7f5c..e03690dd0 100644 --- a/routes/login/index.js +++ b/routes/login/index.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); router.get('*', (req, res) => { - res.render('login'); + res.render('login.njk'); }); module.exports = router; diff --git a/services/passport.js b/services/passport.js index e4813e419..4b96d2352 100644 --- a/services/passport.js +++ b/services/passport.js @@ -115,13 +115,13 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => { res.locals.encodeJSONForHTML = encodeJSONForHTML; if (err) { - return res.render('auth-callback', { + return res.render('auth-callback.njk', { auth: { err, data: null }, }); } if (!user) { - return res.render('auth-callback', { + return res.render('auth-callback.njk', { auth: { err: new ErrNotAuthorized(), data: null }, }); } @@ -132,7 +132,7 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => { SetTokenForSafari(req, res, token); // We logged in the user! Let's send back the user data. - res.render('auth-callback', { + res.render('auth-callback.njk', { auth: { err: null, data: { user, token } }, }); }; diff --git a/views/account/email/confirm.ejs b/views/account/email/confirm.ejs deleted file mode 100644 index 47bc7773b..000000000 --- a/views/account/email/confirm.ejs +++ /dev/null @@ -1,63 +0,0 @@ - - - - <%= t('confirm_email.email_confirmation') %> - <%- include ../../partials/account %> - - -
    -
    -

    <%= t('confirm_email.email_confirmation') %>

    -

    <%= t('confirm_email.click_to_confirm') %>

    -
    -
    - -
    -
    -
    - - - - diff --git a/views/account/email/confirm.njk b/views/account/email/confirm.njk new file mode 100644 index 000000000..6111cb21e --- /dev/null +++ b/views/account/email/confirm.njk @@ -0,0 +1,63 @@ +{% extends "templates/account.njk" %} + +{% block title %}{{ t('confirm_email.email_confirmation') }}{% endblock %} + +{% block html %} +
    +
    +

    {{ t('confirm_email.email_confirmation') }}

    +

    {{ t('confirm_email.click_to_confirm') }}

    +
    +
    + +
    +
    +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/views/account/password/reset.ejs b/views/account/password/reset.ejs deleted file mode 100644 index c8b106139..000000000 --- a/views/account/password/reset.ejs +++ /dev/null @@ -1,85 +0,0 @@ - - - - <%= t('password_reset.set_new_password') %> - <%- include ../../partials/account %> - - -
    -
    -

    <%= t('password_reset.set_new_password') %>

    -

    <%= t('password_reset.change_password_help') %>

    -
    -
    - - - -
    -
    -
    - - - - diff --git a/views/account/password/reset.njk b/views/account/password/reset.njk new file mode 100644 index 000000000..b6f227dea --- /dev/null +++ b/views/account/password/reset.njk @@ -0,0 +1,85 @@ +{% extends "templates/account.njk" %} + +{% block title %}{{ t('password_reset.set_new_password') }}{% endblock %} + +{% block html %} +
    +
    +

    {{ t('password_reset.set_new_password') }}

    +

    {{ t('password_reset.change_password_help') }}

    +
    +
    + + + +
    +
    +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/views/admin.ejs b/views/admin.ejs deleted file mode 100644 index 89cc150f7..000000000 --- a/views/admin.ejs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Talk - Coral Admin - <%- include partials/head %> - - - - <%- include partials/custom-css %> - - -
    - - - - diff --git a/views/admin.njk b/views/admin.njk new file mode 100644 index 000000000..4b5236424 --- /dev/null +++ b/views/admin.njk @@ -0,0 +1,14 @@ +{% extends "templates/base.njk" %} + +{% block title %}Talk Admin{% endblock %} + +{% block css %} + + + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/views/api/graphiql.ejs b/views/api/graphiql.ejs deleted file mode 100644 index 8007569b3..000000000 --- a/views/api/graphiql.ejs +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - GraphiQL - - <%- include ../partials/dev %> - - - - - - - - - <%- include ../partials/dev-nav %> -
    - - - diff --git a/views/api/graphiql.njk b/views/api/graphiql.njk new file mode 100644 index 000000000..b726918f3 --- /dev/null +++ b/views/api/graphiql.njk @@ -0,0 +1,126 @@ +{% extends "templates/development.njk" %} + +{% block title %}GraphiQL{% endblock %} + +{% block css %} +{# Include the base development pieces #} +{{ super() }} + + +{% endblock %} + +{% block js %} + + + + + +{% endblock %} + +{% block html %} +
    +{% endblock %} diff --git a/views/auth-callback.ejs b/views/auth-callback.ejs deleted file mode 100644 index e4d2387b7..000000000 --- a/views/auth-callback.ejs +++ /dev/null @@ -1,10 +0,0 @@ - - - - <%- include partials/data %> - - - - - - diff --git a/views/auth-callback.njk b/views/auth-callback.njk new file mode 100644 index 000000000..7119a6255 --- /dev/null +++ b/views/auth-callback.njk @@ -0,0 +1,10 @@ + + + + {% include "partials/data.njk" %} + + + + + + diff --git a/views/dev/article.ejs b/views/dev/article.ejs deleted file mode 100644 index 008d3989e..000000000 --- a/views/dev/article.ejs +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - <%= title %> - <%- include ../partials/dev %> - - - <%- include ../partials/dev-nav %> -
    -

    <%= title %>

    -
    - - -
    - - diff --git a/views/dev/article.njk b/views/dev/article.njk new file mode 100644 index 000000000..2fb8b3724 --- /dev/null +++ b/views/dev/article.njk @@ -0,0 +1,55 @@ +{% extends "templates/development.njk" %} + +{% block title %}{{ title }}{% endblock %} + +{% block meta %} + + + + + + + + +{% endblock %} + +{% block html %} +
    +

    {{ title }}

    +
    +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/views/dev/articles.ejs b/views/dev/articles.ejs deleted file mode 100644 index 1b07ed116..000000000 --- a/views/dev/articles.ejs +++ /dev/null @@ -1,40 +0,0 @@ - - - All Assets - <%- include ../partials/dev %> - - - <%- include ../partials/dev-nav %> -
    -
    -

    All Assets

    - <%= skip + 1 %> - <%= skip + assets.length %> of <%= count %> Assets -
    -
    - <% if (skip === 0) { %> Create a random article<% } %> - <% assets.forEach(function (asset) { %> - -
    -
    <%= asset.title %>
    - Created <%= asset.created_at.toLocaleString('en-US') %> -
    - <%= asset.url %> -
    - <% }) %> -
    - <% if (count !== assets.length) { %> - - <% } %> -
    - - diff --git a/views/dev/articles.njk b/views/dev/articles.njk new file mode 100644 index 000000000..e204c39fc --- /dev/null +++ b/views/dev/articles.njk @@ -0,0 +1,37 @@ +{% extends "templates/development.njk" %} + +{% block title %}All Assets{% endblock %} + +{% block html %} +
    +
    +

    All Assets

    + {{ skip + 1 }} - {{ skip + assets.length }} of {{ count }} Assets +
    + + {% if count !== assets.length %} + + {% endif %} +
    +{% endblock %} diff --git a/views/embed/stream.ejs b/views/embed/stream.ejs deleted file mode 100644 index a1c708b55..000000000 --- a/views/embed/stream.ejs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - <%- include ../partials/head %> - - - <%- include ../partials/custom-css %> - - -
    - - - diff --git a/views/embed/stream.njk b/views/embed/stream.njk new file mode 100644 index 000000000..ddc620f8e --- /dev/null +++ b/views/embed/stream.njk @@ -0,0 +1,16 @@ +{% extends "templates/base.njk" %} + +{% block title %}Talk{% endblock %} + +{% block css %} + + +{% endblock %} + +{% block html %} +
    +{% endblock %} + +{% block js %} + +{% endblock %} diff --git a/views/login.ejs b/views/login.ejs deleted file mode 100644 index 671b25c83..000000000 --- a/views/login.ejs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - <%- include partials/head %> - - <%- include partials/custom-css %> - - -
    - - - - diff --git a/views/login.njk b/views/login.njk new file mode 100644 index 000000000..2288898c7 --- /dev/null +++ b/views/login.njk @@ -0,0 +1,16 @@ +{% extends "templates/base.njk" %} + +{% block title %}Talk - Login{% endblock %} + +{% block css %} + +{% endblock %} + +{% block html %} +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/views/partials/account.ejs b/views/partials/account.ejs deleted file mode 100644 index ff2166816..000000000 --- a/views/partials/account.ejs +++ /dev/null @@ -1,4 +0,0 @@ -<%- include head %> - - -<%- include custom-css %> diff --git a/views/partials/custom-css.ejs b/views/partials/custom-css.ejs deleted file mode 100644 index d453c37e1..000000000 --- a/views/partials/custom-css.ejs +++ /dev/null @@ -1 +0,0 @@ -<% if (locals.customCssUrl) { %><% } %> diff --git a/views/partials/custom-css.njk b/views/partials/custom-css.njk new file mode 100644 index 000000000..6d725e31e --- /dev/null +++ b/views/partials/custom-css.njk @@ -0,0 +1,3 @@ +{% if customCssUrl %} + +{% endif %} diff --git a/views/partials/data.ejs b/views/partials/data.ejs deleted file mode 100644 index f0bbf5925..000000000 --- a/views/partials/data.ejs +++ /dev/null @@ -1,3 +0,0 @@ -<%_ if (data != null) { _%> - -<%_ } _%> \ No newline at end of file diff --git a/views/partials/data.njk b/views/partials/data.njk new file mode 100644 index 000000000..4edaf2e2e --- /dev/null +++ b/views/partials/data.njk @@ -0,0 +1,3 @@ +{% if data %} + +{% endif %} diff --git a/views/partials/dev-nav.ejs b/views/partials/dev-nav.ejs deleted file mode 100644 index 088c52c20..000000000 --- a/views/partials/dev-nav.ejs +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/views/partials/favicon.njk b/views/partials/favicon.njk new file mode 100644 index 000000000..5ec2aad8a --- /dev/null +++ b/views/partials/favicon.njk @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/views/partials/head.ejs b/views/partials/head.ejs deleted file mode 100644 index dc3df7103..000000000 --- a/views/partials/head.ejs +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - -<%- include data %> - - diff --git a/views/partials/nav.njk b/views/partials/nav.njk new file mode 100644 index 000000000..ecea9018d --- /dev/null +++ b/views/partials/nav.njk @@ -0,0 +1,8 @@ + diff --git a/views/templates/account.njk b/views/templates/account.njk new file mode 100644 index 000000000..7e1b6e6c9 --- /dev/null +++ b/views/templates/account.njk @@ -0,0 +1,6 @@ +{% extends "templates/base.njk" %} + +{% block css %} + + +{% endblock %} diff --git a/views/templates/base.njk b/views/templates/base.njk new file mode 100644 index 000000000..23048151e --- /dev/null +++ b/views/templates/base.njk @@ -0,0 +1,39 @@ + + + + {# Meta tags #} + + + {% block meta %}{% endblock %} + + {# Favicon Configuration #} + {% include "partials/favicon.njk" %} + + {# CSP and security headers #} + {% block security %}{% endblock %} + + {# Title #} + {% block title %}{% endblock %} + + {# CSS #} + + + + {% include "partials/custom-css.njk" %} + {% block css %}{% endblock %} + + {# Static data injection #} + {% include "partials/data.njk" %} + + {# Configuration #} + + + + {% block body %} + {% block html %} +
    + {% endblock %} + {% endblock %} + {% block js %}{% endblock %} + + diff --git a/views/partials/dev.ejs b/views/templates/development.njk similarity index 54% rename from views/partials/dev.ejs rename to views/templates/development.njk index 691b548e5..f7fab6682 100644 --- a/views/partials/dev.ejs +++ b/views/templates/development.njk @@ -1,5 +1,17 @@ - +{% extends "templates/base.njk" %} + +{# Null out the security block, we don't want/need that in development #} +{% block security %}{% endblock %} + +{% block css %} - + +{% endblock %} + +{% block body %} + {% include "partials/nav.njk" %} + + {% block html %}{% endblock %} +{% endblock %} diff --git a/yarn.lock b/yarn.lock index f0da6d006..770dcc7ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2392,6 +2392,12 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" +consolidate@0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.14.0.tgz#b03acd566a2565ca96e99f44fd1417486b4df88d" + dependencies: + bluebird "^3.1.1" + constantinople@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/constantinople/-/constantinople-3.1.0.tgz#7569caa8aa3f8d5935d62e1fa96f9f702cd81c79" From d83c2fd566e53d00350bac570c5a8177628d4b4c Mon Sep 17 00:00:00 2001 From: Mendel Konikov Date: Thu, 17 May 2018 20:03:12 -0400 Subject: [PATCH 084/162] Only display paragraph if delete requested --- .../client/components/DeleteMyAccount.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js b/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js index 179fa0193..6eae91043 100644 --- a/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js +++ b/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js @@ -63,14 +63,13 @@ class DeleteMyAccount extends React.Component {

    {t('delete_request.delete_my_account_description')}

    -

    - {scheduledDeletionDate && - t( - 'delete_request.already_submitted_request_description', - moment(scheduledDeletionDate).format('MMM Do YYYY, h:mm:ss a') - )} -

    {scheduledDeletionDate ? ( +

    + {t( + 'delete_request.already_submitted_request_description', + moment(scheduledDeletionDate).format('MMM Do YYYY, h:mm:ss a') + )} +

    From c84a9716a11ccdcc2942f92a0e00b66aa7c4747b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 17 May 2018 18:37:50 -0600 Subject: [PATCH 085/162] added support for csp --- middleware/contentSecurityPolicy.js | 49 +++++++++++++++++++ middleware/nonce.js | 9 ++++ .../views/unsubscribe-notifications.njk | 11 ++++- .../server/views/download.njk | 2 +- routes/api/v1/csp.js | 33 +++++++++++++ routes/api/v1/index.js | 1 + routes/index.js | 16 +++--- routes/plugins.js | 10 ++-- views/account/email/confirm.njk | 2 +- views/account/password/reset.njk | 2 +- views/admin.njk | 2 +- views/login.njk | 2 +- 12 files changed, 120 insertions(+), 19 deletions(-) create mode 100644 middleware/contentSecurityPolicy.js create mode 100644 middleware/nonce.js create mode 100644 routes/api/v1/csp.js diff --git a/middleware/contentSecurityPolicy.js b/middleware/contentSecurityPolicy.js new file mode 100644 index 000000000..992e41e36 --- /dev/null +++ b/middleware/contentSecurityPolicy.js @@ -0,0 +1,49 @@ +const helmet = require('helmet'); +const { WEBSOCKET_LIVE_URI } = require('../config'); +const { BASE_PATH, BASE_URL, STATIC_URL } = require('../url'); +const { URL } = require('url'); + +// websocketSrc represents the host where we can connect for websocket requests. +const websocketSrc = new URL(WEBSOCKET_LIVE_URI || BASE_URL).host; + +// staticSrc represents any static asset hosted on the static host. +const staticSrc = new URL(STATIC_URL).host; + +// nonceSrc represents the nonce source that is used to indicate a safe resource +// to load. +const nonceSrc = (req, res) => `'nonce-${res.locals.nonce}'`; + +module.exports = helmet.contentSecurityPolicy({ + directives: { + reportUri: `${BASE_PATH}api/v1/csp`, // report all policy violations to our reporting uri + defaultSrc: ["'none'"], // by default, do not allow anything at all + scriptSrc: [ + "'self'", + 'https://ajax.googleapis.com', // for jquery + staticSrc, // for any static files loaded from a cdn + nonceSrc, + ], + styleSrc: [ + "'self'", + 'https://maxcdn.bootstrapcdn.com', // for bootstrap css + 'https://fonts.googleapis.com', // for google fonts + 'https://code.getmdl.io', // for mdl css + staticSrc, // for any static files loaded from a cdn + nonceSrc, + ], + connectSrc: ["'self'", websocketSrc], + fontSrc: [ + "'self'", + 'https://maxcdn.bootstrapcdn.com', // for font-awesome + 'https://fonts.gstatic.com', // for google fonts + staticSrc, // for any static files loaded from a cdn + nonceSrc, + ], + imgSrc: [ + "'self'", + staticSrc, // for any static files loaded from a cdn + nonceSrc, + ], + }, + browserSniff: false, +}); diff --git a/middleware/nonce.js b/middleware/nonce.js new file mode 100644 index 000000000..1181fac85 --- /dev/null +++ b/middleware/nonce.js @@ -0,0 +1,9 @@ +const uuid = require('uuid/v4'); + +// nonce is designed to create a random value that can be used in conjunction +// with the csp middleware. +module.exports = (req, res, next) => { + res.locals.nonce = uuid(); + + next(); +}; diff --git a/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.njk b/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.njk index f8191d525..5080277f0 100644 --- a/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.njk +++ b/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.njk @@ -2,10 +2,17 @@ {% block title %}{{ t('talk-plugin-notifications.unsubscribe_page.unsubscribe') }}{% endblock %} +{% block css %} +{{ super() }} + +{% endblock %} + {% block html %}
    {{ t('talk-plugin-notifications.unsubscribe_page.token_invalid') }}
    - +
    {{ t('talk-plugin-notifications.unsubscribe_page.are_unsubscribed') }}
    {{ t('talk-plugin-notifications.unsubscribe_page.click_to_confirm') }} @@ -15,7 +22,7 @@ {% block js %} - - - - + {% endblock %} diff --git a/views/login.njk b/views/login.njk index 2288898c7..6a3c1112f 100644 --- a/views/login.njk +++ b/views/login.njk @@ -11,6 +11,6 @@ {% endblock %} {% block js %} - + {% endblock %} From 5f410276c6ace553e5821b1edf1579445ab60ef9 Mon Sep 17 00:00:00 2001 From: Mendel Konikov Date: Thu, 17 May 2018 20:41:59 -0400 Subject: [PATCH 086/162] Show overflow text on download comments button when disabled --- .../client/components/DownloadCommentHistory.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/talk-plugin-profile-data/client/components/DownloadCommentHistory.css b/plugins/talk-plugin-profile-data/client/components/DownloadCommentHistory.css index 55ea39969..cd523a0cd 100644 --- a/plugins/talk-plugin-profile-data/client/components/DownloadCommentHistory.css +++ b/plugins/talk-plugin-profile-data/client/components/DownloadCommentHistory.css @@ -1,6 +1,9 @@ .button { margin: 0; - + &:disabled { + overflow: visible; + height: auto; + } i { font-size: inherit; vertical-align: sub; @@ -9,4 +12,4 @@ .most_recent { color: #808080; -} +} \ No newline at end of file From ebcff4d95e378f46fc0b1b693e9bb8250a86a490 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 17 May 2018 18:58:01 -0600 Subject: [PATCH 087/162] patched migration bugs --- models/schema/user.js | 10 +++++++++ services/actions.js | 49 +++++++++++++++++++------------------------ 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/models/schema/user.js b/models/schema/user.js index a4ccfd185..a27b8dc39 100644 --- a/models/schema/user.js +++ b/models/schema/user.js @@ -339,6 +339,11 @@ User.virtual('banned') }) .set(function(status) { this.status.banned.status = status; + + if (!this.status.banned.history) { + this.status.banned.history = []; + } + this.status.banned.history.push({ status, created_at: new Date(), @@ -357,6 +362,11 @@ User.virtual('suspended') }) .set(function(until) { this.status.suspension.until = until; + + if (!this.status.suspension.history) { + this.status.suspension.history = []; + } + this.status.suspension.history.push({ until, created_at: new Date(), diff --git a/services/actions.js b/services/actions.js index 1c95e5128..790adc6a0 100644 --- a/services/actions.js +++ b/services/actions.js @@ -2,7 +2,7 @@ const ActionModel = require('../models/action'); const CommentModel = require('../models/comment'); const UserModel = require('../models/user'); const _ = require('lodash'); -const errors = require('../errors'); +const { ErrAlreadyExists } = require('../errors'); const incrActionCounts = async (action, value) => { const ACTION_TYPE = action.action_type.toLowerCase(); @@ -41,35 +41,30 @@ const incrActionCounts = async (action, value) => { * @param {object} update the update operation for the mongo findOneAndUpdate op * @param {object} options the options operation for the mongo findOneAndUpdate op */ -const findOnlyOneAndUpdate = async (query, update, options = {}) => - new Promise((resolve, reject) => { - ActionModel.findOneAndUpdate( - query, - update, - Object.assign({}, options, { - // Use raw result to get `updatedExisting`. - passRawResult: true, +const findOnlyOneAndUpdate = async (query, update, options = {}) => { + const raw = await ActionModel.findOneAndUpdate( + query, + update, + Object.assign({}, options, { + // Use raw result to get `updatedExisting`. + rawResult: true, - // Ensure that if it's new, we return the new object created. - new: true, + // Ensure that if it's new, we return the new object created. + new: true, - // Perform an upsert in the event that this doesn't exist. - upsert: true, + // Perform an upsert in the event that this doesn't exist. + upsert: true, - // Set the default values if not provided based on the mongoose models. - setDefaultsOnInsert: true, - }), - (err, doc, raw) => { - if (err) { - return reject(err); - } - if (raw.lastErrorObject.updatedExisting) { - return reject(new errors.ErrAlreadyExists(raw.value)); - } - return resolve(raw.value); - } - ); - }); + // Set the default values if not provided based on the mongoose models. + setDefaultsOnInsert: true, + }) + ); + if (raw.lastErrorObject.updatedExisting) { + throw new ErrAlreadyExists(raw.value); + } + + return raw.value; +}; module.exports = class ActionsService { /** From 8ee90387f689b5eba29cc1ed24a2304a8c100e8e Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 7 May 2018 12:33:47 +0200 Subject: [PATCH 088/162] German translations for v4.4.1 --- locales/de.yml | 60 ++++++++++++---- .../talk-plugin-local-auth/translations.yml | 70 +++++++++++++++++++ .../client/translations.yml | 51 ++++++++++++++ .../talk-plugin-profile-data/translations.yml | 37 ++++++++++ 4 files changed, 206 insertions(+), 12 deletions(-) diff --git a/locales/de.yml b/locales/de.yml index d2b247fbc..94ecfc1f3 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -20,10 +20,13 @@ de: bio_offensive: "Diese Biographie ist unangemessen" cancel: "Abbrechen" confirm_email: + email_confirmation: "E-Mail-Bestätigung" click_to_confirm: "Unten klicken, um E-Mail-Adresse zu bestätigen" confirm: "Bestätigen" password_reset: + mail_sent: 'Falls Sie eine registriertes Konto haben, wurde Ihnen ein Zurücksetzen-Link an diese E-Mail-Adresse geschickt' set_new_password: "Passwort ändern" + change_password_help: "Bitte geben Sie ein neues Passwort ein. Benutzen Sie ein sicheres!" new_password: "Neues Passwort" new_password_help: "Das Passwort benötigt mindestens 8 Zeichen" confirm_new_password: "Neues Passwort bestätigen" @@ -120,9 +123,10 @@ de: custom_css_url: "Benutzerdefinierte CSS-URL" custom_css_url_desc: "URL eines CSS-Stylesheets zum Überschreiben des Standard-Designs" days: Tage - description: "Als Administrator können Sie die Einstellungen für den Kommentarbereich dieses Artikels anpassen:" + description: "Ändern Sie die Einstellungen für den Kommentarbereich dieses Artikels." domain_list_text: "Geben Sie Domains an, für die diese Talk-Instanz freigegeben werden soll, z.B. für lokale Test- oder Produktionsumgebungen (Bsp.: localhost:3000 staging.domain.com domain.com)." domain_list_title: "Zugelassene Domains" + edit_info: "Information bearbeiten" edit_comment_timeframe_heading: "Zeitlimit zur Bearbeitung von Kommentaren" edit_comment_timeframe_text_pre: "Kommentatoren haben" edit_comment_timeframe_text_post: "Sekunden Zeit, um ihre Kommentare zu bearbeiten." @@ -148,17 +152,31 @@ de: open_stream_configuration: "Dieser Kommentarbereich ist momentan geöffnet. Nach dem Schließen dieses Kommentarbereich wird es nicht mehr möglich sein, zu kommentieren. Bestehende Kommentare bleiben sichtbar." require_email_verification: "E-Mail-Bestätigung erforderlich" require_email_verification_text: "Neue Nutzer müssen ihre E-Mail-Adresse bestätigen." + save: "Speichern" save_changes: "Änderungen speichern" shortcuts: Tastaturkürzel sign_out: "Abmelden" stories: Artikel stream_settings: "Einstellungen Kommentarbereich" + access_message: "Sie müssen Administrator sein, um auf die Einstellungen zuzugreifen. Fragen Sie ggf. einen Administrator, der Ihnen mehr Recht zuweisen kann!" suspect_word_title: "Liste verdächtiger Wörter" suspect_word_text: "Kommentare, die diese Wörter oder Phrasen enthalten (unabhängig von Groß-/Kleinschreibung), werden im Kommentarbereich markiert. Geben Sie ein Wort ein und bestätigen Sie mit Eingabetaste oder Tab. Es ist auch möglich, einen komma-separierten Text einzufügen." tech_settings: "Technische Einstellungen" + organization_information: "Über die Organisation" + organization_info_copy: "Wir verwenden diese Informationen in automatisierten E-Mail-Benachrichtigungen, die Talk versendet. Damit können Nutzer Ihre Organisation identifizieren und sie haben die Möglichkeit bei Problemen in Kontakt mit Ihnen zu treten." + organization_info_copy_2: "Wir empfehlen, einee generische E-Mail-Adresse (z.B. community@yournewsroom.com) für diesen Zweck einzurichten. Die kann über die Zeit gleich bleiben, und gibt nach außen keine Namen preis, die von Nutzern im Fall von Konflikten für persönliche Angriffe missbraucht werden könnten." + organization_details: "Details zur Organisation" + organization_name: "Name der Organisation" + organization_contact_email: "E-Mail-Adresse der Organisation" title: "Kommentarbereich konfigurieren" weeks: Wochen wordlist: "Gesperrte Wörter" + save_changes_dialog: + unsaved_changes: "Ungespeicherte Änderungen" + copy: "Sie haben einen oder mehrere Änderungen vorgenommen, ohne zu speichern. Möchten Sie jetzt speichern oder die Änderungen verwerfen?" + save_settings: "Einstellungen speichern" + discard: "Verwerfen" + cancel: "Abbrechen" continue: "Fortfahren" createdisplay: check_the_form: "Ungültige Eingabe. Bitte prüfen Sie die Felder." @@ -200,11 +218,17 @@ de: we_received_a_request: "Wir haben eine Anfrage erhalten, Ihr Passwort zurückzusetzen. Sollten Sie dies nicht angefordert haben, können Sie diese Nachricht ignorieren." if_you_did: "Falls doch," please_click: "klicken Sie bitte hier zum Zurücksetzen" + subject: "Passwort zurücksetzen" + password_change: + subject: "{0} Passwort-Änderung" + body: "Das Passwort Ihres Benutzerkontos wurde geändert.\n\nFalls Sie diese Änderung nicht angefordert haben, kontaktieren Sie uns bitte unter {0}." embedlink: copy: "In die Zwischenablage kopieren" error: + PASSWORD_INCORRECT: "Ihr bestehendes Passwort wurde falsch eingegeben" COMMENT_PARENT_NOT_VISIBLE: "Der Kommentar, auf den Sie antworten möchten, wurde entfernt oder existiert nicht." EMAIL_VERIFICATION_TOKEN_INVALID: "Code zur E-Mail-Bestätigung ist ungültig." + EMAIL_ALREADY_VERIFIED: "E-Mail-Adresse ist bereits bestätigt." PASSWORD_RESET_TOKEN_INVALID: "Ihr Link zum Passwort zurücksetzen ist ungültig." COMMENT_TOO_SHORT: "Kommentare sollten mehr als ein Zeichen enthalten, bitte überprüfen Sie Ihren Kommentar und probieren Sie es erneut." NOT_AUTHORIZED: "Sie sind nicht berechtigt, diese Aktion auszuführen." @@ -227,15 +251,20 @@ de: ALREADY_EXISTS: "Ressource existiert bereits" INVALID_ASSET_URL: "Asset-URL ist ungültig" CANNOT_IGNORE_STAFF: "Mitarbeiter können nicht ignoriert werden." - email: "E-Mail-Adresse ungültig" + INCORRECT_PASSWORD: "Falsches Passwort" + email: "Bitte geben Sie eine gültige E-Mail-Adresse ein." + DELETION_NOT_SCHEDULED: "Löschvorgang wurde nicht geplant" confirm_password: "Passwörter nicht identisch. Bitte erneut überprüfen" network_error: "Server-Verbindung fehlgeschlagen. Bitte überprüfen Sie ihre Internetverbindung und versuchen Sie es erneut." email_not_verified: "E-Mail-Adresse {0} nicht bestätigt." email_password: "E-Mail und/oder Passwort inkorrekt." organization_name: "Namen von Organisationen dürfen nur Buchstaben und Zahlen enthalten." + organization_contact_email: "E-Mail-Adresse der Organisation ist ungültig." password: "Passwort muss mindestens 8 Zeichen enthalten" username: "Nutzernamen dürfen nur Buchstaben, Zahlen und _ enthalten" unexpected: "Unerwarteter Fehler aufgetreten. Es tut uns leid!" + required_field: "Dieses Feld ist erforderlich" + temporarily_suspended: "Ihr Konto ist vorübergehend gesperrt. Es wird wieder aktiviert am {0}. Bei Fragen setzen Sie sich mit uns in Kontakt." flag_comment: "Kommentar melden" flag_reason: "Grund der Meldung (optional)" flag_username: "Nutzername melden" @@ -245,6 +274,7 @@ de: comment: Kommentar comment_is_ignored: "Dieser Kommentar ist nicht sichtbar, da Sie den Nutzer ignorieren." comment_is_rejected: "Sie haben diesen Kommentar abgelehnt." + comment_is_deleted: "Der Kommentar wurde vom Nutzer gelöscht." comment_is_hidden: "Dieser Kommentar ist nicht verfügbar." comments: Kommentare configure_stream: "Konfigurieren" @@ -333,6 +363,7 @@ de: sort: "Sortieren" show_shortcuts: "Tastaturkürzel anzeigen" singleview: "Zen-Modus" + system_withheld: "System Withheld" thismenu: "Dieses Menü öffnen" jump_to_queue: "Zu bestimmter Liste springen" thousand: T @@ -355,6 +386,9 @@ de: report_notif: "Vielen Dank für Ihre Meldung. Unsere Moderatoren wurden informiert und werden sich in Kürze darum kümmern." report_notif_remove: "Ihre Meldung wurde entfernt." reported: Gemeldet + comment_history_blank: + title: Sie haben noch keine Kommentare verfasst + info: Hier wird ein Verlauf Ihrer verfassten Kommentare erscheinen settings: from_settings_page: "Sie können auf Ihrer Profilseite Ihren Kommentarverlauf einsehen." my_comment_history: "Mein Kommentarverlauf" @@ -366,7 +400,7 @@ de: stream: all_comments: "Alle Kommentare" temporarily_suspended: "Entsprechend der Community-Regeln von {0} wurde Ihr Konto vorübergehend gesperrt. Nehmen Sie {1} wieder an der Diskussion teil." - comment_not_found: "Kommentar nicht gefunden" + comment_not_found: "Dieser Kommentar wurde entfernt oder existiert nicht." no_comments: "Es gibt noch keine Kommentare. Schreiben Sie doch einen..." no_comments_and_closed: "Es gab zu diesem Artikel keine Kommentare." step_1_header: "Ein Problem melden" @@ -393,6 +427,8 @@ de: one_hour: "1 Stunde" hours: "{0} Stunden" days: "{0} Tage" + hour: "{0} hours" + day: "{0} days" cancel: "Abbrechen" suspend_user: "Nutzer vorübergehend sperren" email_message_suspend: "Sehr geehrte/r {0}, entsprechend der Community-Richtlinien von {1} wurde Ihr Konto vorübergehend gesperrt. Während der Sperrung können Sie weder kommentieren noch andere Aktionen ausführen. Nehmen Sie {2} wieder an der Diskussion teil." @@ -432,15 +468,15 @@ de: rejected: "Abgelehnte" user_history: "Konto-Verlauf" user_history: - user_banned: "User banned" - ban_removed: "Ban removed" - username_status: "Username {0}" - suspended: "Suspended, {0}" - suspension_removed: "Suspension removed" + user_banned: "Nutzer gesperrt" + ban_removed: "Sperrung aufgehoben" + username_status: "Nutzername {0}" + suspended: "Vorübergehend gesperrt, {0}" + suspension_removed: "Vorübergehende Sperrung aufgehoben" system: "System" - date: "Date" - action: "Action" - taken_by: "Taken By" + date: "Datum" + action: "Aktion" + taken_by: "Durch" user_impersonating: "Gibt sich für jemand anderen aus" user_no_comment: "Sie haben noch keinen Kommentar abgegeben. Teilen Sie Ihre Meinung mit uns!" username_offensive: "Dieser Nutzername ist unangemessen" @@ -458,7 +494,7 @@ de: username: "Nutzername" password: "Passwort" confirm_password: "Passwort bestätigen" - organization_contact_email: "Organization Contact Email" + organization_contact_email: "Kontakt-Adresse der Organisation" save: "Speichern" permitted_domains: title: "Zugelassene Domains" diff --git a/plugins/talk-plugin-local-auth/translations.yml b/plugins/talk-plugin-local-auth/translations.yml index 7f67e33bd..4e6486c08 100644 --- a/plugins/talk-plugin-local-auth/translations.yml +++ b/plugins/talk-plugin-local-auth/translations.yml @@ -68,6 +68,76 @@ en: description_2: "You can change your account settings by visiting" path: "My Profile > Settings" alert: "Email Added!" +de: + email: + email_change_original: + subject: Änderung Ihrer E-Mail-Adresse + body: Ihre E-Mail-Adresse wurde von {0} zu {1} geändert. Falls Sie diese Änderung nicht selbst vorgenommen haben, kontaktieren Sie bitte zur Sicherheit {2}. + error: + NO_LOCAL_PROFILE: Mit diesem Benutzerkonto ist keine E-Mail-Adresse verbunden. + LOCAL_PROFILE: Es ist bereits eine bestätigte E-Mail-Adresse mit diesem Benutzerkonto verbunden. + INCORRECT_PASSWORD: Das Passwort war nicht korrekt. + talk-plugin-local-auth: + change_password: + change_password: "Passwort ändern" + passwords_dont_match: "Die Passwörter stimmen nicht überein" + required_field: "Diese Angabe ist erforderlich" + forgot_password: "Passwort vergessen?" + save: "Speichern" + cancel: "Abbrechen" + edit: "Ändern" + changed_password_msg: "Passwort geändert - Ihr Passwort wurde erfolgreich geändert" + forgot_password_sent: "Passwort vergessen - Wir haben Ihnen eine E-Mail zum Zurücksetzen des Passwortes geschickt" + change_username: + change_username_note: "Nutzernamen können nur alle 14 Tage geändert werden. Ihr Nutzername ist zur Zeit nicht editierbar." + save: "Speichern" + edit_profile: "Profil ändern" + cancel: "Abbrechen" + confirm_username_change: "Änderung des Nutzernamens bestätigen" + description: "Sie möchten Ihren Nutzernamen ändern: der neue Nutzername wird an allen alten und neuen Kommentaren erscheinen." + old_username: "Alter Nutzername" + new_username: "Neuer Nutzername" + bottom_note: "Achtung: die nächste Änderung des Nutzernamens ist erst nach 14 Tagen möglich" + confirm_changes: "Änderung bestätigen" + username_does_not_match: "Die Nutzernamen stimmen nicht überein" + cant_be_equal: "Der neue Nutzername {0} muss sich vom alten unterscheiden." + changed_username_success_msg: "Nutzername geändert - Ihr Nutzername wurde erfolgreich aktualisiert. Die nächste Änderung des Nutzernamens ist erst nach 14 Tagen möglich." + change_username_attempt: "Der Nutzername kann zur Zeit nicht aktualisiert werden. Änderungen sind nur nach jeweils 14 Tagen möglich." + change_email: + confirm_email_change: "Änderung der E-Mail-Adresse bestätigen" + description: "Sie versuchen, Ihre E-Mail-Adresse ändern: die neue E-Mail-Adresse wird zum Login sowie für Benachrichtigungen bzgl. Ihres Benutzerkontos verwendet." + old_email: "Alte E-Mail-Adresse" + new_email: "Neue E-Mail-Adresse" + enter_password: "Passwort" + incorrect_password: "Passwort nicht korrekt" + confirm_change: "Änderung bestätigen" + cancel: "Abbrechen" + change_email_msg: "E-Mail-Adresse erfolgreich aktualisiert - die neue E-Mail-Adresse ab sofort zum Anmelden und für Benachrichtigungen verwendet." + add_email: + add_email_address: "E-Mail-Adresse hinzufügen" + enter_email_address: "E-Mail-Adresse:" + invalid_email_address: "Ungültige E-Mail-Adresse" + confirm_email_address: "Bestätigung der E-Mail-Adresse:" + email_does_not_match: "Die E-Mail-Adressen stimmen nicht überein" + insert_password: "Passwort auswählen:" + required_field: "Dieses Feld ist erforderlich" + done: "Fertig" + content: + title: "E-Mail-Adresse hinzufügen" + description: "Aus Sicherheitsgründen benötigen wir eine E-Mail-Adresse zu jedem Benutzerkonto. Ihre E-Mail-Adresse wird für folgendes verwendet:" + item_1: "Benachrichtigungen über Änderungen am Benutzerkonto (Nutzername, E-Mail-Adresse, Passwort)" + item_2: "Ermöglicht den Download des eigenen Kommentar-Archivs" + item_3: "Kommentar-Benachrichtigungen erhalten, die Sie explizit angefordert haben" + verify: + title: "E-Mail-Adresse bestätigen" + description: "Wir haben einen E-Mail an {0} geschickt. Bitte bestätigen Sie Ihre E-Mail-Adresse, um damit Benachrichtigungen über Änderungen am Benutzerkonto zu erhalten." + added: + title: "E-Mail-Adresse hinzugefügt" + description: "Ihre E-Mail-Adresse wurde dem Benutzerkonto hinzugefügt." + subtitle: "Sie möchten Ihre E-Mail-Adresse ändern?" + description_2: "Sie können Ihre Konto-Einstellugen ändern unter" + path: "Mein Profil > Profil-Einstellungen" + alert: "E-Mail-Adresse hinzugefügt!" es: talk-plugin-local-auth: change_password: diff --git a/plugins/talk-plugin-profile-data/client/translations.yml b/plugins/talk-plugin-profile-data/client/translations.yml index d1fdccb73..046c3c3b3 100644 --- a/plugins/talk-plugin-profile-data/client/translations.yml +++ b/plugins/talk-plugin-profile-data/client/translations.yml @@ -49,3 +49,54 @@ en: subtitle: "Are you sure you want to delete your account?" description: "To confirm you would like to delete your account please type in the following phrase into the text box below:" type_to_confirm: "Type phrase below to confirm" +de: + download_request: + section_title: "Mein Kommentar-Archiv herunterladen" + you_will_get_a_copy: "Sie werden eine E-Mail mit einem Download-Link erhalten. Sie können" + download_rate: "eine Download-Anfrage alle {0} Tage stellen" + most_recent_request: "Ihre letzte Anfrage" + request: "Kommentar-Archiv anfordern" + rate_limit: "Sie können die nächste Anfrage stellen in {0}" + hours: "{0} Stunden" + days: "{0} Tagen" + hour: "{0} Stunde" + day: "{0} Tag" + download_preparing: "Bereite Konto-Download vor - Überprüfen Sie Ihr E-Mail-Postfach, sie sollten den Download-Link demnächst erhalten" + delete_request: + account_deletion_cancelled: 'Konto-Löschung abgebrochen - Ihre Anfrage, Ihr Konto zu löschen wurde abgebrochen.' + account_deletion_requested: 'Konto-Löschung angefordert' + received_on: "Die Anfrage, Ihr Konto zu löschen haben wir erhalten am " + cancel_request_description: "Fall Sie ihr Konto wieder aktivieren möchten, können Sie die Lösch-Anfrage hier abbrechen" + before: "vorher" + cancel_account_deletion_request: "Konto-Lösch-Anfrage abbrechen" + delete_my_account: "Mein Benutzerkonto löschen" + delete_my_account_description: "Mit dem Löschen Ihres Kontos werden Ihr Profil sowie alle Ihre Kommentare dauerhaft von dieser Website entfernt." + already_submitted_request_description: "Sie haben bereits eine Lösch-Anfrage gestellt. Ihr Konto wird nach dem {0} gelöscht. Bis zu diesem Zeitpunkt können Sie die Anfrage noch abbrechen" + your_request_submitted_description: "Ihre Lösch-Anfrage wurde übermittelt und eine Bestätigungsanfrage an die dem Konto zugehörige E-Mail-Adresse geschickt." + your_account_deletion_scheduled: "Ihr Benutzerkonto wird gelöscht nach dem:" + changed_your_mind: "Haben Sie Ihre Meinung geändert?" + simply_go_to: "Gehen Sie einfach vor dem Zeitpunkt zu Ihrem Konto und klicken Sie" + tell_us_why: "Sagen Sie uns warum" + feedback_copy: "Wir würden gern erfahren, warum Sie sich entschieden haben, Ihr Konto zu löschen. Schicken Sie uns eine E-Mail mit Feedback an" + done: "Fertig" + cancel: "Abbrechen" + proceed: "Fortfahren" + input_is_not_correct: "Die Eingabe ist nicht korrekt" + step_0: + you_are_attempting: "Sie versuchen Ihr Konto zu löschen. Das bedeutet:" + item_1: "Alle Ihre Kommentare werden von der Website entfernt" + item_2: "Alle Ihre Kommentare werden aus unserer Datenbank gelöscht" + item_3: "Ihr Nutzername und Ihre E-Mail-Adresse werden aus unserem System gelöscht" + step_1: + subtitle: "Wann wird mein Benutzerkonto entfernt?" + description: "Ihr Konto wird {0} Stunden nachdem Sie die Anfrage gestellt haben gelöscht." + subtitle_2: "Kann ich weiterhin Kommentare schreiben, bis mein Konto gelöscht wird?" + description_2: "Ja, Sie können kommentieren, antworten usw. bis die {0} Stunden abgelaufen sind." + step_2: + description: "Bevor Ihr Konto gelöscht wird, empfehlen wir Ihnen, Ihr Kommentar-Archiv herunterzuladen. Nach der Konto-Löschung ist dies nicht mehr möglich." + to_download: "Um Ihr Konto-Archiv herunterzuladen gehen sie zu:" + path: "Profil > Mein Kommentar-Archiv herunterladen" + step_3: + subtitle: "Sind Sie sicher, dass Sie Ihr Benutzerkonto löschen möchten?" + description: "Um zu bestätigen, dass Sie Ihr Konto löschen möchten, geben Sie bitte folgende Zeichen in das Textfeld ein:" + type_to_confirm: "Zur Bestätigung Zeichenfolge eingeben" diff --git a/plugins/talk-plugin-profile-data/translations.yml b/plugins/talk-plugin-profile-data/translations.yml index 51555d305..c3b7e40ca 100644 --- a/plugins/talk-plugin-profile-data/translations.yml +++ b/plugins/talk-plugin-profile-data/translations.yml @@ -35,3 +35,40 @@ en: body: "You have cancelled your account deletion request for {0}. Your account is now reactivated." error: DOWNLOAD_TOKEN_INVALID: "Your download link is not valid." +de: + download_landing: + download_your_account: "Mein Kommentar-Archiv herunterladen" + download_details: "Ihr Kommentar-Archiv wird als ZIP-Datei bereitgestellt. Nach dem Entpacken erhalten Sie eine CSV-Datei, die einfach in ein Tabellenkalkulationsprogramm importiert werden kann." + all_information_included: "Für jeden Ihrer Kommentare sind folgende Informationen enthalten:" + information_included: + date: "Wann Sie den Kommentar geschrieben haben" + url: "Die dauerhafte URL (Internetadresse) des Kommentars" + body: "Der Kommentar-Text" + asset_url: "Die URL (Internetadresse) des Artikels an dem der Kommentar erscheint" + confirm: "Kommentar-Archiv herunterladen" + email: + download: + subject: "Ihre Kommentare sind zum Download bereit: {0}" + download_link_ready: "Hier klicken, um Ihre Kommentare von {0} bis {1} herunterzuladen:" + download_archive: "Archiv herunterladen" + delete: + subject: "Ihr Benutzerkonto bei {0} ist zur Löschung vorgesehen" + body: | + Wir haben eine Anfrage erhalten, Ihr Benutzerkonto zu löschen. Die Löschung ist geplant für den {1}. + + Nach diesem Zeitpunkt werden alle Ihre Kommentare von der Website und aus unserer Datenbank gelöscht. Außerdem werden Ihr Nutzername und Ihre E-Mail-Adresse aus unserem System enfernt. + + Falls Sie es sich noch anders überlegen, können Sie sich bis spätestens zum angegebenen Lösch-Zeitpunkt einloggen und die Lösch-Anfrage abbrechen. + deleted: + subject: "Ihre Benutzerkonto bei {0} wurde gelöscht" + body: | + Ihr Kommentar-Konto bei {0} ist nun gelöscht. Schade, auf Wiedersehen! + + Falls Sie sich in Zukunft erneut an der Diskussion beteiligen möchten, können Sie jederzeit ein neues Benutzerkonto einrichten. + + Wenn Sie Lust haben, schreiben Sie uns doch eine Rückmeldung, Feedback, oder Kritik an {1}, damit wir unsere Community verbessern können. Vielen Dank! + cancelDelete: + subject: "Die Lösch-Anfrage für Ihr Benutzerkonto bei {0} wurde abgebrochen" + body: "Sie haben die Lösch-Anfrage für Ihr Benutzerkonto bei {0} abgebrochen. Das Konto ist nun wieder aktiv." + error: + DOWNLOAD_TOKEN_INVALID: "Der Download-Link ist ungültig." From 1f40d6b7bbf79a81c678157f18f67ecc232c71db Mon Sep 17 00:00:00 2001 From: okbel Date: Fri, 18 May 2018 13:05:46 -0300 Subject: [PATCH 089/162] replacing by a one liner :) --- .../coral-admin/src/routes/Community/components/People.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/client/coral-admin/src/routes/Community/components/People.js b/client/coral-admin/src/routes/Community/components/People.js index 6d1457cb8..ce9a8b75a 100644 --- a/client/coral-admin/src/routes/Community/components/People.js +++ b/client/coral-admin/src/routes/Community/components/People.js @@ -132,12 +132,7 @@ class People extends React.Component { {user.email ? user.email - : user.profiles.map(({ id }, i) => { - if (i === user.profiles.length - 1) { - return id; - } - return `${id}, `; - })} + : user.profiles.map(p => p.id).join(', ')} From 0e8065cdf97452e583e79896eb7bf26462506c90 Mon Sep 17 00:00:00 2001 From: okbel Date: Fri, 18 May 2018 13:31:50 -0300 Subject: [PATCH 090/162] Styling --- .../src/components/KarmaTooltip.css | 6 ++- .../coral-admin/src/components/UserDetail.css | 10 ++++- .../coral-admin/src/components/UserDetail.js | 44 ++++++++----------- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/client/coral-admin/src/components/KarmaTooltip.css b/client/coral-admin/src/components/KarmaTooltip.css index 1f7e1b732..dc766c5f0 100644 --- a/client/coral-admin/src/components/KarmaTooltip.css +++ b/client/coral-admin/src/components/KarmaTooltip.css @@ -1,7 +1,7 @@ .karmaTooltip { position: relative; display: inline-block; - margin: 0 4px; + margin: 2px 4px 0; } .icon { @@ -13,6 +13,10 @@ -webkit-touch-callout:none; user-select: none; -webkit-tap-highlight-color:rgba(0,0,0,0); + + > i { + vertical-align: baseline; + } } .icon:hover { diff --git a/client/coral-admin/src/components/UserDetail.css b/client/coral-admin/src/components/UserDetail.css index 0f034d419..ffd076cdb 100644 --- a/client/coral-admin/src/components/UserDetail.css +++ b/client/coral-admin/src/components/UserDetail.css @@ -35,6 +35,10 @@ margin-right: 20px; } +.karmaStat { + display: flex; +} + .stat:last-child { margin-right: 0px; } @@ -47,20 +51,22 @@ font-size: 0.9em; line-height: normal; letter-spacing: 0.4px; - min-width: 35px; - display: inline-block; + min-width: 25px; + display: block; } .statResult { font-size: 1.5em; padding: 5px 0; display: inline-block; + text-align: center; } .statReportResult, .statKarmaResult { color: white; margin: 5px 0; font-weight: 400; + text-align: center; } .statReportResult.reliable, .statKarmaResult.good { diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 070f22ba4..5573d2166 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -205,29 +205,23 @@ class UserDetail extends React.Component {
    • -
      - - {t('user_detail.total_comments')} - -
      + + {t('user_detail.total_comments')} + {totalComments}
    • -
      - - {t('user_detail.reject_rate')} - -
      + + {t('user_detail.reject_rate')} + {rejectedPercent.toFixed(1)}%
    • -
      - - {t('user_detail.reports')} - -
      + + {t('user_detail.reports')} +
    • -
    • +
    • {t('user_detail.karma')} - + + {user.reliable.commenterScore} +
      - - {user.reliable.commenterScore} - +
    From ffa6b99d09aadc14e4c6109cdce3148cad5ff0ac Mon Sep 17 00:00:00 2001 From: Mendel Konikov Date: Fri, 18 May 2018 12:32:24 -0400 Subject: [PATCH 091/162] Wrap els in div --- .../client/components/DeleteMyAccount.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js b/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js index 6eae91043..c2055a16e 100644 --- a/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js +++ b/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js @@ -64,15 +64,17 @@ class DeleteMyAccount extends React.Component { {t('delete_request.delete_my_account_description')}

    {scheduledDeletionDate ? ( -

    - {t( +

    +

    + {t( 'delete_request.already_submitted_request_description', moment(scheduledDeletionDate).format('MMM Do YYYY, h:mm:ss a') )} -

    - +

    + +
    ) : (
    diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 666e6c8f2..1664e7cae 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -187,7 +187,7 @@ export const withUserDetailQuery = withQuery( reliable { flagger commenter - commenterScore + commenterKarma } state { status { diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index bb4b35b74..d1bbf501b 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -322,11 +322,11 @@ export default { reliable: { commenter: { $set: calculateReliability( - prev.user.reliable.commenterScore - 1, + prev.user.reliable.commenterKarma - 1, prev.settings.karma.comment ), }, - commenterScore: { + commenterKarma: { $apply: count => count - 1, }, }, @@ -341,11 +341,11 @@ export default { reliable: { commenter: { $set: calculateReliability( - prev.user.reliable.commenterScore + 1, + prev.user.reliable.commenterKarma + 1, prev.settings.karma.comment ), }, - commenterScore: { + commenterKarma: { $apply: count => count + 1, }, }, diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 1f3016a18..5bc102736 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -20,17 +20,17 @@ type Reliability { # `null` if the reliability cannot be determined. flagger: Boolean - # flaggerScore will contains the number of agreed flags vs disagred flag + # flaggerKarma will contains the number of agreed flags vs disagred flag # count. - flaggerScore: Int! + flaggerKarma: Int! # Commenter will be `true` when the commenter is reliable, `false` if not, or # `null` if the reliability cannot be determined. commenter: Boolean - # commenterScore the number of approved comments (not untouched) subtracted by + # commenterKarma the number of approved comments (not untouched) subtracted by # the number of rejected comments. - commenterScore: Int! + commenterKarma: Int! } ################################################################################ diff --git a/services/karma.js b/services/karma.js index e255e3b14..0437ad98a 100644 --- a/services/karma.js +++ b/services/karma.js @@ -84,7 +84,7 @@ class KarmaModel { return KarmaService.isReliable('flag', this.model); } - get flaggerScore() { + get flaggerKarma() { return get(this.model, 'flag.karma', 0); } @@ -92,7 +92,7 @@ class KarmaModel { return KarmaService.isReliable('comment', this.model); } - get commenterScore() { + get commenterKarma() { return get(this.model, 'comment.karma', 0); } } From 97416d421b3355ded1d782d275d807b33f0a1e44 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 23 May 2018 16:17:38 -0600 Subject: [PATCH 115/162] adjusted karma tooltip --- .../src/components/KarmaTooltip.css | 30 ++++++++++++++++++- .../src/components/KarmaTooltip.js | 29 ++++++++++++++++++ .../coral-admin/src/components/UserDetail.js | 24 ++------------- .../coral-admin/src/containers/UserDetail.js | 1 - locales/en.yml | 2 +- 5 files changed, 62 insertions(+), 24 deletions(-) diff --git a/client/coral-admin/src/components/KarmaTooltip.css b/client/coral-admin/src/components/KarmaTooltip.css index dc766c5f0..ff6f8b742 100644 --- a/client/coral-admin/src/components/KarmaTooltip.css +++ b/client/coral-admin/src/components/KarmaTooltip.css @@ -59,6 +59,34 @@ transform: rotate(180deg); } +.menu ul { + list-style: none; + padding: 0; + + li { + display: flex; + justify-content: space-between; + margin: 5px 0; + } +} + +.label { + padding: 4px 5px; + border-radius: 3px; + color: #fff; + font-weight: 400; + text-align: center; + font-size: .9em; + line-height: normal; + letter-spacing: .4px; + min-width: 25px; + display: block; + + /* &.reliable { background-color: #03AB61; } */ + /* &.neutral { background-color: #616161; } */ + &.unreliable { background-color: #F44336; } +} + .descriptionList { padding: 0; margin: 0; @@ -77,4 +105,4 @@ color: #2B7EB5; text-decoration: underline; display: block; -} \ No newline at end of file +} diff --git a/client/coral-admin/src/components/KarmaTooltip.js b/client/coral-admin/src/components/KarmaTooltip.js index 370856f1f..772b119e9 100644 --- a/client/coral-admin/src/components/KarmaTooltip.js +++ b/client/coral-admin/src/components/KarmaTooltip.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import cn from 'classnames'; import { Icon } from 'coral-ui'; import styles from './KarmaTooltip.css'; @@ -8,6 +9,13 @@ import t from 'coral-framework/services/i18n'; const initialState = { menuVisible: false }; class KarmaTooltip extends React.Component { + static propTypes = { + settings: PropTypes.shape({ + reliable: PropTypes.number.isRequired, + unreliable: PropTypes.number.isRequired, + }).isRequired, + }; + state = initialState; toogleMenu = () => { @@ -19,6 +27,7 @@ class KarmaTooltip extends React.Component { }; render() { + const { settings: { unreliable } } = this.props; const { menuVisible } = this.state; return ( @@ -34,6 +43,26 @@ class KarmaTooltip extends React.Component { {menuVisible && (
    {t('user_detail.user_karma_score')} +
      + {/*
    • + Reliable{' '} + + ≥ {reliable} + +
    • +
    • + Neutral{' '} + + < {reliable}, > {unreliable} + +
    • */} +
    • + {t('user_detail.unreliable')}{' '} + + ≤ {unreliable} + +
    • +
    -
  • - - {t('user_detail.reports')} - - - {capitalize(getReliability(user.reliable.flagger))} - -
  • @@ -258,7 +240,7 @@ class UserDetail extends React.Component { {user.reliable.commenterKarma}
    - +
  • diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 1664e7cae..f12b26b30 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -185,7 +185,6 @@ export const withUserDetailQuery = withQuery( provider } reliable { - flagger commenter commenterKarma } diff --git a/locales/en.yml b/locales/en.yml index 273a6d423..9b38c833f 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -466,10 +466,10 @@ en: email: "Email" total_comments: "Total Comments" reject_rate: "Reject Rate" - reports: "Reports" all: "All" rejected: "Rejected" user_history: "User History" + unreliable: "Unreliable" karma: "Karma" learn_more: "Learn More" user_karma_score: "User Karma Score" From 9a73c6db59ebde11429bc96a2e62013d115bfb44 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 23 May 2018 16:18:10 -0600 Subject: [PATCH 116/162] "Trust" flag to "Karma" and updated translations --- locales/ar.yml | 149 +--------------------------------------------- locales/da.yml | 3 - locales/de.yml | 1 - locales/en.yml | 2 +- locales/es.yml | 1 - locales/fi_FI.yml | 1 - locales/fr.yml | 1 - locales/nl_NL.yml | 1 - locales/pt_BR.yml | 49 --------------- locales/zh_CN.yml | 68 +-------------------- locales/zh_TW.yml | 70 ---------------------- 11 files changed, 3 insertions(+), 343 deletions(-) diff --git a/locales/ar.yml b/locales/ar.yml index 852b9ac1b..2b320a621 100644 --- a/locales/ar.yml +++ b/locales/ar.yml @@ -292,55 +292,7 @@ ar: suspect_word: "كلمة مشتبهة" banned_word: "كلمة محظورة" body_count: "يتجاوز النص الحد الأقصى للطول المسموح" - trust: "ثقة" links: "رابط" - modqueue: - account: "account flags" - actions: Actions - all: all - all_streams: "All Streams" - notify_edited: '{0} edited comment "{1}"' - notify_accepted: '{0} accepted comment "{1}"' - notify_rejected: '{0} rejected comment "{1}"' - notify_flagged: '{0} flagged comment "{1}"' - notify_reset: '{0} reset status of comment "{1}"' - approve: "Approve" - approved: "Approved" - ban_user: "Ban" - billion: B - close: Close - empty_queue: "No more comments to moderate! You're all caught up. Go have some ☕️" - flagged: flagged - reported: reported - less_detail: "Less detail" - likes: likes - million: M - mod_faster: "Moderate faster with keyboard shortcuts" - moderate: "Moderate →" - more_detail: "More detail" - new: New - newest_first: "Newest First" - navigation: Navigation - next_comment: "Go to the next comment" - toggle_search: "Open search" - next_queue: "Switch queues" - oldest_first: "Oldest First" - premod: pre-mod - prev_comment: "Go to the previous comment" - reject: "Reject" - rejected: "Rejected" - reply: "Reply" - select_stream: "Select Stream" - shift_key: "⇧" - shortcuts: "Shortcuts" - sort: "Sort" - show_shortcuts: "Show Shortcuts" - singleview: "Zen mode" - thismenu: "Open this menu" - jump_to_queue: "Jump to specific queue" - thousand: k - try_these: "Try these" - view_more_shortcuts: "View more shortcuts" my_comment_history: "سجل التعليقات" name: اسم no_agree_comment: "لا أوافق على هذا التعليق" @@ -358,9 +310,6 @@ ar: report_notif: "شكرا على الإبلاغ عن هذا التعليق. تم إبلاغ فريق الإشراف لدينا وسيراجعه قريبًا." report_notif_remove: "لقد تمت إزالة بلاغك." reported: بلغ عنه - comment_history_blank: - title: You have not written any comments - info: A history of your comments will appear here settings: from_settings_page: "من صفحة الملف الشخصي يمكنك مشاهدة سجل التعليقات." my_comment_history: "سجل التعليقات" @@ -378,104 +327,8 @@ ar: step_1_header: "بلغ عن مشكلة" step_2_header: "ساعدنا على الفهم" step_3_header: "شكرا لك على المساهمة الخاصة بك" - streams: - all: All - article: Story - closed: Closed - empty_result: "No assets match this search. Maybe try widening your search?" - filter_streams: "Filter Streams" - newest: Newest - oldest: Oldest - open: Open - pubdate: "Publication Date" - search: Search - sort_by: "Sort By" - status: "Stream Status" - stream_status: "Stream Status" - suspenduser: - title_suspend: "Suspend User" - description_suspend: "You are suspending {0}. This comment will go to the Rejected queue, and {0} will not be allowed to like, report, reply or post until the suspension time is complete." - select_duration: "Select suspension duration" - one_hour: "1 hour" - hours: "{0} hours" - days: "{0} days" - hour: "{0} hours" - day: "{0} days" - cancel: "Cancel" - suspend_user: "Suspend User" - email_message_suspend: "Dear {0},\n\nIn accordance with {1}’s community guidelines, your account has been temporarily suspended. During the suspension, you will be unable to comment, flag or engage with fellow commenters. Please rejoin the conversation {2}." - title_notify: "Notify the user of their temporary suspension" - notify_suspend_until: "User {0} has been temporarily suspended. This suspension will automatically end {1}." - description_notify: "Suspending this user will temporarily disable their account." - write_message: "Write a message" - send: Send - reject_username: - username: username - no_cancel: "No cancel" - description_reject: "Would you like to temporarily ban this user because of their {0}? Doing so will temporarily suspend this user until they rewrite their {0}." - title_notify: "Notify the user of their temporary suspension" - description_notify: "Suspending this user will temporarily disable their account." - title_reject: "We noticed you rejected a username" - suspend_user: "Suspend User" - yes_suspend: "Yes suspend" - email_message_reject: "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns." - write_message: "Write a message" - send: Send thank_you: "نحن نقدر سلامتك وردود الفعل. سيراجع المشرف التقرير الخاص بك" - user: - bio_flags: "flags for this bio" - user_bio: "User Bio" - username_flags: "flags for this username" - user_detail: - remove_suspension: "Remove Suspension" - suspend: "Suspend User" - remove_ban: "Remove Ban" - ban: "Ban User" - member_since: "Member Since" - email: "Email" - total_comments: "Total Comments" - reject_rate: "Reject Rate" - reports: "Reports" - all: "All" - rejected: "Rejected" - user_history: "User History" - user_history: - user_banned: "User banned" - ban_removed: "Ban removed" - username_status: "Username {0}" - suspended: "Suspended, {0}" - suspension_removed: "Suspension removed" - system: "System" - date: "Date" - action: "Action" - taken_by: "Taken By" user_impersonating: "هذا المستخدم ينتحل شخصية" user_no_comment: "لم تترك تعليقا مطلقا. إنضم إلى المحادثة!" username_offensive: "اسم المستخدم هذا مسيء" - view_conversation: "عرض المحادثة" - install: - initial: - description: "Let's set up your Talk community in just a few short steps." - submit: "Get Started" - add_organization: - description: "Please tell us the name of your organization. This will appear in emails when inviting new team members." - label: "Organization Name" - save: "Save" - create: - email: "Email address" - username: "Username" - password: "Password" - confirm_password: "Confirm Password" - organization_contact_email: "Organization Contact Email" - save: "Save" - permitted_domains: - title: "Permitted domains" - description: "Enter the domains you would like to permit for Talk, e.g. your local, staging and production environments (ex. localhost:3000, staging.domain.com, domain.com)." - submit: "Finish install" - final: - description: "Thanks for installing Talk! We sent an email to verify your email address. While you finish setting up the account, you can start engaging with your readers now." - launch: "Launch Talk" - close: "Close this Installer" - admin_sidebar: - view_options: "View Options" - sort_comments: "Sort Comments" + view_conversation: "عرض المحادثة" \ No newline at end of file diff --git a/locales/da.yml b/locales/da.yml index 4bcb3bb1d..d21a1aee6 100644 --- a/locales/da.yml +++ b/locales/da.yml @@ -211,7 +211,6 @@ da: NO_SPECIAL_CHARACTERS: "Brugernavne kan kun indeholder bogstaver og _" PASSWORD_LENGTH: "Adgangskoden er for kort" PROFANITY_ERROR: "Brugernavne må ikke inholde stødende indhold. Kontakt venligst administratoren, hvis du mener at dette er en fejl." - RATE_LIMIT_EXCEEDED: "Rate limit exceeded" USERNAME_IN_USE: "Brugernavnet er allerede i brug" USERNAME_REQUIRED: "Du skal indtaste et brugernavn" EMAIL_NOT_VERIFIED: "E-mail address not verified" @@ -287,10 +286,8 @@ da: comment_spam: "Spam" comment_noagree: "Uenig" comment_other: "Andre" - suspect_word: "Suspect Word" banned_word: "Forbudt ord" body_count: "Body overstiger max længde" - trust: "Stol" links: "Link" modqueue: account: "konto flag" diff --git a/locales/de.yml b/locales/de.yml index bec73fa5d..2bf137991 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -322,7 +322,6 @@ de: suspect_word: "Verdächtiges Wort" banned_word: "Unzulässiges Wort" body_count: "Text überschreitet Zeichenlimit" - trust: "Vertrauen" links: "Link" modqueue: account: "Konto-Markierungen" diff --git a/locales/en.yml b/locales/en.yml index 9b38c833f..32399fd9d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -322,7 +322,7 @@ en: suspect_word: "Suspect Word" banned_word: "Banned Word" body_count: "Body exceeds max length" - trust: "Trust" + trust: "Karma" links: "Link" modqueue: account: "account flags" diff --git a/locales/es.yml b/locales/es.yml index ff2777c41..fc9b93b36 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -310,7 +310,6 @@ es: suspect_word: "Palabra sospechosa" banned_word: "Palabra prohibida" body_count: "El texto exede el límite permitido" - trust: "Trust" links: "Link" modqueue: account: "reportes de cuentas" diff --git a/locales/fi_FI.yml b/locales/fi_FI.yml index 494a48cdb..4758a2920 100644 --- a/locales/fi_FI.yml +++ b/locales/fi_FI.yml @@ -292,7 +292,6 @@ fi_FI: suspect_word: "Epäilyttävä sana" banned_word: "Kielletty sana" body_count: "Liian pitkä viesti" - trust: "Luotettava" links: "Linkki" modqueue: account: "Liputuksia" diff --git a/locales/fr.yml b/locales/fr.yml index 1e9e0e496..4e32cf66c 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -300,7 +300,6 @@ fr: suspect_word: "Mot suspect" banned_word: "Mot banni" body_count: "Le texte dépasse la longueur maximale" - trust: "Trust" links: "Lien" modqueue: account: "Signalements du compte" diff --git a/locales/nl_NL.yml b/locales/nl_NL.yml index 9d4057065..297917c9e 100644 --- a/locales/nl_NL.yml +++ b/locales/nl_NL.yml @@ -290,7 +290,6 @@ nl_NL: suspect_word: "Verdacht woord" banned_word: "Geblokeerd woord" body_count: "Tekst is te lang" - trust: "Vertrouwen" links: "Link" modqueue: account: "account meldingen" diff --git a/locales/pt_BR.yml b/locales/pt_BR.yml index 4b7b5c3e3..c464480e0 100644 --- a/locales/pt_BR.yml +++ b/locales/pt_BR.yml @@ -61,11 +61,6 @@ pt_BR: reaction: 'Reação' reactions: 'Reações' story: 'Conversas' - flagged_usernames: - notify_approved: '{0} approved username {1}' - notify_rejected: '{0} rejected username {1}' - notify_flagged: '{0} reported username {1}' - notify_changed: 'user {0} changed their username to {1}' community: account_creation_date: "Data de criação da conta" active: Ativo @@ -273,24 +268,6 @@ pt_BR: loading_results: "Carregando resultados" marketing: "Isso parece um anúncio/marketing" moderate_this_stream: "Moderar comentários" - flags: - reasons: - user: - username_offensive: "Offensive" - username_nolike: "Dislike" - username_impersonating: "Impersonation" - username_spam: "Spam" - username_other: "Other" - comment: - comment_offensive: "Offensive" - comment_spam: "Spam" - comment_noagree: "Disagree" - comment_other: "Other" - suspect_word: "Suspect Word" - banned_word: "Banned Word" - body_count: "Body exceeds max length" - trust: "Trust" - links: "Link" modqueue: account: "contas marcadas" actions: Ações @@ -418,29 +395,6 @@ pt_BR: bio_flags: "Marcadas para este perfil" user_bio: "Perfil do usuário" username_flags: "Marcadas para este usuário" - user_detail: - remove_suspension: "Remove Suspension" - suspend: "Suspend User" - remove_ban: "Remove Ban" - ban: "Ban User" - member_since: "Member Since" - email: "Email" - total_comments: "Total Comments" - reject_rate: "Reject Rate" - reports: "Reports" - all: "All" - rejected: "Rejected" - user_history: "User History" - user_history: - user_banned: "User banned" - ban_removed: "Ban removed" - username_status: "Username {0}" - suspended: "Suspended, {0}" - suspension_removed: "Suspension removed" - system: "System" - date: "Date" - action: "Action" - taken_by: "Taken By" user_impersonating: "Este usuário está representando" user_no_comment: "Você nunca deixou um comentário. Participe da conversa!" username_offensive: "Esse nome de usuário é ofensivo" @@ -468,6 +422,3 @@ pt_BR: description: "Obrigado por instalar o Talk! Enviamos um e-mail para verificar seu endereço de e-mail. Enquanto você terminar de configurar a conta, você pode começar a se envolver com seus leitores agora." launch: "Iniciar Talk" close: "Feche este instalador" - admin_sidebar: - view_options: "View Options" - sort_comments: "Sort Comments" diff --git a/locales/zh_CN.yml b/locales/zh_CN.yml index b3307a886..c70f5b779 100644 --- a/locales/zh_CN.yml +++ b/locales/zh_CN.yml @@ -12,22 +12,11 @@ zh_CN: note_reject_comment: "封禁该用户将使这条评论被移入“被拒”队列。" note_ban_user: "封禁该用户将使其无法编辑或删除评论。" yes_ban_user: "是的,封禁该用户" - write_a_message: "Write a message" - send: "Send" - notify_ban_headline: "Notify the user of ban" - notify_ban_description: "This will notify the user by email that they have been banned from the community" - email_message_ban: "Dear {0},\n\nSomeone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, like or report comments. if you think this has been done in error, please contact our community team." bio_offensive: "该简介含有冒犯言语" cancel: "取消" confirm_email: click_to_confirm: "Click below to confirm your email address" confirm: "Confirm" - password_reset: - set_new_password: "Change Your Password" - new_password: "New Password" - new_password_help: "Password must be at least 8 characters" - confirm_new_password: "Confirm New Password" - change_password: "Change Password" characters_remaining: "字符剩余可用" comment: anon: "匿名" @@ -61,11 +50,6 @@ zh_CN: reaction: '回应' reactions: '回应' story: '文章' - flagged_usernames: - notify_approved: '{0} approved username {1}' - notify_rejected: '{0} rejected username {1}' - notify_flagged: '{0} reported username {1}' - notify_changed: 'user {0} changed their username to {1}' community: account_creation_date: "账户创建日期" active: "活动中" @@ -203,9 +187,6 @@ zh_CN: embedlink: copy: "复制到粘贴板" error: - COMMENT_PARENT_NOT_VISIBLE: "The comment that you're replying to has been removed or doesn't exist." - EMAIL_VERIFICATION_TOKEN_INVALID: "Email verification token is invalid." - PASSWORD_RESET_TOKEN_INVALID: "Your password reset link is invalid." COMMENT_TOO_SHORT: "评论至少应有一个字符。请修改您的评论,再度尝试。" NOT_AUTHORIZED: "您没有权限进行该操作" NO_SPECIAL_CHARACTERS: "用户名只能包含字母、数字跟下划线" @@ -237,7 +218,6 @@ zh_CN: username: "用户名只能包含字母、数字跟下划线" unexpected: "发生了异常错误。对不起!" required_field: "该字段必填" - temporarily_suspended: "Your account is currently suspended. It will be reactivated {0}. Please contact us if you have any questions." flag_comment: "举报评论" flag_reason: "举报理由(可选)" flag_username: "举报用户名" @@ -256,8 +236,6 @@ zh_CN: error: "用户名只能包含字母、数字跟下划线" label: "新用户名" msg: "由于您的用户名不当,您的帐号目前被暂停使用。如要恢复您的帐户,请输入一个新的用户名。如有任何疑问,请与我们联系。" - changed_name: - msg: "Your username change is under review by our moderation team." my_comments: "我的评论" my_profile: "我的资料" new_count: "查看 {0} 更多 {1}" @@ -275,24 +253,6 @@ zh_CN: loading_results: "加载结果中" marketing: "这看起来像是广告" moderate_this_stream: "审查该流" - flags: - reasons: - user: - username_offensive: "Offensive" - username_nolike: "Dislike" - username_impersonating: "Impersonation" - username_spam: "Spam" - username_other: "Other" - comment: - comment_offensive: "Offensive" - comment_spam: "Spam" - comment_noagree: "Disagree" - comment_other: "Other" - suspect_word: "Suspect Word" - banned_word: "Banned Word" - body_count: "Body exceeds max length" - trust: "Trust" - links: "Link" modqueue: account: "帐户标记" actions: "操作" @@ -420,29 +380,6 @@ zh_CN: bio_flags: "对简介的举报" user_bio: "用户简介" username_flags: "对用户名的举报" - user_detail: - remove_suspension: "Remove Suspension" - suspend: "Suspend User" - remove_ban: "Remove Ban" - ban: "Ban User" - member_since: "Member Since" - email: "Email" - total_comments: "Total Comments" - reject_rate: "Reject Rate" - reports: "Reports" - all: "All" - rejected: "Rejected" - user_history: "User History" - user_history: - user_banned: "User banned" - ban_removed: "Ban removed" - username_status: "Username {0}" - suspended: "Suspended, {0}" - suspension_removed: "Suspension removed" - system: "System" - date: "Date" - action: "Action" - taken_by: "Taken By" user_impersonating: "冒名用户" user_no_comment: "您未曾发表评论。现在就来加入对话吧!" username_offensive: "用户名有冒犯性" @@ -468,7 +405,4 @@ zh_CN: final: description: "感谢您安装 Talk!我们已向您的邮箱发送一封验证邮件。当您进行帐号设置时,您可以开始跟您的读者开始互动。" launch: "启动 Talk" - close: "关闭安装程序" - admin_sidebar: - view_options: "View Options" - sort_comments: "Sort Comments" + close: "关闭安装程序" \ No newline at end of file diff --git a/locales/zh_TW.yml b/locales/zh_TW.yml index c186a667f..2b5db5d44 100644 --- a/locales/zh_TW.yml +++ b/locales/zh_TW.yml @@ -12,22 +12,8 @@ zh_TW: note_reject_comment: "封禁該用戶將使這條評論被移入“被拒”列表。" note_ban_user: "封禁該用戶將使其無法編輯或刪除評論。" yes_ban_user: "是的,封禁該用戶" - write_a_message: "Write a message" - send: "Send" - notify_ban_headline: "Notify the user of ban" - notify_ban_description: "This will notify the user by email that they have been banned from the community" - email_message_ban: "Dear {0},\n\nSomeone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, like or report comments. if you think this has been done in error, please contact our community team." bio_offensive: "該介紹包含具有攻擊性的內容。" cancel: "取消" - confirm_email: - click_to_confirm: "Click below to confirm your email address" - confirm: "Confirm" - password_reset: - set_new_password: "Change Your Password" - new_password: "New Password" - new_password_help: "Password must be at least 8 characters" - confirm_new_password: "Confirm New Password" - change_password: "Change Password" characters_remaining: "剩餘字符數" comment: anon: "匿名用戶" @@ -61,11 +47,6 @@ zh_TW: reaction: '回應' reactions: '回應' story: '故事' - flagged_usernames: - notify_approved: '{0} approved username {1}' - notify_rejected: '{0} rejected username {1}' - notify_flagged: '{0} reported username {1}' - notify_changed: 'user {0} changed their username to {1}' community: account_creation_date: "賬戶創建日期" active: 激活 @@ -203,9 +184,6 @@ zh_TW: embedlink: copy: "覆制到剪貼板" error: - COMMENT_PARENT_NOT_VISIBLE: "The comment that you're replying to has been removed or doesn't exist." - EMAIL_VERIFICATION_TOKEN_INVALID: "Email verification token is invalid." - PASSWORD_RESET_TOKEN_INVALID: "Your password reset link is invalid." COMMENT_TOO_SHORT: "評論長度必須超過一個字符,請您修改評論後重試。" NOT_AUTHORIZED: "您無權執行該操作。" NO_SPECIAL_CHARACTERS: "用戶名只能包含字母、數字和下劃線" @@ -237,7 +215,6 @@ zh_TW: username: "用戶名只能包含字母、數字和下劃線。" unexpected: "發生了意外錯誤。抱歉!" required_field: "該字段必填" - temporarily_suspended: "Your account is currently suspended. It will be reactivated {0}. Please contact us if you have any questions." flag_comment: "舉報評論" flag_reason: "舉報原因(可選)" flag_username: "舉報用戶名" @@ -256,8 +233,6 @@ zh_TW: error: "用戶名只能包含字母、數字和下劃線。" label: "新用戶名" msg: "由於您的用戶名不當,您的帳號目前已被暫停使用。如要恢復您的帳戶,請輸入一個新的用戶名。如有任何疑問,請與我們聯繫。" - changed_name: - msg: "Your username change is under review by our moderation team." my_comments: "我的評論" my_profile: "我的概況" new_count: "查看{0}更多{1}" @@ -275,24 +250,6 @@ zh_TW: loading_results: "加載結果" marketing: "這看起來像是廣告/營銷" moderate_this_stream: "審核這個流" - flags: - reasons: - user: - username_offensive: "Offensive" - username_nolike: "Dislike" - username_impersonating: "Impersonation" - username_spam: "Spam" - username_other: "Other" - comment: - comment_offensive: "Offensive" - comment_spam: "Spam" - comment_noagree: "Disagree" - comment_other: "Other" - suspect_word: "Suspect Word" - banned_word: "Banned Word" - body_count: "Body exceeds max length" - trust: "Trust" - links: "Link" modqueue: account: "帳戶標記" actions: 操作 @@ -345,7 +302,6 @@ zh_TW: no_agree_comment: "我不同意這個評論" no_like_bio: "我不喜歡這個個人簡介" no_like_username: "我不喜歡這個用戶名" - already_flagged_username: "You have already flagged this username." other: 其他 permalink: 分享 personal_info: "該評論洩露了個人身份資訊" @@ -420,29 +376,6 @@ zh_TW: bio_flags: "該簡介的標記" user_bio: "用戶簡介" username_flags: "該用戶名的標記" - user_detail: - remove_suspension: "Remove Suspension" - suspend: "Suspend User" - remove_ban: "Remove Ban" - ban: "Ban User" - member_since: "Member Since" - email: "Email" - total_comments: "Total Comments" - reject_rate: "Reject Rate" - reports: "Reports" - all: "All" - rejected: "Rejected" - user_history: "User History" - user_history: - user_banned: "User banned" - ban_removed: "Ban removed" - username_status: "Username {0}" - suspended: "Suspended, {0}" - suspension_removed: "Suspension removed" - system: "System" - date: "Date" - action: "Action" - taken_by: "Taken By" user_impersonating: "此用戶正在冒充" user_no_comment: "您尚未評論過。加入對話吧!" username_offensive: "這個用戶名有冒犯性" @@ -469,6 +402,3 @@ zh_TW: description: "感謝安裝Talk!我們給您發送了一封郵件以驗證您的電子郵箱地址。在完成帳戶設置後,您即可開始與您的讀者互動。" launch: "啟動Talk" close: "關閉安裝程序" - admin_sidebar: - view_options: "View Options" - sort_comments: "Sort Comments" From 0835f3f32163cbdf0ee3766f2e93d38ec9cadbb6 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 23 May 2018 17:16:51 -0600 Subject: [PATCH 117/162] fixed translations --- client/coral-admin/src/components/CommentLabels.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/src/components/CommentLabels.js b/client/coral-admin/src/components/CommentLabels.js index 1f71d9771..5c7da08c7 100644 --- a/client/coral-admin/src/components/CommentLabels.js +++ b/client/coral-admin/src/components/CommentLabels.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Label from 'coral-ui/components/Label'; import Slot from 'coral-framework/components/Slot'; +import { t } from 'coral-framework/services/i18n'; import FlagLabel from 'coral-ui/components/FlagLabel'; import cn from 'classnames'; import styles from './CommentLabels.css'; @@ -63,10 +64,14 @@ const CommentLabels = ({ {getUserFlaggedType(actions)} )} {hasSuspectedWords(actions) && ( - Suspect + + {t('flags.reasons.comment.suspect_word')} + )} {hasHistoryFlag(actions) && ( - History + + {t('flags.reasons.comment.trust')} + )}
    Date: Thu, 24 May 2018 10:09:44 -0600 Subject: [PATCH 118/162] fixed first time user --- services/moderation/phases/karma.js | 42 ++++++++++++++--------------- test/server/services/karma.js | 20 +++++++------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/services/moderation/phases/karma.js b/services/moderation/phases/karma.js index ad55b37a7..7a929824b 100644 --- a/services/moderation/phases/karma.js +++ b/services/moderation/phases/karma.js @@ -6,28 +6,26 @@ module.exports = ctx => { const { connectors: { services: { Karma } } } = ctx; const trust = get(ctx, 'user.metadata.trust', null); - if (trust !== null) { - // If the user is not a reliable commenter (passed the unreliability - // threshold by having too many rejected comments) then we can change the - // status of the comment to `SYSTEM_WITHHELD`, therefore pushing the user's - // comments away from the public eye until a moderator can manage them. This of - // course can only be applied if the comment's current status is `NONE`, - // we don't want to interfere if the comment was rejected. - if (Karma.isReliable('comment', trust) === false) { - // Add the flag related to Trust to the comment. - return { - status: 'SYSTEM_WITHHELD', - actions: [ - { - action_type: 'FLAG', - user_id: null, - group_id: 'TRUST', - metadata: { - trust, - }, + // If the user is not a reliable commenter (passed the unreliability + // threshold by having too many rejected comments) then we can change the + // status of the comment to `SYSTEM_WITHHELD`, therefore pushing the user's + // comments away from the public eye until a moderator can manage them. This of + // course can only be applied if the comment's current status is `NONE`, + // we don't want to interfere if the comment was rejected. + if (Karma.isReliable('comment', trust) === false) { + // Add the flag related to Trust to the comment. + return { + status: 'SYSTEM_WITHHELD', + actions: [ + { + action_type: 'FLAG', + user_id: null, + group_id: 'TRUST', + metadata: { + trust, }, - ], - }; - } + }, + ], + }; } }; diff --git a/test/server/services/karma.js b/test/server/services/karma.js index d725d8308..4a6753125 100644 --- a/test/server/services/karma.js +++ b/test/server/services/karma.js @@ -6,8 +6,8 @@ const Karma = require('../../../services/karma'); const thresholdsBackup = {}; const thresholdsOverride = { comment: { - RELIABLE: 1, - UNRELIABLE: -1, + RELIABLE: 2, + UNRELIABLE: 0, }, flag: { RELIABLE: 1, @@ -35,19 +35,21 @@ describe('services.Karma', () => { describe('#isReliable', () => { it('neutral', () => { - expect(Karma.isReliable('comment', {})).to.be.null; - expect(Karma.isReliable('comment', { comment: {} })).to.be.null; - expect(Karma.isReliable('comment', { comment: { karma: 0 } })).to.be.null; + expect(Karma.isReliable('comment', { comment: { karma: 1 } })).to.be.null; + expect(Karma.isReliable('comment', { comment: { karma: 0 } })).to.not.be + .null; + expect(Karma.isReliable('comment', { comment: { karma: -1 } })).to.not.be + .null; }); it('unreliable', () => { - expect(Karma.isReliable('comment', { comment: { karma: -1 } })).to.be - .false; - expect(Karma.isReliable('comment', { comment: { karma: -2 } })).to.be + expect(Karma.isReliable('comment', {})).to.be.false; + expect(Karma.isReliable('comment', { comment: {} })).to.be.false; + expect(Karma.isReliable('comment', { comment: { karma: 0 } })).to.be .false; }); it('reliable', () => { - expect(Karma.isReliable('comment', { comment: { karma: 1 } })).to.be.true; expect(Karma.isReliable('comment', { comment: { karma: 2 } })).to.be.true; + expect(Karma.isReliable('comment', { comment: { karma: 3 } })).to.be.true; }); }); }); From e1298e4fd96bae86af23194c6fba50ba3dca4ca4 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 24 May 2018 19:00:07 +0200 Subject: [PATCH 119/162] Fix Touch issues on IOS Safari (iPad) --- views/embed/stream.njk | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/views/embed/stream.njk b/views/embed/stream.njk index ddc620f8e..d6c53b210 100644 --- a/views/embed/stream.njk +++ b/views/embed/stream.njk @@ -9,6 +9,13 @@ {% block html %}
    + +{# + Resolves touch handling issues encountered on IOS Safari under certain circumstances + https://www.pivotaltracker.com/n/projects/1863625 +#} + + {% endblock %} {% block js %} From 945ab9a1b66d294325e6fcaf6c8adaf6ba20d0b8 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 12:32:06 -0600 Subject: [PATCH 120/162] Added support for forcing languages --- Dockerfile.onbuild | 3 ++- client/coral-framework/services/i18n.js | 27 +++++++++++++++++---- config.js | 18 ++++++++++++++ docs/source/02-02-advanced-configuration.md | 15 ++++++++++++ services/i18n.js | 14 +++++++---- webpack.config.js | 1 + 6 files changed, 67 insertions(+), 11 deletions(-) diff --git a/Dockerfile.onbuild b/Dockerfile.onbuild index 3e837aad6..85becf461 100644 --- a/Dockerfile.onbuild +++ b/Dockerfile.onbuild @@ -7,6 +7,7 @@ ONBUILD ARG TALK_REPLY_COMMENTS_LOAD_DEPTH=3 ONBUILD ARG TALK_THREADING_LEVEL=3 ONBUILD ARG TALK_DEFAULT_STREAM_TAB=all ONBUILD ARG TALK_DEFAULT_LANG=en +ONBUILD ARG TALK_WHITELISTED_LANGUAGES ONBUILD ARG TALK_PLUGINS_JSON ONBUILD ARG TALK_WEBPACK_SOURCE_MAP @@ -20,4 +21,4 @@ ONBUILD COPY . /usr/src/app ONBUILD RUN cli plugins reconcile && \ yarn && \ yarn build && \ - yarn cache clean \ No newline at end of file + yarn cache clean diff --git a/client/coral-framework/services/i18n.js b/client/coral-framework/services/i18n.js index 02258a585..42a0b2030 100644 --- a/client/coral-framework/services/i18n.js +++ b/client/coral-framework/services/i18n.js @@ -39,7 +39,20 @@ import pt_BR from '../../../locales/pt_BR.yml'; import zh_CN from '../../../locales/zh_CN.yml'; import zh_TW from '../../../locales/zh_TW.yml'; -export const defaultLocale = process.env.TALK_DEFAULT_LANG.replace(/-/g, '_'); +// the list of languages that are whitelisted. If false, all languages that are +// supported by Talk will be enabled. +const whitelistedLanguages = + process.env.TALK_WHITELISTED_LANGUAGES && + process.env.TALK_WHITELISTED_LANGUAGES.split(',').map(l => l.trim()); + +// The default language. If the whitelisted languages is specified and the +// default language is not in that list, then the first language in the +// whitelisted list will be used as the default. +export const defaultLocale = whitelistedLanguages + ? !whitelistedLanguages.includes(process.env.TALK_DEFAULT_LANG) + ? whitelistedLanguages[0] + : process.env.TALK_DEFAULT_LANG + : process.env.TALK_DEFAULT_LANG; export const translations = { ...ar, @@ -64,10 +77,14 @@ let TIMEAGO_INSTANCE; // to the default language. const detectLanguage = () => first( - negotiateLanguages(navigator.languages, supportedLocales, { - defaultLocale, - strategy: 'lookup', - }) + negotiateLanguages( + navigator.languages, + whitelistedLanguages || supportedLocales, + { + defaultLocale, + strategy: 'lookup', + } + ) ); export function setupTranslations() { diff --git a/config.js b/config.js index 77ed4ab2b..4ce755f93 100644 --- a/config.js +++ b/config.js @@ -36,6 +36,13 @@ const CONFIG = { // rendered text. DEFAULT_LANG: process.env.TALK_DEFAULT_LANG || 'en', + // WHITELISTED_LANGUAGES is a comma separated list of language/locales that + // should be supported. If the default language is not included in the + // whitelist list, the first entry will be used as the default. + WHITELISTED_LANGUAGES: + process.env.TALK_WHITELISTED_LANGUAGES && + process.env.TALK_WHITELISTED_LANGUAGES.split(',').map(l => l.trim()), + // When TRUE, it ensures that database indexes created in core will not add // indexes. CREATE_MONGO_INDEXES: process.env.DISABLE_CREATE_MONGO_INDEXES !== 'TRUE', @@ -317,6 +324,17 @@ CONFIG.JWT_COOKIE_NAMES = uniq( ]) ); +//------------------------------------------------------------------------------ +// Locale validation +//------------------------------------------------------------------------------ + +if ( + CONFIG.WHITELISTED_LANGUAGES && + !CONFIG.WHITELISTED_LANGUAGES.includes(CONFIG.DEFAULT_LANG) +) { + CONFIG.DEFAULT_LANG = CONFIG.WHITELISTED_LANGUAGES[0]; +} + //------------------------------------------------------------------------------ // External database url's //------------------------------------------------------------------------------ diff --git a/docs/source/02-02-advanced-configuration.md b/docs/source/02-02-advanced-configuration.md index c4f86dbb8..9d351ed2b 100644 --- a/docs/source/02-02-advanced-configuration.md +++ b/docs/source/02-02-advanced-configuration.md @@ -31,6 +31,21 @@ image you can specify it with `--build-arg TALK_DEFAULT_LANG=en`. Specify the default translation language. (Default `en`) +## TALK_WHITELIST_LANGUAGES + +This is a **Build Variable** and must be consumed during build. If using the +[Docker-onbuild](/talk/installation-from-docker/#onbuild) +image you can specify it with `--build-arg TALK_WHITELIST_LANGUAGES=en`. + +Specify the comma separated whitelisted languages that you want the Talk +application to serve. This will override the available set of languages that +Talk will allow to be served. + +If the [TALK_DEFAULT_LANG](#talk-default-lang) is not included in this list of +whitelisted languages, then the first whitelisted language will become the +default language. If this parameter is empty, then all languages supported by +Talk will be whitelisted. (Default ``) + ## TALK_DEFAULT_STREAM_TAB This is a **Build Variable** and must be consumed during build. If using the diff --git a/services/i18n.js b/services/i18n.js index 52c34a4ed..0a99f1aa0 100644 --- a/services/i18n.js +++ b/services/i18n.js @@ -8,7 +8,7 @@ const { const { first, get, has, merge, isUndefined } = require('lodash'); const yaml = require('yamljs'); const plugins = require('./plugins'); -const { DEFAULT_LANG } = require('../config'); +const { DEFAULT_LANG, WHITELISTED_LANGUAGES } = require('../config'); const resolve = (...paths) => path.resolve(path.join(__dirname, '..', 'locales', ...paths)); @@ -105,10 +105,14 @@ const i18n = { // negotiate the language. const lang = first( - negotiateLanguages(acceptsLanguages, supportedLocales, { - defaultLocale: DEFAULT_LANG, - strategy: 'lookup', - }) + negotiateLanguages( + acceptsLanguages, + WHITELISTED_LANGUAGES || supportedLocales, + { + defaultLocale: DEFAULT_LANG, + strategy: 'lookup', + } + ) ); debug(`decided language as '${lang}'`); diff --git a/webpack.config.js b/webpack.config.js index c6b3a770f..6914499bf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -169,6 +169,7 @@ const config = { TALK_REPLY_COMMENTS_LOAD_DEPTH: '3', TALK_DEFAULT_STREAM_TAB: 'all', TALK_DEFAULT_LANG: 'en', + TALK_WHITELISTED_LANGUAGES: '', }), new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), From c642cc811c625807666f39a6845c603d2bc128c3 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 12:34:58 -0600 Subject: [PATCH 121/162] fix --- docs/source/02-02-advanced-configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/02-02-advanced-configuration.md b/docs/source/02-02-advanced-configuration.md index 9d351ed2b..da7112706 100644 --- a/docs/source/02-02-advanced-configuration.md +++ b/docs/source/02-02-advanced-configuration.md @@ -31,11 +31,11 @@ image you can specify it with `--build-arg TALK_DEFAULT_LANG=en`. Specify the default translation language. (Default `en`) -## TALK_WHITELIST_LANGUAGES +## TALK_WHITELISTED_LANGUAGES This is a **Build Variable** and must be consumed during build. If using the [Docker-onbuild](/talk/installation-from-docker/#onbuild) -image you can specify it with `--build-arg TALK_WHITELIST_LANGUAGES=en`. +image you can specify it with `--build-arg TALK_WHITELISTED_LANGUAGES=en`. Specify the comma separated whitelisted languages that you want the Talk application to serve. This will override the available set of languages that From 8f4846404a932e668808f6fdb13806ef4e7e8af6 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 12:35:45 -0600 Subject: [PATCH 122/162] fixed default doc --- docs/source/02-02-advanced-configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/02-02-advanced-configuration.md b/docs/source/02-02-advanced-configuration.md index da7112706..13ad2c35a 100644 --- a/docs/source/02-02-advanced-configuration.md +++ b/docs/source/02-02-advanced-configuration.md @@ -44,7 +44,7 @@ Talk will allow to be served. If the [TALK_DEFAULT_LANG](#talk-default-lang) is not included in this list of whitelisted languages, then the first whitelisted language will become the default language. If this parameter is empty, then all languages supported by -Talk will be whitelisted. (Default ``) +Talk will be whitelisted. (Default '') ## TALK_DEFAULT_STREAM_TAB From 0c35badd951a71f3c4f85bd19b3184c4403def5a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 12:58:55 -0600 Subject: [PATCH 123/162] name changes --- client/coral-admin/src/components/KarmaTooltip.js | 4 ++-- client/coral-admin/src/components/UserDetail.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/components/KarmaTooltip.js b/client/coral-admin/src/components/KarmaTooltip.js index 772b119e9..0b0866ef1 100644 --- a/client/coral-admin/src/components/KarmaTooltip.js +++ b/client/coral-admin/src/components/KarmaTooltip.js @@ -10,7 +10,7 @@ const initialState = { menuVisible: false }; class KarmaTooltip extends React.Component { static propTypes = { - settings: PropTypes.shape({ + thresholds: PropTypes.shape({ reliable: PropTypes.number.isRequired, unreliable: PropTypes.number.isRequired, }).isRequired, @@ -27,7 +27,7 @@ class KarmaTooltip extends React.Component { }; render() { - const { settings: { unreliable } } = this.props; + const { thresholds: { unreliable } } = this.props; const { menuVisible } = this.state; return ( diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 1b3fe4333..a221e8b86 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -240,7 +240,7 @@ class UserDetail extends React.Component { {user.reliable.commenterKarma}
    - + From a046162f9c20d433f43cbee6dcadf39b17ae39d9 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 13:57:15 -0600 Subject: [PATCH 124/162] `karma` -> `karmaThresholds` --- client/coral-admin/src/components/UserDetail.js | 10 ++++++++-- client/coral-admin/src/containers/UserDetail.js | 2 +- client/coral-admin/src/graphql/index.js | 4 ++-- graph/resolvers/settings.js | 4 ++-- graph/typeDefs.graphql | 5 +++-- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index a221e8b86..fd609373d 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -76,7 +76,13 @@ class UserDetail extends React.Component { renderLoaded() { const { root, - root: { me, user, totalComments, rejectedComments, settings: { karma } }, + root: { + me, + user, + totalComments, + rejectedComments, + settings: { karmaThresholds }, + }, activeTab, selectedCommentIds, toggleSelect, @@ -240,7 +246,7 @@ class UserDetail extends React.Component { {user.reliable.commenterKarma} - + diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index f12b26b30..e34d4df6e 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -228,7 +228,7 @@ export const withUserDetailQuery = withQuery( ${getSlotFragmentSpreads(slots, 'user')} } settings { - karma { + karmaThresholds { comment { reliable unreliable diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index d1bbf501b..6a1b06a6e 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -323,7 +323,7 @@ export default { commenter: { $set: calculateReliability( prev.user.reliable.commenterKarma - 1, - prev.settings.karma.comment + prev.settings.karmaThresholds.comment ), }, commenterKarma: { @@ -342,7 +342,7 @@ export default { commenter: { $set: calculateReliability( prev.user.reliable.commenterKarma + 1, - prev.settings.karma.comment + prev.settings.karmaThresholds.comment ), }, commenterKarma: { diff --git a/graph/resolvers/settings.js b/graph/resolvers/settings.js index 11e21068b..a299530f2 100644 --- a/graph/resolvers/settings.js +++ b/graph/resolvers/settings.js @@ -2,7 +2,7 @@ const { VIEW_PROTECTED_SETTINGS } = require('../../perms/constants'); const { decorateWithPermissionCheck } = require('./util'); const Settings = { - karma: ( + karmaThresholds: ( settings, args, { connectors: { services: { Karma: { THRESHOLDS } } } } @@ -16,7 +16,7 @@ const PROTECTED_SETTINGS = { autoCloseStream: [VIEW_PROTECTED_SETTINGS], wordlist: [VIEW_PROTECTED_SETTINGS], domains: [VIEW_PROTECTED_SETTINGS], - karma: [VIEW_PROTECTED_SETTINGS], + karmaThresholds: [VIEW_PROTECTED_SETTINGS], }; // decorate the fields on the settings resolver with a permission check. diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 5bc102736..1ca003de6 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -897,8 +897,9 @@ type Settings { # domains will return a given list of domains. domains: Domains - # karma contains the currently set thresholds for triggering Trust beheviour. - karma: KarmaThresholds + # karmaThresholds contains the currently set thresholds for triggering Trust + # beheviour. + karmaThresholds: KarmaThresholds } ################################################################################ From e39bcb11ba7cd74aa7b70f6897ea1fdf32a0a5bb Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 14:02:02 -0600 Subject: [PATCH 125/162] added comment --- client/coral-admin/src/components/KarmaTooltip.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-admin/src/components/KarmaTooltip.js b/client/coral-admin/src/components/KarmaTooltip.js index 0b0866ef1..9c4184afd 100644 --- a/client/coral-admin/src/components/KarmaTooltip.js +++ b/client/coral-admin/src/components/KarmaTooltip.js @@ -44,6 +44,7 @@ class KarmaTooltip extends React.Component {
    {t('user_detail.user_karma_score')}
      + {/* NOTE: we may display this data in the future, keeping around for that eventuality */} {/*
    • Reliable{' '} From f8cc658c54e8291d49153b88d58af9b1ef085a1a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 14:34:13 -0600 Subject: [PATCH 126/162] csp fixes --- routes/api/v1/csp.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/routes/api/v1/csp.js b/routes/api/v1/csp.js index ad1101987..5d7005b4e 100644 --- a/routes/api/v1/csp.js +++ b/routes/api/v1/csp.js @@ -4,13 +4,18 @@ const { logger } = require('../../../services/logging'); const router = express.Router(); const schema = Joi.object().keys({ - 'csp-report': Joi.object().keys({ - 'document-uri': Joi.string(), - referrer: Joi.string(), - 'blocked-uri': Joi.string(), - 'violated-directive': Joi.string(), - 'original-policy': Joi.string(), - }), + 'csp-report': Joi.object() + .keys({ + 'document-uri': Joi.string(), + referrer: Joi.string().allow(''), + 'blocked-uri': Joi.string(), + 'violated-directive': Joi.string(), + 'original-policy': Joi.string(), + 'script-sample': Joi.string() + .allow('') + .optional(), + }) + .optionalKeys('referrer', 'script-sample'), }); const json = express.json({ type: 'application/csp-report' }); @@ -18,7 +23,7 @@ const json = express.json({ type: 'application/csp-report' }); router.post('/', json, async (req, res, next) => { const { value, error: err } = Joi.validate(req.body, schema, { stripUnknown: true, - presence: 'required', + presence: 'optional', }); if (err) { res.status(400).end(); From 64c16a18050861815f690075504da361cea8658b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 15:02:08 -0600 Subject: [PATCH 127/162] support the __webpack_nonce__ parameter --- .../helpers/{publicPath.js => webpackGlobals.js} | 14 ++++++++++++-- middleware/nonce.js | 9 ++++++++- middleware/staticTemplate.js | 2 +- webpack.config.js | 7 +++++-- 4 files changed, 26 insertions(+), 6 deletions(-) rename client/coral-framework/helpers/{publicPath.js => webpackGlobals.js} (57%) diff --git a/client/coral-framework/helpers/publicPath.js b/client/coral-framework/helpers/webpackGlobals.js similarity index 57% rename from client/coral-framework/helpers/publicPath.js rename to client/coral-framework/helpers/webpackGlobals.js index 57b6b7d15..99569529b 100644 --- a/client/coral-framework/helpers/publicPath.js +++ b/client/coral-framework/helpers/webpackGlobals.js @@ -1,9 +1,9 @@ -/* global __webpack_public_path__ */ // eslint-disable-line no-unused-vars +/* global __webpack_public_path__, __webpack_nonce__ */ // eslint-disable-line no-unused-vars import { getStaticConfiguration } from 'coral-framework/services/staticConfiguration'; // Load the static url from the static configuration. -const { STATIC_URL } = getStaticConfiguration(); +const { STATIC_URL, SCRIPT_NONCE } = getStaticConfiguration(); // Update the static url for the imported public path so dynamically imported // chunks will use the correct path as defined by the process.env.STATIC_URL @@ -14,3 +14,13 @@ const { STATIC_URL } = getStaticConfiguration(); // https://webpack.js.org/configuration/output/#output-publicpath // __webpack_public_path__ = STATIC_URL + 'static/'; + +// All dynamically included scripts that support nonce's will add this to their +// script tags. +// +// The __webpack_nonce__ can be referenced: https://webpack.js.org/guides/csp/ +// +// Pending issues: +// - https://github.com/webpack-contrib/style-loader/pull/319 +// +__webpack_nonce__ = SCRIPT_NONCE; diff --git a/middleware/nonce.js b/middleware/nonce.js index 1181fac85..7d072cf37 100644 --- a/middleware/nonce.js +++ b/middleware/nonce.js @@ -1,9 +1,16 @@ +const { get, merge } = require('lodash'); const uuid = require('uuid/v4'); // nonce is designed to create a random value that can be used in conjunction // with the csp middleware. module.exports = (req, res, next) => { - res.locals.nonce = uuid(); + const nonce = uuid(); + + // Attach the nonce to the locals. + res.locals.nonce = nonce; + res.locals.data = merge({}, get(res.locals, 'data', {}), { + SCRIPT_NONCE: nonce, + }); next(); }; diff --git a/middleware/staticTemplate.js b/middleware/staticTemplate.js index d8137693e..8d467fcda 100644 --- a/middleware/staticTemplate.js +++ b/middleware/staticTemplate.js @@ -48,7 +48,7 @@ const attachStaticLocals = locals => { for (const key in TEMPLATE_LOCALS) { const value = TEMPLATE_LOCALS[key]; - locals[key] = value; + merge(locals, { [key]: value }); } }; diff --git a/webpack.config.js b/webpack.config.js index 6914499bf..96c09dd9e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -74,7 +74,7 @@ const config = { target: 'web', output: { path: path.join(__dirname, 'dist'), - publicPath: '', + webpackGlobals: '', filename: '[name].[chunkhash].js', chunkFilename: '[name].[chunkhash].chunk.js', }, @@ -309,7 +309,10 @@ const applyConfig = (entries, root = {}) => entry: entries.reduce( (entry, { name, path: modulePath, disablePolyfill = false }) => { const entries = [ - path.join(__dirname, 'client/coral-framework/helpers/publicPath'), + path.join( + __dirname, + 'client/coral-framework/helpers/webpackGlobals' + ), ]; if (disablePolyfill) { entries.push(modulePath); From abafa45be754903b99716a969e4ed2573ca573f9 Mon Sep 17 00:00:00 2001 From: Mendel Konikov Date: Thu, 24 May 2018 17:20:54 -0400 Subject: [PATCH 128/162] Replace break all with break-word --- client/coral-admin/src/routes/Moderation/components/Comment.css | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.css b/client/coral-admin/src/routes/Moderation/components/Comment.css index 864f6f5fa..342c27775 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.css +++ b/client/coral-admin/src/routes/Moderation/components/Comment.css @@ -74,6 +74,7 @@ max-width: 500px; font-weight: 300; font-size: 16px; + word-break: break-word; } .created { From 16573b127e785c28cca77e4aeb2c7851a6684261 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 16:19:52 -0600 Subject: [PATCH 129/162] text-replace error --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 96c09dd9e..3d815e53b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -74,7 +74,7 @@ const config = { target: 'web', output: { path: path.join(__dirname, 'dist'), - webpackGlobals: '', + publicPath: '', filename: '[name].[chunkhash].js', chunkFilename: '[name].[chunkhash].chunk.js', }, From 4800e3275837449e5fdbe3c047db548aeeb2aee5 Mon Sep 17 00:00:00 2001 From: Mendel Konikov Date: Thu, 24 May 2018 19:07:00 -0400 Subject: [PATCH 130/162] Use overflow wrap for better compatibility --- .../routes/Moderation/components/Comment.css | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.css b/client/coral-admin/src/routes/Moderation/components/Comment.css index 342c27775..dd5f0637c 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.css +++ b/client/coral-admin/src/routes/Moderation/components/Comment.css @@ -1,5 +1,4 @@ @custom-media --big-viewport (min-width: 780px); - .root { border-bottom: 1px solid #e0e0e0; font-size: 18px; @@ -13,7 +12,6 @@ margin-top: 13px; min-height: 0; outline: 0; - /* Fix rendering issues in Safari by promoting this into its own layer. @@ -21,7 +19,6 @@ https://www.pivotaltracker.com/story/show/151142211 */ transform: translateZ(0); - &:last-child { border-bottom: none; } @@ -39,7 +36,6 @@ display: flex; align-items: center; justify-content: space-between; - } .author { @@ -74,7 +70,7 @@ max-width: 500px; font-weight: 300; font-size: 16px; - word-break: break-word; + overflow-wrap: break-word; } .created { @@ -95,7 +91,6 @@ font-weight: 500; line-height: 1.2; max-width: 500px; - a { display: inline-block; color: #063b9a; @@ -103,17 +98,15 @@ font-weight: 500; letter-spacing: .5px; margin-left: 10px; - font-size: 13px; margin-left: 5px; padding-bottom: 0px; border-bottom: solid 1px; line-height: 16px; - &:hover { opacity: .9; cursor: pointer; - } + } } } @@ -138,12 +131,10 @@ cursor: pointer; font-weight: normal; white-space: nowrap; - &:hover { text-decoration: underline; opacity: .9; } - i { font-size: 12px; position: relative; @@ -172,7 +163,6 @@ align-items: center; justify-content: flex-end; margin-right: 14px; - i { margin-right: 5px; } @@ -181,7 +171,6 @@ @media (--big-viewport) { .root { margin-bottom: 30px; - &:last-child { border-bottom: 1px solid #e0e0e0; } @@ -190,7 +179,6 @@ .commentContent { display: flex; - blockquote { font-size: inherit; line-height: inherit; @@ -200,4 +188,4 @@ .commentContentFooter { flex: 1; -} +} \ No newline at end of file From 56483f38886f5840b202e5af9d6d2ec3a71ee186 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 25 May 2018 16:09:38 +0200 Subject: [PATCH 131/162] Add missing translations (en/de) for plugins ignore-user and local-auth. --- plugins/talk-plugin-ignore-user/client/translations.yml | 1 + .../client/components/ChangePassword.js | 8 +++++--- .../client/components/ChangeUsernameContentDialog.js | 2 +- plugins/talk-plugin-local-auth/translations.yml | 8 ++++++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/plugins/talk-plugin-ignore-user/client/translations.yml b/plugins/talk-plugin-ignore-user/client/translations.yml index 84a4ca11c..fdf2b1daf 100644 --- a/plugins/talk-plugin-ignore-user/client/translations.yml +++ b/plugins/talk-plugin-ignore-user/client/translations.yml @@ -39,6 +39,7 @@ en: confirmation_title: Ignore {0}? de: talk-plugin-ignore-user: + blank_info: Sie ignorieren derzeit keine Nutzer section_title: Ignorierte Nutzer section_info: Weil Sie die folgenden Nutzer ignorieren, sind deren Kommentare versteckt. stop_ignoring: Ignorieren beenden diff --git a/plugins/talk-plugin-local-auth/client/components/ChangePassword.js b/plugins/talk-plugin-local-auth/client/components/ChangePassword.js index 010df0826..cae112203 100644 --- a/plugins/talk-plugin-local-auth/client/components/ChangePassword.js +++ b/plugins/talk-plugin-local-auth/client/components/ChangePassword.js @@ -185,7 +185,7 @@ class ChangePassword extends React.Component { > Date: Fri, 25 May 2018 16:31:07 +0200 Subject: [PATCH 132/162] One more missing German translation --- plugins/talk-plugin-local-auth/translations.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/talk-plugin-local-auth/translations.yml b/plugins/talk-plugin-local-auth/translations.yml index c4dd56061..ec5bc90d7 100644 --- a/plugins/talk-plugin-local-auth/translations.yml +++ b/plugins/talk-plugin-local-auth/translations.yml @@ -98,6 +98,7 @@ de: forgot_password_sent: "Passwort vergessen - Wir haben Ihnen eine E-Mail zum Zurücksetzen des Passwortes geschickt" change_username: change_username_note: "Nutzernamen können nur alle 14 Tage geändert werden. Ihr Nutzername ist zur Zeit nicht editierbar." + is_not_eligible: "Sie können Ihren Benutzernamen derzeit nicht ändern." save: "Speichern" edit_profile: "Profil ändern" cancel: "Abbrechen" From 325e763fd0f8417b49879afd4df8f089b6755f6c Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 25 May 2018 16:34:29 +0200 Subject: [PATCH 133/162] Remove redundancy --- plugins/talk-plugin-local-auth/translations.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/talk-plugin-local-auth/translations.yml b/plugins/talk-plugin-local-auth/translations.yml index ec5bc90d7..fa9515432 100644 --- a/plugins/talk-plugin-local-auth/translations.yml +++ b/plugins/talk-plugin-local-auth/translations.yml @@ -97,8 +97,8 @@ de: changed_password_msg: "Passwort geändert - Ihr Passwort wurde erfolgreich geändert" forgot_password_sent: "Passwort vergessen - Wir haben Ihnen eine E-Mail zum Zurücksetzen des Passwortes geschickt" change_username: - change_username_note: "Nutzernamen können nur alle 14 Tage geändert werden. Ihr Nutzername ist zur Zeit nicht editierbar." - is_not_eligible: "Sie können Ihren Benutzernamen derzeit nicht ändern." + change_username_note: "Nutzernamen können nur alle 14 Tage geändert werden." + is_not_eligible: "Sie können Ihren Nutzernamen derzeit nicht ändern." save: "Speichern" edit_profile: "Profil ändern" cancel: "Abbrechen" From b7d2621cb89f510c16b2b4f2d0e8af0e2dfa1f8f Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 25 May 2018 17:19:43 +0200 Subject: [PATCH 134/162] Remove redundant period. It's in the translation strings already. --- services/mailer/templates/password-reset.txt.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/mailer/templates/password-reset.txt.ejs b/services/mailer/templates/password-reset.txt.ejs index 2b5e60a9b..577f23bec 100644 --- a/services/mailer/templates/password-reset.txt.ejs +++ b/services/mailer/templates/password-reset.txt.ejs @@ -1,3 +1,3 @@ -<%= t('email.password_reset.we_received_a_request') %>. <%= t('email.password_reset.if_you_did') %> <%= t('email.password_reset.please_click') %>: +<%= t('email.password_reset.we_received_a_request') %> <%= t('email.password_reset.if_you_did') %> <%= t('email.password_reset.please_click') %>: <%= BASE_URL %>account/password/reset#<%= token %> From e1d77daf47afceddcf73a3d9ae4f9bc920caccb6 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 25 May 2018 11:16:24 -0600 Subject: [PATCH 135/162] enhanced docs around jwt's --- docs/source/02-01-required-configuration.md | 2 +- docs/source/integrating/authentication.md | 31 +++++++++++++++------ docs/themes/coral/source/css/talk.scss | 5 ++-- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/docs/source/02-01-required-configuration.md b/docs/source/02-01-required-configuration.md index be1e1d0b2..a4a26b73d 100644 --- a/docs/source/02-01-required-configuration.md +++ b/docs/source/02-01-required-configuration.md @@ -81,4 +81,4 @@ TALK_JWT_SECRET=jX9y8G2ApcVLwyL{$6s3 Be default, we sign our tokens with HMAC using a SHA-256 hash algorithm. If you want to change the signing algorithm, or use multiple signing/verifying keys, -refer to our [Advanced Configuration](/talk/advanced-configuration/) documentation. +refer to our [Advanced Configuration](/talk/advanced-configuration/#talk-jwt-secret) documentation. diff --git a/docs/source/integrating/authentication.md b/docs/source/integrating/authentication.md index 5b19768fe..77315a9d8 100644 --- a/docs/source/integrating/authentication.md +++ b/docs/source/integrating/authentication.md @@ -25,9 +25,10 @@ state (you don't use the auth anywhere else now). A great example of this is our You can integrate Talk with any authentication service to enable single sign-on for users. The steps to do that are: -1. Create a service that generates [JWT tokens](https://jwt.io). +1. Create a service that generates [JWT tokens](https://jwt.io/introduction/). 2. Push the token into the embed. -3. Implement the `tokenUserNotFound` hook to process the token. +3. Implement the [`tokenUserNotFound`](#implement-tokenusernotfound) hook to + process the token. ### Create JWT Token @@ -39,7 +40,20 @@ Using that demo application, you'll see how you can: 1. Create a node application that can issue JWT's that are compatible with Talk. 2. Provide a validation endpoint that can be used by Talk to validate the token - and get the user via the `tokenUserNotFound` hook. + and get the user via the [`tokenUserNotFound`](#implement-tokenusernotfound) + hook. + +It's also important to note a few requirements for proper integration with Talk. +The generated JWT must contain the following claims: + +- [`jti`](https://tools.ietf.org/html/rfc7519#section-4.1.7): a unique identifier for the token (like a uuid/v4) +- [`exp`](https://tools.ietf.org/html/rfc7519#section-4.1.4): the expiry date of the token as a unix timestamp +- [`sub`](https://tools.ietf.org/html/rfc7519#section-4.1.2): the user identifier that can be used to lookup the user in the mongo + database + - The user may not yet exist in the database, but that's the responsibility + of the [`tokenUserNotFound`](#implement-tokenusernotfound) hook. +- [`iss`](https://tools.ietf.org/html/rfc7519#section-4.1.1): the issuer for the token must match the value of `TALK_JWT_ISSUER` +- [`aud`](https://tools.ietf.org/html/rfc7519#section-4.1.3): the audience for the token must match the value of `TALK_JWT_AUDIENCE` ### Push token into embed @@ -47,7 +61,8 @@ We're assuming that your CMS is capable of authenticating a user account, or at least having the user's details available to send off to the token creation service we created/used in the previous step. -Using the token that was created for the user, you simply have to ammend the template where Talk is rendering to read as the following: +Using the token that was created for the user, you simply have to amend the +template where Talk is rendering to read as the following: ```js Coral.Talk.render(document.getElementById('coralStreamEmbed'), { @@ -72,12 +87,12 @@ example issuer and Talk must match: | Talk | Token Issuer Example | |------|----------------------| -|`JWT_ISSUER`|`JWT_ISSUER`| -|`JWT_AUDIENCE`|`JWT_AUDIENCE`| -|`SECRET`|`JWT_SECRET`*| +|[`TALK_JWT_ISSUER`](/talk/advanced-configuration/#talk-jwt-issuer)|`JWT_ISSUER`| +|[`TALK_JWT_AUDIENCE`](/talk/advanced-configuration/#talk-jwt-audience)|`JWT_AUDIENCE`| +|[`TALK_JWT_SECRET`](/talk/advanced-configuration/#talk-jwt-secret)|`JWT_SECRET`*| \* Note that secrets is a pretty complex topic, refer to the -[TALK-JWT-SECRET](/talk/advanced-configuration/#TALK-JWT-SECRET) configuration +[TALK_JWT_SECRET](/talk/advanced-configuration/#talk-jwt-secret) configuration reference, the basic takeaway is that the secret used to sign the tokens issued by the issuer must be able to be verified by Talk. diff --git a/docs/themes/coral/source/css/talk.scss b/docs/themes/coral/source/css/talk.scss index afbd6f3af..f3b0c24e8 100644 --- a/docs/themes/coral/source/css/talk.scss +++ b/docs/themes/coral/source/css/talk.scss @@ -291,11 +291,10 @@ pre { .content { article { - p a:not(.plain-link) { - @extend .coral-link; - } + p a:not(.plain-link), ul:not(.toc__menu) li a, ol li a, + td a, dd > a { @extend .coral-link; } From 3a79cc684b9bf6df799bfef8aebe1ae3fce8c282 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 25 May 2018 11:19:02 -0600 Subject: [PATCH 136/162] added missing translation --- locales/en.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en.yml b/locales/en.yml index 534593679..950f4bbd2 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -227,6 +227,7 @@ en: embedlink: copy: "Copy to Clipboard" error: + AUTHENTICATION: "An error occurred trying to authenticate your account." PASSWORD_INCORRECT: "Your current password was entered incorrectly" COMMENT_PARENT_NOT_VISIBLE: "The comment that you're replying to has been removed or doesn't exist." EMAIL_VERIFICATION_TOKEN_INVALID: "Email verification token is invalid." From 7d861236b8b4ab578456b1a2f34e59412b131445 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 25 May 2018 11:21:53 -0600 Subject: [PATCH 137/162] disable CSP until we can work on configuration + webpack issues --- docs/source/02-02-advanced-configuration.md | 5 +++-- routes/index.js | 4 ++-- routes/plugins.js | 5 ++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/02-02-advanced-configuration.md b/docs/source/02-02-advanced-configuration.md index 13ad2c35a..451865e47 100644 --- a/docs/source/02-02-advanced-configuration.md +++ b/docs/source/02-02-advanced-configuration.md @@ -512,13 +512,14 @@ tracing of GraphQL requests. **Note: Apollo Engine is a premium service, charges may apply.** -## TALK_ENABLE_STRICT_CSP + + ## ALLOW_NO_LIMIT_QUERIES diff --git a/routes/index.js b/routes/index.js index 5183e2881..cb8e03b33 100644 --- a/routes/index.js +++ b/routes/index.js @@ -9,7 +9,6 @@ const path = require('path'); const compression = require('compression'); const plugins = require('../services/plugins'); const staticTemplate = require('../middleware/staticTemplate'); -const contentSecurityPolicy = require('../middleware/contentSecurityPolicy'); const nonce = require('../middleware/nonce'); const staticServer = require('express-static-gzip'); const { DISABLE_STATIC_SERVER } = require('../config'); @@ -76,7 +75,8 @@ router.use(compression()); // STATIC ROUTES //============================================================================== -const staticMiddleware = [staticTemplate, nonce, contentSecurityPolicy]; +// TODO: re-add CSP once we've resolved issues with dynamic webpack loading. +const staticMiddleware = [staticTemplate, nonce]; router.use('/admin', ...staticMiddleware, require('./admin')); router.use('/account', ...staticMiddleware, require('./account')); diff --git a/routes/plugins.js b/routes/plugins.js index fcc1125b9..8504c0c94 100644 --- a/routes/plugins.js +++ b/routes/plugins.js @@ -2,13 +2,12 @@ const express = require('express'); const debug = require('debug')('talk:routes:plugins'); const plugins = require('../services/plugins'); const staticTemplate = require('../middleware/staticTemplate'); -const contentSecurityPolicy = require('../middleware/contentSecurityPolicy'); const nonce = require('../middleware/nonce'); const router = express.Router(); -// Apply the middleware. -router.use(staticTemplate, nonce, contentSecurityPolicy); +// TODO: re-add CSP once we've resolved issues with dynamic webpack loading. +router.use(staticTemplate, nonce); // Inject server route plugins. plugins.get('server', 'router').forEach(plugin => { From 540df09b5bbe60af48ea4473e0beb750a7399ed9 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 25 May 2018 11:48:55 -0600 Subject: [PATCH 138/162] Update stream.njk --- views/embed/stream.njk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/embed/stream.njk b/views/embed/stream.njk index d6c53b210..8b9cf52b6 100644 --- a/views/embed/stream.njk +++ b/views/embed/stream.njk @@ -11,8 +11,8 @@
      {# - Resolves touch handling issues encountered on IOS Safari under certain circumstances - https://www.pivotaltracker.com/n/projects/1863625 + Resolves touch handling issues encountered on IOS Safari under certain circumstances: + https://stackoverflow.com/questions/12363742/touchstart-event-is-not-firing-inside-iframe-ios-6 #} From c1eab3f2c65b46de00daa33f662ffc8322b2e91e Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 25 May 2018 11:51:43 -0600 Subject: [PATCH 139/162] moved event handler to bundle --- client/coral-embed-stream/src/index.js | 5 +++++ views/embed/stream.njk | 7 ------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index a4c6bcd39..1142239c6 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -8,6 +8,11 @@ import reducers from './reducers'; import TalkProvider from 'coral-framework/components/TalkProvider'; import pluginsConfig from 'pluginsConfig'; +// Resolves touch handling issues encountered on IOS Safari under certain +// circumstances. It may be related to issues reported here: +// https://stackoverflow.com/questions/12363742/touchstart-event-is-not-firing-inside-iframe-ios-6 +document.body.addEventListener('touchstart', () => {}); + async function main() { const context = await createContext({ reducers, diff --git a/views/embed/stream.njk b/views/embed/stream.njk index 8b9cf52b6..ddc620f8e 100644 --- a/views/embed/stream.njk +++ b/views/embed/stream.njk @@ -9,13 +9,6 @@ {% block html %}
      - -{# - Resolves touch handling issues encountered on IOS Safari under certain circumstances: - https://stackoverflow.com/questions/12363742/touchstart-event-is-not-firing-inside-iframe-ios-6 -#} - - {% endblock %} {% block js %} From ba2abab3589211aaf6ebec1854147f67ef892670 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 25 May 2018 11:54:34 -0600 Subject: [PATCH 140/162] added pivotal tracker ref --- client/coral-embed-stream/src/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 1142239c6..a2718e49d 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -10,7 +10,10 @@ import pluginsConfig from 'pluginsConfig'; // Resolves touch handling issues encountered on IOS Safari under certain // circumstances. It may be related to issues reported here: -// https://stackoverflow.com/questions/12363742/touchstart-event-is-not-firing-inside-iframe-ios-6 +// +// https://stackoverflow.com/questions/12363742/touchstart-event-is-not-firing-inside-iframe-ios-6 +// +// Further details: https://www.pivotaltracker.com/story/show/157794038 document.body.addEventListener('touchstart', () => {}); async function main() { From 2c6843129a47b8abf26110e7e1842486c924e38e Mon Sep 17 00:00:00 2001 From: Erik Reyna Date: Fri, 25 May 2018 15:02:34 -0400 Subject: [PATCH 141/162] move placeholder right behind contentEditable --- plugins/talk-plugin-rich-text/client/components/rte/RTE.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/talk-plugin-rich-text/client/components/rte/RTE.css b/plugins/talk-plugin-rich-text/client/components/rte/RTE.css index 2bf1feaf5..0927c7eb3 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/RTE.css +++ b/plugins/talk-plugin-rich-text/client/components/rte/RTE.css @@ -1,5 +1,5 @@ .contentEditable { - background: #fff; + background: transparent; border: solid 1px #bbb; min-height: 120px; box-sizing: border-box; @@ -15,6 +15,7 @@ position: absolute; margin: 12px 0 0 12px; color: #bbb; + z-index: -1; } .toolbarDisabled { From cedabf04e305514aaaac709064ae637d47fb3b60 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Fri, 25 May 2018 15:04:00 -0400 Subject: [PATCH 142/162] Bump version to 4.4.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b21e2f78a..2697484c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "talk", - "version": "4.4.1", + "version": "4.4.2", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", "private": true, From 90e4682cf68acbd3d534d1e8edbcd3ac94bc0771 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 25 May 2018 22:49:35 +0200 Subject: [PATCH 143/162] Take karma into account when doing e2e --- .../src/components/ApproveButton.js | 3 +- .../src/components/RejectButton.js | 3 +- .../Moderation/components/ModerationMenu.js | 1 + .../Moderation/components/ModerationQueue.js | 10 +++- test/e2e/globals.js | 3 -- test/e2e/page_objects/admin.js | 23 ++++++++- test/e2e/specs/03_embedStream.js | 10 ++-- test/e2e/specs/05_banUser.js | 49 +++++++++++++++++++ test/e2e/specs/06_suspendUser.js | 19 +++---- 9 files changed, 98 insertions(+), 23 deletions(-) diff --git a/client/coral-admin/src/components/ApproveButton.js b/client/coral-admin/src/components/ApproveButton.js index 884ac3da5..96dbdd375 100644 --- a/client/coral-admin/src/components/ApproveButton.js +++ b/client/coral-admin/src/components/ApproveButton.js @@ -14,7 +14,8 @@ const ApproveButton = ({ active, minimal, onClick, className, disabled }) => { className={cn( styles.root, { [styles.minimal]: minimal, [styles.active]: active }, - className + className, + 'talk-admin-approve-button' )} onClick={onClick} disabled={disabled || active} diff --git a/client/coral-admin/src/components/RejectButton.js b/client/coral-admin/src/components/RejectButton.js index 69e1047e6..2aabff007 100644 --- a/client/coral-admin/src/components/RejectButton.js +++ b/client/coral-admin/src/components/RejectButton.js @@ -14,7 +14,8 @@ const RejectButton = ({ active, minimal, onClick, className, disabled }) => { className={cn( styles.root, { [styles.minimal]: minimal, [styles.active]: active }, - className + className, + 'talk-admin-reject-button' )} onClick={onClick} disabled={disabled || active} diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js index 27394c6c9..5300ce3eb 100644 --- a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js @@ -24,6 +24,7 @@ const ModerationMenu = ({ asset = {}, items, getModPath, activeTab }) => { > {items.map(queue => ( nodes.some(node => node.id === id); @@ -380,6 +381,11 @@ class ModerationQueue extends React.Component { ...props } = this.props; + const rootClassName = cn( + styles.root, + `talk-admin-moderate-queue-${this.props.activeTab}` + ); + if (comments.length === 0) { return (
      @@ -401,7 +407,7 @@ class ModerationQueue extends React.Component { const comment = comments[index]; return ( -
      +
      +
      this.viewNewComments()} count={comments.length - view.length} diff --git a/test/e2e/globals.js b/test/e2e/globals.js index aeecd204e..6a7eb07fb 100644 --- a/test/e2e/globals.js +++ b/test/e2e/globals.js @@ -23,9 +23,6 @@ module.exports = { username: 'user', password: 'testtest', }, - comment: { - body: 'This is a test comment', - }, organizationName: 'Coral', organizationContactEmail: 'coral@coralproject.net', }, diff --git a/test/e2e/page_objects/admin.js b/test/e2e/page_objects/admin.js index 8e319e499..64d22c97b 100644 --- a/test/e2e/page_objects/admin.js +++ b/test/e2e/page_objects/admin.js @@ -70,9 +70,28 @@ module.exports = { }, moderate: { selector: '.talk-admin-moderation-container', + commands: [ + { + url: function() { + return `${this.api.launchUrl}/admin/moderate`; + }, + ready() { + return this.waitForElementVisible('body'); + }, + goToQueue(queue) { + this.click(`#talk-admin-moderate-tab-${queue}`).expect.section( + `.talk-admin-moderate-queue-${queue}` + ).to.be.visible; + return this; + }, + }, + ], elements: { - comment: '.talk-admin-moderate-comment', - commentUsername: '.talk-admin-moderate-comment-username', + firstComment: '.talk-admin-moderate-comment', + firstCommentUsername: '.talk-admin-moderate-comment-username', + firstCommentContent: '.talk-admin-comment', + firstCommentApprove: '.talk-admin-approve-button', + firstCommentReject: '.talk-admin-reject-button', }, }, stories: { diff --git a/test/e2e/specs/03_embedStream.js b/test/e2e/specs/03_embedStream.js index 4b6831271..9eca7a736 100644 --- a/test/e2e/specs/03_embedStream.js +++ b/test/e2e/specs/03_embedStream.js @@ -1,3 +1,5 @@ +const commentBody = 'Embed Stream Test'; + module.exports = { '@tags': ['embedStream', 'login'], @@ -40,28 +42,26 @@ module.exports = { }, 'user posts a comment': client => { const comments = client.page.embedStream().section.comments; - const { testData: { comment } } = client.globals; comments .waitForElementVisible('@commentBoxTextarea') - .setValue('@commentBoxTextarea', comment.body) + .setValue('@commentBoxTextarea', commentBody) .waitForElementVisible('@commentBoxPostButton') .click('@commentBoxPostButton') .waitForElementVisible('@firstCommentContent') .getText('@firstCommentContent', result => { - comments.assert.equal(result.value, comment.body); + comments.assert.equal(result.value, commentBody); }); }, 'signed in user sees comment history': client => { const profile = client.page.embedStream().goToProfileSection(); - const { testData: { comment } } = client.globals; profile .waitForElementVisible('@myCommentHistory') .waitForElementVisible('@myCommentHistoryComment') .getText('@myCommentHistoryComment', result => { - profile.assert.equal(result.value, comment.body); + profile.assert.equal(result.value, commentBody); }); }, 'user sees replies and reactions to comments': client => { diff --git a/test/e2e/specs/05_banUser.js b/test/e2e/specs/05_banUser.js index 45ab0c347..51055a180 100644 --- a/test/e2e/specs/05_banUser.js +++ b/test/e2e/specs/05_banUser.js @@ -1,3 +1,5 @@ +const commentBody = 'Ban User Test'; + module.exports = { before: client => { client.setWindowPosition(0, 0); @@ -109,4 +111,51 @@ module.exports = { .waitForElementVisible('@commentBoxTextarea') .waitForElementVisible('@commentBoxPostButton'); }, + 'user posts comment, karma should stop it from happening': client => { + const comments = client.page.embedStream().section.comments; + + comments + .waitForElementVisible('@commentBoxTextarea') + .setValue('@commentBoxTextarea', commentBody) + .waitForElementVisible('@commentBoxPostButton') + .click('@commentBoxPostButton'); + + client.pause(2000); + + comments.waitForElementNotPresent('@firstCommentContent'); + }, + 'user logs out 3': client => { + const embedStream = client.page.embedStream(); + const comments = embedStream.section.comments; + + comments.logout(); + }, + 'admin logs in (3)': client => { + const adminPage = client.page.admin(); + const { testData: { admin } } = client.globals; + + adminPage.navigateAndLogin(admin); + }, + 'admin goes to moderation queue reported': client => { + const adminPage = client.page.admin(); + + adminPage.goToModerate().goToQueue('reported'); + }, + 'comment should be in reported queue': client => { + const moderate = client.page.admin().section.moderate; + + moderate + .waitForElementVisible('@firstComment') + .getText('@firstCommentContent', result => { + moderate.assert.equal(result.value, commentBody); + }); + }, + 'approve comment to restore karma': client => { + const moderate = client.page.admin().section.moderate; + + moderate.click('@firstCommentApprove'); + + // TODO: check why this fails. + // .waitForElementNotPresent('@firstComment'); + }, }; diff --git a/test/e2e/specs/06_suspendUser.js b/test/e2e/specs/06_suspendUser.js index 81fde3781..e02f21db9 100644 --- a/test/e2e/specs/06_suspendUser.js +++ b/test/e2e/specs/06_suspendUser.js @@ -1,3 +1,5 @@ +const commentBody = 'Suspend User Test'; + module.exports = { before: client => { client.setWindowPosition(0, 0); @@ -25,16 +27,15 @@ module.exports = { }, 'user posts comment': client => { const comments = client.page.embedStream().section.comments; - const { testData: { comment } } = client.globals; comments .waitForElementVisible('@commentBoxTextarea') - .setValue('@commentBoxTextarea', comment.body) + .setValue('@commentBoxTextarea', commentBody) .waitForElementVisible('@commentBoxPostButton') .click('@commentBoxPostButton') .waitForElementVisible('@firstCommentContent') .getText('@firstCommentContent', result => { - comments.assert.equal(result.value, comment.body); + comments.assert.equal(result.value, commentBody); }); }, 'user logs out': client => { @@ -84,9 +85,9 @@ module.exports = { .goToModerate(); moderate - .waitForElementVisible('@comment') - .waitForElementVisible('@commentUsername') - .click('@commentUsername'); + .waitForElementVisible('@firstComment') + .waitForElementVisible('@firstCommentUsername') + .click('@firstCommentUsername'); userDetailDrawer .waitForElementVisible('@actionsMenu') @@ -112,9 +113,9 @@ module.exports = { const { moderate, userDetailDrawer } = adminPage.section; moderate - .waitForElementVisible('@comment') - .waitForElementVisible('@commentUsername') - .click('@commentUsername'); + .waitForElementVisible('@firstComment') + .waitForElementVisible('@firstCommentUsername') + .click('@firstCommentUsername'); userDetailDrawer .waitForElementVisible('@tabBar') From ba3af0aeb34f5e1ababde2a44ab0980906035e41 Mon Sep 17 00:00:00 2001 From: Erik Reyna Date: Fri, 25 May 2018 18:07:41 -0400 Subject: [PATCH 144/162] apply @cvle suggestion for fix --- plugins/talk-plugin-rich-text/client/components/rte/RTE.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/talk-plugin-rich-text/client/components/rte/RTE.css b/plugins/talk-plugin-rich-text/client/components/rte/RTE.css index 0927c7eb3..683388707 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/RTE.css +++ b/plugins/talk-plugin-rich-text/client/components/rte/RTE.css @@ -1,5 +1,5 @@ .contentEditable { - background: transparent; + background: #fff; border: solid 1px #bbb; min-height: 120px; box-sizing: border-box; @@ -15,7 +15,8 @@ position: absolute; margin: 12px 0 0 12px; color: #bbb; - z-index: -1; + user-select: none; + pointer-events: none; } .toolbarDisabled { From 1fb206daa42d8d1ef1fa08c4f0c5bd6c2ab6bc52 Mon Sep 17 00:00:00 2001 From: Helmut Januschka Date: Mon, 28 May 2018 10:56:46 +0200 Subject: [PATCH 145/162] add missing dependency --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 2697484c4..de37ff004 100644 --- a/package.json +++ b/package.json @@ -157,6 +157,7 @@ "node-emoji": "^1.8.1", "node-fetch": "^1.7.2", "nodemailer": "^2.6.4", + "nunjucks": "^3.1.3", "npm-run-all": "^4.1.2", "parallel-webpack": "^2.2.0", "passport": "^0.4.0", From 657736593068ed0c41311313faf6800db064bd18 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 28 May 2018 16:19:56 +0200 Subject: [PATCH 146/162] Use standard buttons to fix IOS bug --- client/coral-framework/styles/reset.css | 1 + client/coral-ui/components/BareButton.js | 25 +++++++++++++------- client/coral-ui/components/Dropdown.css | 30 ++++++++++-------------- client/coral-ui/components/Dropdown.js | 24 ++++--------------- client/coral-ui/components/Option.css | 2 ++ client/coral-ui/components/Option.js | 25 +++++++++++--------- 6 files changed, 51 insertions(+), 56 deletions(-) diff --git a/client/coral-framework/styles/reset.css b/client/coral-framework/styles/reset.css index 8cfd14c41..0a7ddb46b 100644 --- a/client/coral-framework/styles/reset.css +++ b/client/coral-framework/styles/reset.css @@ -6,6 +6,7 @@ border: none; touch-action: manipulation; padding: 0; + margin: 0; overflow: hidden; diff --git a/client/coral-ui/components/BareButton.js b/client/coral-ui/components/BareButton.js index 6d3f9fa93..911d76297 100644 --- a/client/coral-ui/components/BareButton.js +++ b/client/coral-ui/components/BareButton.js @@ -7,17 +7,26 @@ import cn from 'classnames'; * BareButton is a button whose styling is stripped off to a minimum. * Can pass anchor=true to use `a` instead of `button` */ -const BareButton = ({ anchor, className, ...props }) => { - let Element = 'button'; - if (anchor) { - Element = 'a'; +export default class BareButton extends React.Component { + ref = null; + + handleRef = ref => (this.ref = ref); + focus = () => this.ref.focus(); + + render() { + const { anchor, className, ...props } = this.props; + const Element = anchor ? 'a' : 'button'; + return ( + + ); } - return ; -}; +} BareButton.propTypes = { className: PropTypes.string, anchor: PropTypes.bool, }; - -export default BareButton; diff --git a/client/coral-ui/components/Dropdown.css b/client/coral-ui/components/Dropdown.css index c17e6322a..3128a8be4 100644 --- a/client/coral-ui/components/Dropdown.css +++ b/client/coral-ui/components/Dropdown.css @@ -1,32 +1,26 @@ .dropdown { position: relative; - height: 34px; - background: #2c2c2c; - box-sizing: border-box; - color: white; - border-radius: 3px; - box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); - line-height: 20px; - - font-size: 0.98em; - border-radius: 3px; - cursor: pointer; - - &.disabled { - color: #e5e5e5; - background: #888; - cursor: default; - pointer-events: none; - } } .toggle { padding: 8px 45px 8px 15px; outline: none; + color: white; + background: #2c2c2c; + border-radius: 3px; + height: 34px; + box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); + line-height: 20px; + font-size: 0.98em; &:focus { background: #888; } + + &:disabled { + color: #e5e5e5; + background: #888; + } } .toggleOpen { diff --git a/client/coral-ui/components/Dropdown.js b/client/coral-ui/components/Dropdown.js index bf885477f..380d6c91f 100644 --- a/client/coral-ui/components/Dropdown.js +++ b/client/coral-ui/components/Dropdown.js @@ -4,6 +4,7 @@ import styles from './Dropdown.css'; import Icon from './Icon'; import cn from 'classnames'; import ClickOutside from 'coral-framework/components/ClickOutside'; +import { BareButton } from 'coral-ui'; class Dropdown extends React.Component { toggleRef = null; @@ -88,16 +89,6 @@ class Dropdown extends React.Component { this.toggle(); }; - handleKeyDown = e => { - const code = e.which; - - // 13 = Return, 32 = Space - if (code === 13 || code === 32) { - e.preventDefault(); - this.toggle(); - } - }; - hideMenu = () => { this.setState({ isOpen: false, @@ -155,23 +146,18 @@ class Dropdown extends React.Component { styles.dropdown, className, containerClassName, - 'dd dd-container', - { - [styles.disabled]: disabled, - } + 'dd dd-container' )} > -
      {this.props.icon && (
      + {this.state.isOpen && (
      diff --git a/client/coral-ui/components/Option.css b/client/coral-ui/components/Option.css index 892bc4e7c..eb76df93d 100644 --- a/client/coral-ui/components/Option.css +++ b/client/coral-ui/components/Option.css @@ -1,7 +1,9 @@ .option { + width: 100%; padding: 10px; outline: none; white-space: nowrap; + text-align: left; &:focus, &:hover { background-color: #ccc; diff --git a/client/coral-ui/components/Option.js b/client/coral-ui/components/Option.js index 704bca179..95473d4c6 100644 --- a/client/coral-ui/components/Option.js +++ b/client/coral-ui/components/Option.js @@ -1,13 +1,15 @@ import React from 'react'; +import { findDOMNode } from 'react-dom'; import PropTypes from 'prop-types'; import styles from './Option.css'; import cn from 'classnames'; +import { BareButton } from 'coral-ui'; class Option extends React.Component { ref = null; handleRef = ref => { - this.ref = ref; + this.ref = findDOMNode(ref); }; focus = () => { @@ -19,16 +21,17 @@ class Option extends React.Component { const { className, label = '', onClick, onKeyDown } = this.props; const id = this.props.id ? this.props.id : this.props.value; return ( -
    • - {label} +
    • + + {label} +
    • ); } From ed679096c84f2437ee5fc9b25c9850020eccc441 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 28 May 2018 17:16:44 +0200 Subject: [PATCH 147/162] Workaround RTE Button focus bug on IOS --- .../client/components/rte/factories/createToggle.js | 11 ----------- .../client/components/rte/lib/api.js | 11 ++++++++++- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js b/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js index 59b75fdf2..1fe8641f6 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import Button from '../components/Button'; -import bowser from 'bowser'; /** * createToggle creates a button that can be active, inactive or disabled @@ -33,17 +32,7 @@ const createToggle = ( this.execCommand(); }; - // Detect whether there was focus on the RTE before the click. - hadFocusBeforeClick = false; - handleMouseDown = () => (this.hadFocusBeforeClick = this.props.api.focused); - handleClick = () => { - // Skip IOS when the focus was not there before. - // IOS fails to focus to the RTE correctly and scrolls to nirvana. - // See https://www.pivotaltracker.com/story/show/157607216 - if (!this.hadFocusBeforeClick && bowser.ios) { - return; - } this.props.api.focus(); this.formatToggle(); this.props.api.focus(); diff --git a/plugins/talk-plugin-rich-text/client/components/rte/lib/api.js b/plugins/talk-plugin-rich-text/client/components/rte/lib/api.js index 9e018f093..0c5fbcf2f 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/lib/api.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/lib/api.js @@ -1,4 +1,4 @@ -import { isSelectionInside } from './dom'; +import { isSelectionInside, selectEndOfNode } from '../lib/dom'; /** * An instance of API is passed to all the buttons to @@ -26,6 +26,15 @@ function createAPI( return getContainer(); }, focus() { + // IOS workaround inspired by https://github.com/facebook/draft-js/issues/1075#issuecomment-369700275. + // Putting the selection inside the contentEditable before calling `focus` + // prevents an undesired scroll jump. + if (!isSelectionInside(this.container)) { + if (this.container.childNodes.length === 0) { + this.container.appendChild(document.createTextNode('')); + } + selectEndOfNode(this.container); + } this.container.focus(); }, isSelectionInside() { From da820ad6db73811b2cdb52e15fc04414b4d36f91 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 28 May 2018 09:42:22 -0600 Subject: [PATCH 148/162] updated yarn.lock --- yarn.lock | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index a65a3524d..59b3958d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1977,6 +1977,24 @@ chokidar@^1.5.2, chokidar@^1.6.0, chokidar@^1.6.1, chokidar@^1.7.0: optionalDependencies: fsevents "^1.0.0" +chokidar@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.3.tgz#dcbd4f6cbb2a55b4799ba8a840ac527e5f4b1176" + dependencies: + anymatch "^2.0.0" + async-each "^1.0.0" + braces "^2.3.0" + glob-parent "^3.1.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^2.1.1" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + upath "^1.0.0" + optionalDependencies: + fsevents "^1.1.2" + chokidar@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.2.tgz#4dc65139eeb2714977735b6a35d06e97b494dfd7" @@ -2834,7 +2852,7 @@ debug@*, debug@3.1.0, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0: dependencies: ms "2.0.0" -debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: +debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -4106,6 +4124,12 @@ fs-extra@^4.0.1: jsonfile "^4.0.0" universalify "^0.1.0" +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + dependencies: + minipass "^2.2.1" + fs-promise@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/fs-promise/-/fs-promise-0.3.1.tgz#bf34050368f24d6dc9dfc6688ab5cead8f86842a" @@ -4136,6 +4160,13 @@ fsevents@^1.0.0, fsevents@^1.1.1: nan "^2.3.0" node-pre-gyp "^0.6.39" +fsevents@^1.1.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" + dependencies: + nan "^2.9.2" + node-pre-gyp "^0.10.0" + fstream-ignore@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" @@ -5019,6 +5050,12 @@ iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +iconv-lite@^0.4.4: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + dependencies: + safer-buffer ">= 2.1.2 < 3" + icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" @@ -5051,6 +5088,12 @@ ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + dependencies: + minimatch "^3.0.4" + ignore@^3.3.3, ignore@^3.3.5: version "3.3.7" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" @@ -7359,6 +7402,19 @@ minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" +minipass@^2.2.1, minipass@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233" + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" + dependencies: + minipass "^2.2.1" + mississippi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f" @@ -7566,6 +7622,10 @@ nan@^2.3.2: version "2.9.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866" +nan@^2.9.2: + version "2.10.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" + nanomatch@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" @@ -7620,6 +7680,14 @@ nearley@^2.7.10: railroad-diagrams "^1.0.0" randexp "^0.4.2" +needle@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d" + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" + negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" @@ -7759,6 +7827,21 @@ node-notifier@^5.2.1: shellwords "^0.1.1" which "^1.3.0" +node-pre-gyp@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46" + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.0" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.1.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + node-pre-gyp@^0.6.39: version "0.6.39" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" @@ -7965,6 +8048,17 @@ normalize-url@~2.0.0: query-string "^5.0.1" sort-keys "^2.0.0" +npm-bundled@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" + +npm-packlist@^1.1.6: + version "1.1.10" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a" + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-path@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.4.tgz#c641347a5ff9d6a09e4d9bce5580c4f505278e64" @@ -8033,6 +8127,17 @@ nunjucks@^3.1.2: optionalDependencies: chokidar "^1.6.0" +nunjucks@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/nunjucks/-/nunjucks-3.1.3.tgz#9a23c844af01c143a0b40f3bdd1212a9d7877260" + dependencies: + a-sync-waterfall "^1.0.0" + asap "^2.0.3" + postinstall-build "^5.0.1" + yargs "^3.32.0" + optionalDependencies: + chokidar "^2.0.0" + "nwmatcher@>= 1.3.7 < 2.0.0", nwmatcher@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c" @@ -10161,6 +10266,10 @@ safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, s version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +safe-buffer@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + safe-json-stringify@~1: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.1.0.tgz#bd2b6dad1ebafab3c24672a395527f01804b7e19" @@ -10171,6 +10280,10 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + samsam@1.1.2, samsam@~1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" @@ -11145,6 +11258,18 @@ tar@^2.0.0, tar@^2.2.1: fstream "^1.0.2" inherits "2" +tar@^4: + version "4.4.4" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd" + dependencies: + chownr "^1.0.1" + fs-minipass "^1.2.5" + minipass "^2.3.3" + minizlib "^1.1.0" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + tcomb@^2.5.1: version "2.7.0" resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0" @@ -12041,6 +12166,10 @@ yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" + yaml-lint@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/yaml-lint/-/yaml-lint-1.0.0.tgz#655068f583263eec7e2a0ded8f5b1895dd667e1c" From 4299c373bac2faede859bd7c08d4f7ad4c4e21c6 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 28 May 2018 15:17:31 -0300 Subject: [PATCH 149/162] changes and updates to the e2e --- nightwatch.conf.js | 6 +- package.json | 2 +- yarn.lock | 250 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 196 insertions(+), 62 deletions(-) diff --git a/nightwatch.conf.js b/nightwatch.conf.js index 75dfa5ef6..16777b844 100644 --- a/nightwatch.conf.js +++ b/nightwatch.conf.js @@ -10,15 +10,15 @@ module.exports = { selenium: { start_process: true, server_path: - 'node_modules/selenium-standalone/.selenium/selenium-server/3.7.1-server.jar', + 'node_modules/selenium-standalone/.selenium/selenium-server/3.8.1-server.jar', log_path: REPORTS_FOLDER, host: '127.0.0.1', port: 6666, cli_args: { 'webdriver.chrome.driver': - 'node_modules/selenium-standalone/.selenium/chromedriver/2.33-x64-chromedriver', + 'node_modules/selenium-standalone/.selenium/chromedriver/2.37-x64-chromedriver', 'webdriver.gecko.driver': - 'node_modules/selenium-standalone/.selenium/geckodriver/0.19.1-x64-geckodriver', + 'node_modules/selenium-standalone/.selenium/geckodriver/0.20.1-x64-geckodriver', }, }, test_settings: { diff --git a/package.json b/package.json index de37ff004..adee3da8d 100644 --- a/package.json +++ b/package.json @@ -157,8 +157,8 @@ "node-emoji": "^1.8.1", "node-fetch": "^1.7.2", "nodemailer": "^2.6.4", - "nunjucks": "^3.1.3", "npm-run-all": "^4.1.2", + "nunjucks": "^3.1.3", "parallel-webpack": "^2.2.0", "passport": "^0.4.0", "passport-jwt": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index 59b3958d6..a4cec7ec7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -649,12 +649,18 @@ async@^1.4.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.0.0, async@^2.1.2, async@^2.1.4, async@^2.4.1, async@~2.6.0: +async@^2.0.0, async@^2.1.2, async@^2.4.1, async@~2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" dependencies: lodash "^4.14.0" +async@^2.1.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" + dependencies: + lodash "^4.17.10" + async@~0.2.6: version "0.2.10" resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" @@ -690,7 +696,11 @@ aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" -aws4@^1.2.1, aws4@^1.6.0: +aws4@^1.2.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" + +aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" @@ -1413,10 +1423,11 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" bl@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e" + version "1.2.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" dependencies: - readable-stream "^2.0.5" + readable-stream "^2.3.5" + safe-buffer "^5.1.1" block-stream@*: version "0.0.9" @@ -1648,6 +1659,17 @@ bson@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.6.tgz#444db59ddd4c24f0cb063aabdc5c8c7b0ceca912" +buffer-alloc-unsafe@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-0.1.1.tgz#ffe1f67551dd055737de253337bfe853dfab1a6a" + +buffer-alloc@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.1.0.tgz#05514d33bf1656d3540c684f65b1202e90eca303" + dependencies: + buffer-alloc-unsafe "^0.1.0" + buffer-fill "^0.1.0" + buffer-crc32@^0.2.1, buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1656,6 +1678,10 @@ buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" +buffer-fill@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-0.1.1.tgz#76d825c4d6e50e06b7a31eb520c04d08cc235071" + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -2235,8 +2261,8 @@ colors@^1.1.2, colors@~1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" dependencies: delayed-stream "~1.0.0" @@ -2256,7 +2282,7 @@ commander@2.9.0: dependencies: graceful-readlink ">= 1.0.0" -commander@^2.11.0, commander@^2.9.0: +commander@^2.11.0: version "2.12.2" resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" @@ -2264,6 +2290,10 @@ commander@^2.14.1: version "2.14.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" +commander@^2.9.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -2627,6 +2657,16 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -4097,6 +4137,10 @@ from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + fs-extra@^0.24.0: version "0.24.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.24.0.tgz#d4e4342a96675cb7846633a6099249332b539952" @@ -5465,12 +5509,17 @@ is-ip@1.0.0: dependencies: ip-regex "^1.0.0" +is-my-ip-valid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" + is-my-json-valid@^2.12.4: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" + version "2.17.2" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c" dependencies: generate-function "^2.0.0" generate-object-property "^1.1.0" + is-my-ip-valid "^1.0.0" jsonpointer "^4.0.0" xtend "^4.0.0" @@ -6990,11 +7039,11 @@ lodash.values@^4.3.0: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" -lodash@^4.0.0, lodash@^4.1.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1: +lodash@^4.0.0, lodash@^4.1.0, lodash@^4.2.0, lodash@^4.2.1: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" -lodash@^4.17.10: +lodash@^4.14.0, lodash@^4.17.10, lodash@^4.17.4: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" @@ -7352,16 +7401,22 @@ miller-rabin@^4.0.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -"mime-db@>= 1.33.0 < 2": +"mime-db@>= 1.33.0 < 2", mime-db@~1.33.0: version "1.33.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" -mime-types@^2.1.10, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: +mime-types@^2.1.10, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: mime-db "~1.30.0" +mime-types@^2.1.12, mime-types@~2.1.7: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + dependencies: + mime-db "~1.33.0" + mime@1.4.1, mime@^1.3.4, mime@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" @@ -7702,6 +7757,10 @@ nib@~1.1.2: dependencies: stylus "0.54.5" +nice-try@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4" + nightwatch@^0.9.16: version "0.9.19" resolved "https://registry.yarnpkg.com/nightwatch/-/nightwatch-0.9.19.tgz#4bd9757273d30b845f04847a98b71be9bb7c4b3b" @@ -8549,7 +8608,7 @@ path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" -path-key@^2.0.0: +path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -9778,7 +9837,7 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.4, readable-stream@^2.1.5, readable-stream@^2.2.2: +"readable-stream@1 || 2", readable-stream@^2.0.4, readable-stream@^2.1.5, readable-stream@^2.2.2: version "2.3.5" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d" dependencies: @@ -9808,7 +9867,7 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@2, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.3.3: +readable-stream@2, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -9820,6 +9879,18 @@ readable-stream@2, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stre string_decoder "~1.0.3" util-deprecate "~1.0.1" +readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.3.0, readable-stream@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -10050,31 +10121,6 @@ request@2, request@^2.55.0, request@^2.74.0, request@^2.81.0, request@^2.83.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@2.79.0, request@~2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~2.0.6" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - qs "~6.3.0" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" - uuid "^3.0.0" - request@2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" @@ -10102,6 +10148,56 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" +request@2.87.0: + version "2.87.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + +request@~2.79.0: + version "2.79.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.11.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~2.0.6" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + qs "~6.3.0" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "~0.4.1" + uuid "^3.0.0" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -10262,11 +10358,11 @@ rxjs@^5.4.2: dependencies: symbol-observable "1.0.1" -safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" -safe-buffer@^5.1.2: +safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -10367,19 +10463,19 @@ select@^1.1.2: resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" selenium-standalone@^6.11.0: - version "6.12.0" - resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.12.0.tgz#789730db09a105f1cce12c6424d795d11c543bd4" + version "6.15.0" + resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-6.15.0.tgz#c8dc77bd45154afbda7509ec53dc599809f63ee4" dependencies: async "^2.1.4" commander "^2.9.0" - cross-spawn "^5.1.0" + cross-spawn "^6.0.0" debug "^3.0.0" lodash "^4.17.4" minimist "^1.2.0" mkdirp "^0.5.1" progress "2.0.0" - request "2.79.0" - tar-stream "1.5.2" + request "2.87.0" + tar-stream "1.6.1" urijs "^1.18.4" which "^1.2.12" yauzl "^2.5.0" @@ -10394,7 +10490,7 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", semver@^5.3.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" @@ -10838,8 +10934,8 @@ srcset@^1.0.0: number-is-nan "^1.0.0" sshpk@^1.7.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + version "1.14.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -10992,6 +11088,12 @@ string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + dependencies: + safe-buffer "~5.1.0" + stringify-object@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.2.2.tgz#9853052e5a88fb605a44cd27445aa257ad7ffbcd" @@ -11000,7 +11102,11 @@ stringify-object@^3.2.2: is-obj "^1.0.1" is-regexp "^1.0.0" -stringstream@~0.0.4, stringstream@~0.0.5: +stringstream@~0.0.4: + version "0.0.6" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" + +stringstream@~0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" @@ -11232,7 +11338,19 @@ tar-pack@^3.4.0: tar "^2.2.1" uid-number "^0.0.6" -tar-stream@1.5.2, tar-stream@^1.1.2: +tar-stream@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395" + dependencies: + bl "^1.0.0" + buffer-alloc "^1.1.0" + end-of-stream "^1.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.0" + xtend "^4.0.0" + +tar-stream@^1.1.2: version "1.5.2" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.2.tgz#fbc6c6e83c1a19d4cb48c7d96171fc248effc7bf" dependencies: @@ -11391,6 +11509,10 @@ to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" +to-buffer@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + to-capital-case@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/to-capital-case/-/to-capital-case-1.0.0.tgz#a57c5014fd5a37217cf05099ff8a421bbf9c9b7f" @@ -11473,12 +11595,18 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@>=2.3.3, tough-cookie@^2.2.0, tough-cookie@^2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3: +tough-cookie@>=2.3.3, tough-cookie@^2.2.0, tough-cookie@^2.3.3, tough-cookie@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" dependencies: punycode "^1.4.1" +tough-cookie@~2.3.0: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + dependencies: + punycode "^1.4.1" + tr46@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -11742,8 +11870,8 @@ upper-case@^1.1.1: resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" urijs@^1.18.4: - version "1.19.0" - resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.0.tgz#d8aa284d0e7469703a6988ad045c4cbfdf08ada0" + version "1.19.1" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.1.tgz#5b0ff530c0cbde8386f6342235ba5ca6e995d25a" urix@^0.1.0: version "0.1.0" @@ -12008,12 +12136,18 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" -which@1, which@^1.2.10, which@^1.2.12, which@^1.2.9, which@^1.3.0: +which@1, which@^1.2.10, which@^1.2.9, which@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" dependencies: isexe "^2.0.0" +which@^1.2.12: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" From b15a306c433beb44f518e209f6e2ca1310889439 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 28 May 2018 16:20:20 -0300 Subject: [PATCH 150/162] =?UTF-8?q?=C3=A7hanges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/coral-admin/src/routes/Moderation/graphql.js | 5 +++++ test/e2e/page_objects/popup.js | 4 +++- test/e2e/specs/05_banUser.js | 7 +++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/graphql.js b/client/coral-admin/src/routes/Moderation/graphql.js index 8bb6349d8..31d124d93 100644 --- a/client/coral-admin/src/routes/Moderation/graphql.js +++ b/client/coral-admin/src/routes/Moderation/graphql.js @@ -298,6 +298,11 @@ export function handleCommentChange( notificationShown = true; }; + console.log('root.me', root.me); + console.log('comment.status_history', comment.status_history); + + debugger; + Object.keys(queueConfig).forEach(queue => { // Comment should be placed in queue. if (nextQueues.indexOf(queue) >= 0) { diff --git a/test/e2e/page_objects/popup.js b/test/e2e/page_objects/popup.js index 824546e5b..cb35563f5 100644 --- a/test/e2e/page_objects/popup.js +++ b/test/e2e/page_objects/popup.js @@ -5,7 +5,9 @@ module.exports = { return this.waitForElementVisible('body'); }, login(user) { - return this.setValue('@emailInput', user.email) + return this.waitForElementVisible('@emailInput') + .setValue('@emailInput', user.email) + .waitForElementVisible('@passwordInput') .setValue('@passwordInput', user.password) .waitForElementVisible('@signIn') .waitForElementVisible('@loginButton') diff --git a/test/e2e/specs/05_banUser.js b/test/e2e/specs/05_banUser.js index 51055a180..6a5dfb12c 100644 --- a/test/e2e/specs/05_banUser.js +++ b/test/e2e/specs/05_banUser.js @@ -86,7 +86,7 @@ module.exports = { .waitForElementVisible('@firstRow') .waitForElementVisible('@dropdownStatus') .click('@dropdownStatus') - .waitForElementVisible('@dropdownStatusActive') + .waitForElementVisible('@optionRemoveBan') .click('@optionRemoveBan'); }, 'admin logs out 2': client => { @@ -154,8 +154,7 @@ module.exports = { const moderate = client.page.admin().section.moderate; moderate.click('@firstCommentApprove'); - - // TODO: check why this fails. - // .waitForElementNotPresent('@firstComment'); + client.pause(1000000); + moderate.waitForElementNotPresent('@firstComment'); }, }; From 3a84da94958d70e2df2e8fb6c30cc16310e35099 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 29 May 2018 16:42:04 +0200 Subject: [PATCH 151/162] Activate Websocket and add browser console logging --- bin/cli-assets | 2 +- bin/cli-token | 2 +- bin/cli-users | 2 +- .../src/routes/Moderation/graphql.js | 5 --- package.json | 2 +- scripts/e2e.js | 4 +- .../{utils => helpers}/SortedWindowHandler.js | 0 test/e2e/helpers/printBrowserLog.js | 37 +++++++++++++++++++ test/e2e/page_objects/embedStream.js | 2 +- test/e2e/specs/01_install.js | 5 ++- test/e2e/specs/02_admin.js | 5 ++- test/e2e/specs/03_embedStream.js | 4 +- test/e2e/specs/04_userStatus.js | 5 ++- test/e2e/specs/05_banUser.js | 10 +++-- test/e2e/specs/06_suspendUser.js | 4 +- yarn.lock | 17 ++++++--- 16 files changed, 80 insertions(+), 26 deletions(-) rename test/e2e/{utils => helpers}/SortedWindowHandler.js (100%) create mode 100644 test/e2e/helpers/printBrowserLog.js diff --git a/bin/cli-assets b/bin/cli-assets index 5c1afb741..8242a02d6 100755 --- a/bin/cli-assets +++ b/bin/cli-assets @@ -7,7 +7,7 @@ const util = require('./util'); const program = require('commander'); const parseDuration = require('ms'); -const Table = require('cli-table'); +const Table = require('cli-table2'); const AssetModel = require('../models/asset'); const CommentModel = require('../models/comment'); const AssetsService = require('../services/assets'); diff --git a/bin/cli-token b/bin/cli-token index b131728e6..0d4dc4e10 100755 --- a/bin/cli-token +++ b/bin/cli-token @@ -8,7 +8,7 @@ const util = require('./util'); const program = require('commander'); const mongoose = require('../services/mongoose'); const TokensService = require('../services/tokens'); -const Table = require('cli-table'); +const Table = require('cli-table2'); // Register the shutdown criteria. util.onshutdown([() => mongoose.disconnect()]); diff --git a/bin/cli-users b/bin/cli-users index bf34cce97..743119a0a 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -8,7 +8,7 @@ const util = require('./util'); const program = require('commander'); const inquirer = require('inquirer'); const { stripIndent } = require('common-tags'); -const Table = require('cli-table'); +const Table = require('cli-table2'); // Make things colorful! require('colors'); diff --git a/client/coral-admin/src/routes/Moderation/graphql.js b/client/coral-admin/src/routes/Moderation/graphql.js index 31d124d93..8bb6349d8 100644 --- a/client/coral-admin/src/routes/Moderation/graphql.js +++ b/client/coral-admin/src/routes/Moderation/graphql.js @@ -298,11 +298,6 @@ export function handleCommentChange( notificationShown = true; }; - console.log('root.me', root.me); - console.log('comment.status_history', comment.status_history); - - debugger; - Object.keys(queueConfig).forEach(queue => { // Comment should be placed in queue. if (nextQueues.indexOf(queue) >= 0) { diff --git a/package.json b/package.json index adee3da8d..a313c1ec0 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "brotli-webpack-plugin": "^0.5.0", "bunyan": "^1.8.12", "bunyan-debug-stream": "^1.0.8", - "cli-table": "^0.3.1", + "cli-table2": "^0.2.0", "clipboard": "^1.7.1", "colors": "^1.1.2", "commander": "^2.11.0", diff --git a/scripts/e2e.js b/scripts/e2e.js index 37ada228a..d86b3ba47 100755 --- a/scripts/e2e.js +++ b/scripts/e2e.js @@ -5,7 +5,7 @@ process.env['NODE_ENV'] = 'test'; const browserstack = require('browserstack-local'); const { onshutdown, shutdown } = require('../bin/util'); const program = require('commander'); -const Table = require('cli-table'); +const Table = require('cli-table2'); const serve = require('../serve'); const childProcess = require('child_process'); const uuid = require('uuid').v4; @@ -181,7 +181,7 @@ class E2E { console.log('Dropping test database'); await mongoose.connection.dropDatabase(); console.log('Starting the server'); - await serve(); + await serve({ websockets: true }); if (this.browserstackEnabled) { browserstack.localIdentifier = this.localIdentifier; diff --git a/test/e2e/utils/SortedWindowHandler.js b/test/e2e/helpers/SortedWindowHandler.js similarity index 100% rename from test/e2e/utils/SortedWindowHandler.js rename to test/e2e/helpers/SortedWindowHandler.js diff --git a/test/e2e/helpers/printBrowserLog.js b/test/e2e/helpers/printBrowserLog.js new file mode 100644 index 000000000..b40fb4507 --- /dev/null +++ b/test/e2e/helpers/printBrowserLog.js @@ -0,0 +1,37 @@ +// Make things colorful! +require('colors'); + +const Table = require('cli-table2'); + +function getLevelColoried(level) { + switch (level) { + case 'WARNING': + return level.yellow; + case 'SEVERE': + return level.red; + default: + return level; + } +} + +function printBrowserLog(client) { + return new Promise(resolve => { + client.getLog('browser', entries => { + if (entries.length) { + let table = new Table({ + head: ['Level'.cyan, 'Message'.cyan], + wordWrap: true, + colWidths: [10, 80], + }); + + for (let entry of entries) { + table.push([getLevelColoried(entry.level), entry.message]); + } + console.log(table.toString()); + } + resolve(); + }); + }); +} + +module.exports = printBrowserLog; diff --git a/test/e2e/page_objects/embedStream.js b/test/e2e/page_objects/embedStream.js index de3a29973..42dedefba 100644 --- a/test/e2e/page_objects/embedStream.js +++ b/test/e2e/page_objects/embedStream.js @@ -1,5 +1,5 @@ const iframeId = 'coralStreamEmbed_iframe'; -const SortedWindowHandler = require('../utils/SortedWindowHandler'); +const SortedWindowHandler = require('../helpers/SortedWindowHandler'); module.exports = { commands: [ diff --git a/test/e2e/specs/01_install.js b/test/e2e/specs/01_install.js index 75e6a60eb..91f08f7af 100644 --- a/test/e2e/specs/01_install.js +++ b/test/e2e/specs/01_install.js @@ -1,3 +1,5 @@ +const printBrowserLog = require('../helpers/printBrowserLog'); + module.exports = { '@tags': ['install'], @@ -6,7 +8,8 @@ module.exports = { client.resizeWindow(1600, 1200); }, - afterEach: (client, done) => { + afterEach: async (client, done) => { + await printBrowserLog(client); if (client.currentTest.results.failed) { throw new Error('Test Case failed, skipping all the rest'); } diff --git a/test/e2e/specs/02_admin.js b/test/e2e/specs/02_admin.js index 07f5b574c..844e653fe 100644 --- a/test/e2e/specs/02_admin.js +++ b/test/e2e/specs/02_admin.js @@ -1,3 +1,5 @@ +const printBrowserLog = require('../helpers/printBrowserLog'); + module.exports = { '@tags': ['admin', 'login'], @@ -6,7 +8,8 @@ module.exports = { client.resizeWindow(1024, 800); }, - afterEach: (client, done) => { + afterEach: async (client, done) => { + await printBrowserLog(client); if (client.currentTest.results.failed) { throw new Error('Test Case failed, skipping all the rest'); } diff --git a/test/e2e/specs/03_embedStream.js b/test/e2e/specs/03_embedStream.js index 9eca7a736..d98c0f9a7 100644 --- a/test/e2e/specs/03_embedStream.js +++ b/test/e2e/specs/03_embedStream.js @@ -1,3 +1,4 @@ +const printBrowserLog = require('../helpers/printBrowserLog'); const commentBody = 'Embed Stream Test'; module.exports = { @@ -8,7 +9,8 @@ module.exports = { client.resizeWindow(1600, 1200); }, - afterEach: (client, done) => { + afterEach: async (client, done) => { + await printBrowserLog(client); if (client.currentTest.results.failed) { throw new Error('Test Case failed, skipping all the rest'); } diff --git a/test/e2e/specs/04_userStatus.js b/test/e2e/specs/04_userStatus.js index 8d659221d..c54aa308c 100644 --- a/test/e2e/specs/04_userStatus.js +++ b/test/e2e/specs/04_userStatus.js @@ -1,10 +1,13 @@ +const printBrowserLog = require('../helpers/printBrowserLog'); + module.exports = { before: client => { client.setWindowPosition(0, 0); client.resizeWindow(1600, 1200); }, - afterEach: (client, done) => { + afterEach: async (client, done) => { + await printBrowserLog(client); if (client.currentTest.results.failed) { throw new Error('Test Case failed, skipping all the rest'); } diff --git a/test/e2e/specs/05_banUser.js b/test/e2e/specs/05_banUser.js index 6a5dfb12c..3f6a33cf9 100644 --- a/test/e2e/specs/05_banUser.js +++ b/test/e2e/specs/05_banUser.js @@ -1,3 +1,4 @@ +const printBrowserLog = require('../helpers/printBrowserLog'); const commentBody = 'Ban User Test'; module.exports = { @@ -6,7 +7,8 @@ module.exports = { client.resizeWindow(1600, 1200); }, - afterEach: (client, done) => { + afterEach: async (client, done) => { + await printBrowserLog(client); if (client.currentTest.results.failed) { throw new Error('Test Case failed, skipping all the rest'); } @@ -153,8 +155,8 @@ module.exports = { 'approve comment to restore karma': client => { const moderate = client.page.admin().section.moderate; - moderate.click('@firstCommentApprove'); - client.pause(1000000); - moderate.waitForElementNotPresent('@firstComment'); + moderate + .click('@firstCommentApprove') + .waitForElementNotPresent('@firstComment'); }, }; diff --git a/test/e2e/specs/06_suspendUser.js b/test/e2e/specs/06_suspendUser.js index e02f21db9..1febe820c 100644 --- a/test/e2e/specs/06_suspendUser.js +++ b/test/e2e/specs/06_suspendUser.js @@ -1,3 +1,4 @@ +const printBrowserLog = require('../helpers/printBrowserLog'); const commentBody = 'Suspend User Test'; module.exports = { @@ -6,7 +7,8 @@ module.exports = { client.resizeWindow(1600, 1200); }, - afterEach: (client, done) => { + afterEach: async (client, done) => { + await printBrowserLog(client); if (client.currentTest.results.failed) { throw new Error('Test Case failed, skipping all the rest'); } diff --git a/yarn.lock b/yarn.lock index a4cec7ec7..0f6606433 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2114,11 +2114,14 @@ cli-spinners@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" -cli-table@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" +cli-table2@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/cli-table2/-/cli-table2-0.2.0.tgz#2d1ef7f218a0e786e214540562d4bd177fe32d97" dependencies: - colors "1.0.3" + lodash "^3.10.1" + string-width "^1.0.1" + optionalDependencies: + colors "^1.1.2" cli-truncate@^0.2.1: version "0.2.1" @@ -2248,7 +2251,7 @@ colors@0.5.x: version "0.5.1" resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774" -colors@1.0.3, colors@1.0.x: +colors@1.0.x: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" @@ -7039,6 +7042,10 @@ lodash.values@^4.3.0: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" +lodash@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + lodash@^4.0.0, lodash@^4.1.0, lodash@^4.2.0, lodash@^4.2.1: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" From 576820f1f8cd30ce82516a8419eaa767dea78f7b Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 29 May 2018 16:50:11 +0200 Subject: [PATCH 152/162] Fix warnings --- client/coral-framework/components/TalkProvider.js | 2 +- .../talk-plugin-auth/client/login/components/SignUp.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/coral-framework/components/TalkProvider.js b/client/coral-framework/components/TalkProvider.js index 9caba7fb8..804e57862 100644 --- a/client/coral-framework/components/TalkProvider.js +++ b/client/coral-framework/components/TalkProvider.js @@ -1,5 +1,5 @@ import React from 'react'; -const PropTypes = require('prop-types'); +import PropTypes from 'prop-types'; import { ApolloProvider } from 'react-apollo'; class TalkProvider extends React.Component { diff --git a/plugins/talk-plugin-auth/client/login/components/SignUp.js b/plugins/talk-plugin-auth/client/login/components/SignUp.js index 96dadcd67..878b53b5d 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignUp.js +++ b/plugins/talk-plugin-auth/client/login/components/SignUp.js @@ -75,7 +75,7 @@ class SignUp extends React.Component { showErrors={!!emailError} errorMsg={emailError} onChange={this.handleEmailChange} - autocomplete="off" + autoComplete="off" /> {passwordError && ( @@ -116,7 +116,7 @@ class SignUp extends React.Component { errorMsg={passwordRepeatError} onChange={this.handlePasswordRepeatChange} minLength="8" - autocomplete="off" + autoComplete="off" /> Date: Tue, 29 May 2018 16:52:07 +0200 Subject: [PATCH 153/162] Typo --- test/e2e/helpers/printBrowserLog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/helpers/printBrowserLog.js b/test/e2e/helpers/printBrowserLog.js index b40fb4507..a9b8e50f4 100644 --- a/test/e2e/helpers/printBrowserLog.js +++ b/test/e2e/helpers/printBrowserLog.js @@ -3,7 +3,7 @@ require('colors'); const Table = require('cli-table2'); -function getLevelColoried(level) { +function getLevelColorized(level) { switch (level) { case 'WARNING': return level.yellow; @@ -25,7 +25,7 @@ function printBrowserLog(client) { }); for (let entry of entries) { - table.push([getLevelColoried(entry.level), entry.message]); + table.push([getLevelColorized(entry.level), entry.message]); } console.log(table.toString()); } From 5c7345313e0bcb9c106a85d1b840d98fcd2649b4 Mon Sep 17 00:00:00 2001 From: Leandro Date: Wed, 30 May 2018 09:25:10 +0200 Subject: [PATCH 154/162] remove unused method --- client/coral-admin/src/components/BanUserDialog.js | 10 ++++++++-- client/coral-admin/src/containers/BanUserDialog.js | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/components/BanUserDialog.js b/client/coral-admin/src/components/BanUserDialog.js index 430ee2fe4..a5a2d2a5f 100644 --- a/client/coral-admin/src/components/BanUserDialog.js +++ b/client/coral-admin/src/components/BanUserDialog.js @@ -30,6 +30,12 @@ class BanUserDialog extends React.Component { }); }; + handlePerform = () => { + this.props.onPerform({ + message: this.state.message, + }); + }; + renderStep0() { const { onCancel, username, info } = this.props; @@ -63,7 +69,7 @@ class BanUserDialog extends React.Component { } renderStep1() { - const { onCancel, onPerform } = this.props; + const { onCancel } = this.props; const { message } = this.state; return ( @@ -95,7 +101,7 @@ class BanUserDialog extends React.Component {