Compare commits

...
66 Commits
Author SHA1 Message Date
Kim Gardner dd0601a801 Merge pull request #2135 from coralproject/concurrency
Concurrency
2018-12-19 20:53:30 +00:00
Wyatt Johnson 9f7d485871 Update package.json 2018-12-19 20:39:25 +00:00
Wyatt Johnson ec816636e6 feat: added concurrency features 2018-12-19 12:40:08 -07:00
Kim Gardner 08f579225b Bump version 4.6.11 (#2130) 2018-12-17 16:34:43 +00:00
Kim Gardner 46987b6303 Merge pull request #2122 from oafx/oafx/qbb-localization
Add localization for QuestionBoxBuilder
2018-12-17 15:09:31 +00:00
Kim Gardner c2d07ec79f Merge branch 'master' into oafx/qbb-localization 2018-12-17 14:43:32 +00:00
Kim Gardner a98f5ef980 Merge pull request #2128 from oafx/oafx/fix/moderate-commentcontainer-wordbreak
fix minor ui/css bug in moderation/comment component caused by long links
2018-12-17 14:42:54 +00:00
Christian Ruttorf d6515e23f8 fix ui/css bug in moderation/comment component caused by long words/links 2018-12-16 08:48:21 +01:00
immber a3db05845d Merge pull request #2126 from coralproject/docs-update
Add Documentation Redirect - asset management to cms integration
2018-12-14 13:06:59 -08:00
Wyatt Johnson c8fc419cec Update _redirects 2018-12-14 20:46:57 +00:00
immber 677c8a0828 Merge branch 'master' into docs-update 2018-12-14 12:06:22 -08:00
immber 08146fd979 added redirect 2018-12-14 12:04:28 -08:00
Kim Gardner ab92fe3fe8 Merge branch 'master' into oafx/qbb-localization 2018-12-14 18:18:33 +00:00
immber 95435f0793 Docs update (#2120)
* merging in typo fixes from docs (rtd) branch

* revert generateIntrospectionResult.json

* revert git ignore

* added asset mgmt to prelaunch checklist

* updated cusotm plugin docker onbuild instructions

* add google to auth options

* add google to auth options

* added yarn watch to from source

* added dev links to quickstart

* typo on following

* fixed typos

* fixing links

* added lazy render and updated lazy load

* added version note

* renamed asset management to cms-integration

* added link to asset scraping
2018-12-14 17:05:52 +00:00
immber 7eed540671 added link to asset scraping 2018-12-13 13:10:14 -08:00
immber 374951bd33 renamed asset management to cms-integration 2018-12-13 13:04:01 -08:00
immber 42a694f56e Merge branch 'master' of https://github.com/coralproject/talk into docs-update 2018-12-13 12:52:18 -08:00
immber 1c997aea31 Merge branch 'docs-update' of https://github.com/coralproject/talk into docs-update 2018-12-13 12:51:58 -08:00
immber 44830f6cb1 added version note 2018-12-13 12:51:50 -08:00
Wyatt Johnson 22d263c6d2 Embed Optimizations (#2121)
* feat: refactored bridge

- Cleaned up implementation of bridge
- Removed unneeded babel-polyfill and webpack globals

* fix: fixed up some static headers

- bumped version

* fix: moved intersection observer out of chunk

* fix: fixed misplaced "!"
2018-12-13 20:41:40 +00:00
Christian Ruttorf 78ba82cc05 add localization for QuestionBoxBuilder title, add PropTypes for QuestionBoxBuilder 2018-12-13 17:42:50 +01:00
immber a6b9cc57ac Merge branch 'master' into docs-update 2018-12-12 12:46:04 -08:00
immber cd9cb56844 added lazy render and updated lazy load 2018-12-12 12:44:19 -08:00
Wyatt Johnson d331bdee18 fix: fixed misplaced "!" 2018-12-12 12:59:54 -07:00
Wyatt Johnson d6f6fbcd9c CDN + Observabilities fixes (#2119)
* feat: added heroku fixes

- Support pulling the request id from the request
  headers
- Add proper cache headers to static files

* Update package.json
2018-12-12 18:45:50 +00:00
immber 1ed5df71da fix bug in cli-setup (#2116)
* fix bug in cli-setup

* fix cli setup bug

* fix: fixed password bug
2018-12-12 00:20:16 +00:00
immber 8b1b61f695 pulling changes from upstream 2018-12-10 14:26:58 -08:00
Kim Gardner f1a3a5ca28 Lazy Load Fix (#2110)
* Bump version 4.6.8

* fix: added JS fix for static URL's
2018-12-06 22:45:52 +00:00
Wyatt Johnson 8ef44dc6f5 fix: dep order 2018-12-06 14:41:39 -07:00
Kiwi 362f29f77e Support lazy rendering (#2109)
* feat: support lazy rendering

* fix: added new env var to onbuild args

* review: apply suggestions
2018-12-06 21:24:31 +00:00
immber 08d342ea2e Docs update (#2105)
* merging in typo fixes from docs (rtd) branch

* revert generateIntrospectionResult.json

* revert git ignore

* added asset mgmt to prelaunch checklist

* updated cusotm plugin docker onbuild instructions

* add google to auth options

* add google to auth options

* added yarn watch to from source

* added dev links to quickstart

* typo on following

* fixed typos

* fixing links
2018-12-03 19:47:46 +00:00
immber 99777d6b49 fixing links 2018-12-03 11:07:40 -08:00
immber 6f6da1fa25 merging changes from master 2018-12-03 09:34:25 -08:00
immber 2fac304163 fixed typos 2018-12-03 09:33:03 -08:00
immber 5ca7c092db Merge branch 'master' into docs-update 2018-11-30 18:05:35 -08:00
immber cfae6b5374 typo on following 2018-11-30 18:04:20 -08:00
immber 3668a2cffa added dev links to quickstart 2018-11-30 17:56:20 -08:00
immber 92681f52aa added yarn watch to from source 2018-11-30 17:39:02 -08:00
immber 847ded5bb4 add google to auth options 2018-11-30 17:19:27 -08:00
immber 76c959f790 add google to auth options 2018-11-30 17:11:42 -08:00
immber 4659392ad3 updated cusotm plugin docker onbuild instructions 2018-11-30 15:10:00 -08:00
immber e9cec73966 added asset mgmt to prelaunch checklist 2018-11-30 14:44:35 -08:00
immber bac0ee9c3b revert git ignore 2018-11-28 14:00:07 -08:00
immber 5ae293af89 revert generateIntrospectionResult.json 2018-11-28 13:58:56 -08:00
immber cce464b82a merging in typo fixes from docs (rtd) branch 2018-11-28 13:56:31 -08:00
Kim Gardner 5d32c4ec03 Merge pull request #2097 from richardvenneman/nl-talk-plugins
Add/fix Dutch (nl_NL) translations for Talk plugins
2018-11-28 18:01:22 +00:00
Kim Gardner 50d4e2bb9b Merge branch 'master' into nl-talk-plugins 2018-11-27 21:08:22 +00:00
Richard Venneman debdd3eae4 Fix nl typo 2018-11-27 16:56:56 -04:00
Richard Venneman 286d9baddf Update Dutch (nl_NL) translations for talk-plugin-love 2018-11-27 16:56:49 -04:00
Richard Venneman 9c06a63afd Add Dutch (nl_NL) translations for talk notifications plugins 2018-11-27 16:55:32 -04:00
Richard Venneman f38e72c4df Add Dutch (nl_NL) translations for talk-plugin-google-auth 2018-11-27 16:32:36 -04:00
Richard Venneman e2fac4a2dc Fix and update talk-plugin-auth Dutch (nl_NL) translations 2018-11-27 16:30:42 -04:00
Richard Venneman ba0513fd6c Fix talk-plugin-member-since Dutch (nl_NL) translation
Member != User
2018-11-27 16:22:00 -04:00
Richard Venneman 8e29f0094c Add Dutch (nl_NL) translations for talk-plugin-facebook-auth 2018-11-27 16:20:39 -04:00
Kim Gardner f60c7e3ca6 Version 4.6.7 (#2096) 2018-11-27 20:20:00 +00:00
Richard Venneman b3c034d89d Add Dutch (nl_NL) translation for talk-plugin-local-auth 2018-11-27 16:16:34 -04:00
Kim Gardner be2657eb30 Merge pull request #2095 from richardvenneman/update-nl-translations
Update Dutch (nl_NL) translations
2018-11-27 19:39:16 +00:00
Richard Venneman fd32d2aa24 Merge branch 'master' into update-nl-translations 2018-11-27 15:29:05 -04:00
Richard Venneman 809b39ae2a Update Dutch (nl_NL) translations 2018-11-27 15:25:14 -04:00
Kim Gardner 6197981480 Merge pull request #2092 from coralproject/sp-trans-4
Fixed Sp translations
2018-11-27 18:39:51 +00:00
Kim Gardner eb58dd52f8 Fixed Sp translations 2018-11-26 15:25:44 +00:00
Kim Gardner f5c011f44b Merge pull request #2077 from coralproject/ie11-fixes
Fix IE11 issues in Admin
2018-11-16 12:59:04 -05:00
Chi Vinh Le 396ccbd6c2 fix: workaround flexbox bug in IE11 2018-11-15 23:51:28 +01:00
Chi Vinh Le 4fd61b3731 fix: polyfill missing forEach for NodeLists in IE11 2018-11-15 23:51:14 +01:00
Kim Gardner cbd795dc95 Merge pull request #2073 from coralproject/sp-trans-3
Add Spanish translations for blank comment history
2018-11-09 16:18:02 -05:00
Kim Gardner 65a6b484a9 Add Spanish translations for blank comment history 2018-11-09 10:00:53 -05:00
49 changed files with 1080 additions and 502 deletions
+3 -3
View File
@@ -41,9 +41,9 @@ integration_job: &integration_job
- attach_workspace:
at: ~/coralproject/talk
- <<: *create_indexes
- run:
name: Setup the database with defaults
command: ./bin/cli setup --defaults
# - run:
# name: Setup the database with defaults
# command: ./bin/cli setup --defaults
- run:
name: Run the integration tests
command: bash .circleci/e2e.sh
+1
View File
@@ -11,6 +11,7 @@ ONBUILD ARG TALK_DEFAULT_LANG=en
ONBUILD ARG TALK_WHITELISTED_LANGUAGES
ONBUILD ARG TALK_PLUGINS_JSON
ONBUILD ARG TALK_WEBPACK_SOURCE_MAP
ONBUILD ARG TALK_DEFAULT_LAZY_RENDER
# Bundle app source
ONBUILD COPY . /usr/src/app
+17 -5
View File
@@ -1,8 +1,10 @@
#!/usr/bin/env node
const throng = require('throng');
const { CONCURRENCY } = require('../config');
const { logger } = require('../services/logging');
const util = require('./util');
const program = require('commander');
const serve = require('../serve');
//==============================================================================
// Setting up the program command line arguments.
@@ -22,8 +24,18 @@ program
)
.parse(process.argv);
// Start serving.
serve(program).catch(err => {
console.error(err);
util.shutdown(1);
throng({
workers: CONCURRENCY,
start: i => {
logger.info({ workerID: i }, 'started worker');
// Load in the serve.
const serve = require('../serve');
// Start serving.
serve(program).catch(err => {
console.error(err);
util.shutdown(1);
});
},
});
+37 -31
View File
@@ -45,11 +45,10 @@ const performSetup = async () => {
} catch (err) {
// If the error is `not init`, then we're good, otherwise, it's something
// else.
if (err instanceof ErrSettingsNotInit) {
if (!err instanceof ErrSettingsNotInit) {
throw err;
return;
}
throw err;
}
if (program.defaults) {
@@ -138,7 +137,7 @@ const performSetup = async () => {
console.log("\nWe'll ask you some questions about your first admin user.\n");
let user = await inquirer.prompt([
let { username, email } = await inquirer.prompt([
{
type: 'input',
name: 'username',
@@ -161,39 +160,46 @@ const performSetup = async () => {
return 'Email is required';
},
},
{
name: 'password',
message: 'Password',
type: 'password',
filter: password => {
return UsersService.isValidPassword(password).catch(err => {
throw err.message;
});
},
},
{
name: 'confirmPassword',
message: 'Confirm Password',
type: 'password',
filter: (confirmPassword, { password }) => {
if (password !== confirmPassword) {
return Promise.reject(new Error('Passwords do not match'));
}
return UsersService.isValidPassword(confirmPassword).catch(err => {
throw err.message;
});
},
},
]);
let password = '';
while (!password) {
answers = await inquirer.prompt([
{
name: 'password',
message: 'Password',
type: 'password',
filter: password => {
try {
UsersService.isValidPassword(password);
} catch (err) {
throw err.message;
}
return password;
},
},
{
name: 'confirmPassword',
message: 'Confirm Password',
type: 'password',
},
]);
if (answers.password !== answers.confirmPassword) {
console.error('Passwords do not match');
} else {
password = answers.password;
}
}
const ctx = Context.forSystem();
let { user: newUser } = await SetupService.setup(ctx, {
settings: settings.toObject(),
user: {
email: user.email,
username: user.username,
password: user.password,
email,
username,
password,
},
});
+5
View File
@@ -14,6 +14,11 @@ import { hideShortcutsNote } from './actions/moderation';
smoothscroll.polyfill();
if (!NodeList.prototype.forEach) {
// Polyfill IE11 missing forEach in NodeList.
NodeList.prototype.forEach = Array.prototype.forEach;
}
function init({ store, localStorage }) {
const shouldHide = localStorage.getItem('coral:shortcutsNote') === 'hide';
if (shouldHide) {
@@ -62,13 +62,19 @@
font-weight: 300;
margin-bottom: 8px;
overflow-wrap: break-word;
word-break:break-word;
}
.body {
margin-top: 0px;
flex: 1;
color: black;
max-width: 500px;
/*
IE11 fix Next line was supposed to be:
max-width: 500px;
*/
padding-right: 20px;
/** IE11 fix end **/
font-weight: 300;
font-size: 16px;
overflow-wrap: break-word;
@@ -1,6 +1,7 @@
import React from 'react';
import QuestionBox from '../../../components/QuestionBox';
import DefaultQuestionBoxIcon from '../../../components/DefaultQuestionBoxIcon';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './QuestionBoxBuilder.css';
import { Icon } from 'coral-ui';
@@ -12,6 +13,7 @@ const icons = [{ default: DefaultIcon }, 'forum', 'build', 'format_quote'];
class QuestionBoxBuilder extends React.Component {
render() {
const {
title,
questionBoxIcon,
questionBoxContent,
onContentChange,
@@ -20,7 +22,7 @@ class QuestionBoxBuilder extends React.Component {
return (
<div className={styles.root}>
<h4>Include an Icon</h4>
<h4>{title}</h4>
<ul className={styles.iconList}>
{icons.map(item => {
@@ -53,4 +55,12 @@ class QuestionBoxBuilder extends React.Component {
}
}
QuestionBoxBuilder.propTypes = {
title: PropTypes.string,
questionBoxIcon: PropTypes.string,
questionBoxContent: PropTypes.string,
onContentChange: PropTypes.func,
onIconChange: PropTypes.func,
};
export default QuestionBoxBuilder;
@@ -69,6 +69,7 @@ class Settings extends React.Component {
{questionBoxEnable && (
<div className={styles.questionBoxContainer}>
<QuestionBoxBuilder
title={t('configure.include_an_icon')}
questionBoxIcon={questionBoxIcon}
questionBoxContent={questionBoxContent}
onIconChange={onQuestionBoxIconChange}
+324
View File
@@ -0,0 +1,324 @@
import queryString from 'querystringify';
import URLSearchParams from '@ungap/url-search-params';
import pym from 'pym.js';
import EventEmitter from 'eventemitter2';
import { buildUrl } from 'coral-framework/utils/url';
import SnackBar from './SnackBar';
import onIntersect from './onIntersect';
import {
createStorage,
connectStorageToPym,
} from 'coral-framework/services/storage';
// Rebuild the origin if it isn't defined. This is our poor-mans polyfill
// for the location APIs.
if (!window.location.origin) {
window.location.origin = `${window.location.protocol}//${
window.location.hostname
}${window.location.port ? `:${window.location.port}` : ''}`;
}
const NOTIFICATION_OFFSET = 200;
// Ensure there is a trailing slash.
function ensureEndSlash(p) {
return p.match(/\/$/) ? p : `${p}/`;
}
// Build the URL to load in the pym iframe.
function buildStreamIframeUrl(talkBaseUrl, query) {
let url = talkBaseUrl + 'embed/stream?';
url += queryString.stringify(query);
return url;
}
// detectAssetURL will try to grab the canonical url from the head, if it isn't
// available, source the url from the current one.
function detectAssetURL() {
try {
// Try to get the url from the canonical tag on the page.
return document.querySelector('link[rel="canonical"]').href;
} catch (e) {
window.console.warn(
'This page does not include a canonical link tag. Talk has inferred this asset_url from the window object. Query params have been stripped, which may cause a single thread to be present across multiple pages.'
);
return window.location.origin + window.location.pathname;
}
}
function buildQuery({ asset_id, asset_url }) {
// Compose the query to send down to the Talk API so it knows what to load.
const query = {};
// Parse the url parameters to extract some of the information.
const search = new URLSearchParams(window.location.search);
// Pull the Comment ID out of the query string.
const commentID = search.get('commentId') || search.get('commentID');
if (commentID) {
query.comment_id = commentID;
}
// Insert the asset_id into the query.
if (asset_id) {
query.asset_id = asset_id;
}
// If the asset_url is defined, use it, otherwise, detect it.
if (asset_url) {
query.asset_url = asset_url;
} else {
query.asset_url = detectAssetURL();
}
return query;
}
// Get dimensions of viewport.
function viewportDimensions() {
let target = window;
let prefix = 'inner';
if (!('innerWidth' in window)) {
prefix = 'client';
target = document.documentElement || document.body;
}
return {
width: target[`${prefix}Width`],
height: target[`${prefix}Height`],
};
}
export default class Bridge {
constructor(
element,
{
// Pull out the URLs used to setup Talk.
talk: talkBaseUrl,
talkStaticUrl = talkBaseUrl,
// Default the following to null.
events = null,
snackBarStyles = null,
onAuthChanged = null,
// Determine if we're in lazy mode or not. By default, the build argument
// will determine the lazy render status. This default is primarily used
// when the embed code cannot be changed, but control of the Talk serving
// domain is available.
lazy = process.env.TALK_DEFAULT_LAZY_RENDER === 'TRUE',
// Any additional options are extracted to be sent to the embed via the
// pym bridge.
...opts
}
) {
this.pym = null;
this.element = element;
this.opts = opts;
this.query = buildQuery(this.opts);
this.emitter = new EventEmitter({ wildcard: true });
this.snackBar = new SnackBar(snackBarStyles || {});
this.onAuthChanged = onAuthChanged;
this.talkBaseUrl = ensureEndSlash(talkBaseUrl);
this.talkStaticUrl = ensureEndSlash(talkStaticUrl);
this.lazy = lazy;
// Store queued operations in a queue that can be processed once the stream
// is rendered.
this.queued = [];
// Attach to the events emitted by the pym parent.
if (events) {
events(this.emitter);
}
// Start the embed loading process.
if (this.lazy) {
// When the dom element containing the talk embed container is in view,
// render the stream with force turned on so that it skips this portion.
onIntersect(this.element, () => this.load());
} else {
// We aren't being lazy, load it now!
this.load();
}
}
ensureRendered() {
// Check to see if the pym bridge is created, and the embed is loaded.
if (this.pym === null) {
throw new Error('Stream Embed must be rendered first');
}
}
queueWhenRendered(callback) {
// Check to see if the queue is alive, if it isn't, run the callback now,
// otherwise, push the callback to be processed when the stream has loaded.
if (this.queued !== null) {
this.queued.push(callback);
} else {
callback();
}
}
setupPym() {
const url = buildStreamIframeUrl(this.talkBaseUrl, this.query);
this.pym = new pym.Parent(this.element.id, url, {
title: this.opts.title,
id: `${this.element.id}_iframe`,
name: `${this.element.id}_iframe`,
});
// NOTE: Workaround for iOS Safari which ignores `width` but respects `min-width` value.
this.pym.el.firstChild.style.width = '1px';
this.pym.el.firstChild.style.minWidth = '100%';
// Resize parent iframe height when child height changes
let cachedHeight;
this.pym.onMessage('height', height => {
if (height !== cachedHeight) {
this.pym.el.firstChild.style.height = `${height}px`;
cachedHeight = height;
}
});
// Send the config back over the pym bridge if requested.
this.pym.onMessage('getConfig', () => {
this.pym.sendMessage('config', JSON.stringify(this.opts));
});
// If the auth changes, and someone is listening for it, then re-emit it.
if (this.onAuthChanged) {
this.pym.onMessage('coral-auth-changed', message => {
this.onAuthChanged(message ? JSON.parse(message) : null);
});
}
// Remove the permalink comment id from the search.
this.pym.onMessage('coral-view-all-comments', () => {
const query = queryString.parse(location.search);
// Remove the commentId/commentID url param.
delete query.commentId;
delete query.commentID;
// Rebuild the search field without the commentId in it.
const search = queryString.stringify(query);
// Change the url.
window.history.replaceState(
{},
document.title,
buildUrl({ ...location, search })
);
});
// Remove the permalink comment id from the hash.
this.pym.onMessage('coral-view-comment', id => {
const search = queryString.stringify({
...queryString.parse(location.search),
commentId: id,
});
// Change the url to the permalink url.
window.history.replaceState(
{},
document.title,
buildUrl({ ...location, search })
);
});
// Helps child show notifications at the right scrollTop.
this.pym.onMessage('getPosition', () => {
const { height } = viewportDimensions();
let position = height + document.body.scrollTop;
if (position > NOTIFICATION_OFFSET) {
position = position - NOTIFICATION_OFFSET;
}
this.pym.sendMessage('position', position);
});
// When end-user clicks link in iframe, open it in parent context
this.pym.onMessage('navigate', url => {
// Open the new window, detach the opener, and focus on it.
const w = window.open(url, '_blank');
w.opener = null;
w.focus();
});
// Pass events from iframe to the event emitter.
this.pym.onMessage('event', raw => {
const { eventName, value } = JSON.parse(raw);
this.emitter.emit(eventName, value);
});
}
/**
* load will configure the pym parent if it hasn't already been, and setup the
* snackBar. If there are any queued operations, it will run them first.
*/
load() {
if (this.pym !== null) {
throw new Error('Stream Embed already rendered');
}
// Setup Pym.
this.setupPym();
// Attach the snackBar to the pym parent and to the body of the page.
this.snackBar.attach(window.document.body, this.pym);
// If the user clicks outside the embed, then tell the embed.
document.addEventListener('click', this.handleClick.bind(this), true);
// Listens to ${name}Storage requests on pym and relay it to
// ${name}Storage.
['local', 'session'].forEach(name => {
connectStorageToPym(
createStorage(`${name}Storage`),
this.pym,
`${name}Storage`
);
});
// Process any queued operations.
const queued = this.queued;
this.queued = null;
queued.forEach(callback => callback());
}
handleClick() {
this.pym.sendMessage('click');
}
enablePluginsDebug() {
this.pym.sendMessage('enablePluginsDebug');
}
disablePluginsDebug() {
this.pym.sendMessage('disablePluginsDebug');
}
login(token) {
this.pym.sendMessage('login', token);
}
logout() {
this.pym.sendMessage('logout');
}
remove() {
// Remove the event listeners.
document.removeEventListener('click', this.handleClick.bind(this));
this.emitter.removeAllListeners();
// Remove the snackbar.
this.snackBar.remove();
// Remove the pym parent.
this.pym.remove();
this.pym = null;
}
}
-196
View File
@@ -1,196 +0,0 @@
import queryString from 'querystringify';
import pym from 'pym.js';
import EventEmitter from 'eventemitter2';
import { buildUrl } from 'coral-framework/utils/url';
import Snackbar from './Snackbar';
import {
createStorage,
connectStorageToPym,
} from 'coral-framework/services/storage';
const NOTIFICATION_OFFSET = 200;
// Build the URL to load in the pym iframe.
function buildStreamIframeUrl(talkBaseUrl, query) {
let url = [
talkBaseUrl,
talkBaseUrl.match(/\/$/) ? '' : '/', // make sure no double-'/' if opts.talk already ends with '/'
'embed/stream?',
].join('');
url += queryString.stringify(query);
return url;
}
// Get dimensions of viewport.
function viewportDimensions() {
let e = window,
a = 'inner';
if (!('innerWidth' in window)) {
a = 'client';
e = document.documentElement || document.body;
}
return {
width: e[`${a}Width`],
height: e[`${a}Height`],
};
}
export default class Stream {
constructor(el, talkBaseUrl, query, config) {
this.query = query;
// Extract the non-opts opts from the object.
const {
events = null,
snackBarStyles = null,
onAuthChanged = null,
...opts
} = config;
this.opts = opts;
this.emitter = new EventEmitter({ wildcard: true });
this.pym = new pym.Parent(el.id, buildStreamIframeUrl(talkBaseUrl, query), {
title: opts.title,
id: `${el.id}_iframe`,
name: `${el.id}_iframe`,
});
this.snackBar = new Snackbar(snackBarStyles || {});
// Workaround: IOS Safari ignores `width` but respects `min-width` value.
this.pym.el.firstChild.style.width = '1px';
this.pym.el.firstChild.style.minWidth = '100%';
// Resize parent iframe height when child height changes
let cachedHeight;
this.pym.onMessage('height', height => {
if (height !== cachedHeight) {
this.pym.el.firstChild.style.height = `${height}px`;
cachedHeight = height;
}
});
// Attach to the events emitted by the pym parent.
if (events) {
events(this.emitter);
}
this.pym.onMessage('getConfig', () => {
this.pym.sendMessage('config', JSON.stringify(opts));
});
// If the auth changes, and someone is listening for it, then re-emit it.
if (onAuthChanged) {
this.pym.onMessage('coral-auth-changed', message => {
onAuthChanged(message ? JSON.parse(message) : null);
});
}
// Attach the snackbar to the pym parent and to the body of the page.
this.snackBar.attach(window.document.body, this.pym);
// Remove the permalink comment id from the hash.
this.pym.onMessage('coral-view-all-comments', () => {
const query = queryString.parse(location.search);
// Remove the commentId url param.
delete query.commentId;
const search = queryString.stringify(query);
const url = buildUrl({ ...location, search });
// Change the url.
window.history.replaceState({}, document.title, url);
});
// Remove the permalink comment id from the hash.
this.pym.onMessage('coral-view-comment', id => {
const search = queryString.stringify({
...queryString.parse(location.search),
commentId: id,
});
// Remove the commentId url param.
const url = buildUrl({ ...location, search });
// Change the url.
window.history.replaceState({}, document.title, url);
});
// Helps child show notifications at the right scrollTop.
this.pym.onMessage('getPosition', () => {
const { height } = viewportDimensions();
let position = height + document.body.scrollTop;
if (position > NOTIFICATION_OFFSET) {
position = position - NOTIFICATION_OFFSET;
}
this.pym.sendMessage('position', position);
});
// When end-user clicks link in iframe, open it in parent context
this.pym.onMessage('navigate', url => {
window.open(url, '_blank').focus();
});
// Pass events from iframe to the event emitter.
this.pym.onMessage('event', raw => {
const { eventName, value } = JSON.parse(raw);
this.emitter.emit(eventName, value);
});
// If the user clicks outside the embed, then tell the embed.
document.addEventListener('click', this.handleClick.bind(this), true);
// Listens to local storage requests on pym and relay it to local storage.
connectStorageToPym(
createStorage('localStorage'),
this.pym,
'localStorage'
);
// Listens to session storage requests on pym and relay it to session storage.
connectStorageToPym(
createStorage('sessionStorage'),
this.pym,
'sessionStorage'
);
}
enablePluginsDebug() {
this.pym.sendMessage('enablePluginsDebug');
}
disablePluginsDebug() {
this.pym.sendMessage('disablePluginsDebug');
}
login(token) {
this.pym.sendMessage('login', token);
}
logout() {
this.pym.sendMessage('logout');
}
remove() {
// Remove the event listeners.
document.removeEventListener('click', this.handleClick.bind(this));
this.emitter.removeAllListeners();
// Remove the snackbar.
this.snackBar.remove();
// Remove the pym parent.
this.pym.remove();
}
handleClick() {
this.pym.sendMessage('click');
}
}
-33
View File
@@ -1,33 +0,0 @@
export default class StreamInterface {
constructor(stream) {
this._stream = stream;
}
on(eventName, callback) {
return this._stream.emitter.on(eventName, callback);
}
off(eventName, callback) {
return this._stream.emitter.off(eventName, callback);
}
login(token) {
return this._stream.login(token);
}
logout() {
return this._stream.logout();
}
remove() {
return this._stream.remove();
}
enablePluginsDebug() {
return this._stream.enablePluginsDebug();
}
disablePluginsDebug() {
return this._stream.disablePluginsDebug();
}
}
+55 -90
View File
@@ -1,106 +1,71 @@
import URLSearchParams from 'url-search-params';
import Stream from './Stream';
import StreamInterface from './StreamInterface';
// Polyfill IntersectionObserver always, the alternative is that we have to also
// polyfill for Promise, which itself adds 1KB gziped, which means that the
// 4KB that the intersection observer really doesn't take up that much in terms
// of size.
import 'intersection-observer';
// Rebuild the origin if it isn't defined. This is our poor-mans polyfill
// for the location API's.
if (!window.location.origin) {
window.location.origin = `${window.location.protocol}//${
window.location.hostname
}${window.location.port ? `:${window.location.port}` : ''}`;
}
import Bridge from './Bridge';
import wrapBridge from './wrapBridge';
// parses the Asset URL from the config variable
function parseAssetURL() {
try {
// Try to get the url from the canonical tag on the page.
return document.querySelector('link[rel="canonical"]').href;
} catch (e) {
window.console.warn(
'This page does not include a canonical link tag. Talk has inferred this asset_url from the window object. Query params have been stripped, which may cause a single thread to be present across multiple pages.'
/**
* validateElement will throw an error when the element is not configured
* correctly or is not an HTMLElement at all.
*
* @param {HTMLElement} element the HTMLElement where the stream will be rendered
*/
function validateElement(element) {
if (!element) {
throw new Error(
'Please provide Coral.Talk.render() the HTMLElement you want to render Talk in.'
);
}
return window.location.origin + window.location.pathname;
if (typeof element !== 'object') {
throw new Error(
`Coral.Talk.render() expected HTMLElement but got ${element} (${typeof element})`
);
}
// Ensure element has an id, as pym can't directly accept the HTMLElement.
if (!element.id) {
element.id = `_${Math.random()}`;
}
}
export class Talk {
/**
* validateConfig is the configuration validation tool.
*
* @param {Object} config the configuration that will be used to setup Talk.
*/
function validateConfig(config) {
if (!config || typeof config !== 'object' || !config.talk) {
throw new Error(
'Coral.Talk.render() expected configuration with at least opts.talk as the Talk Base URL, none found'
);
}
}
export const Talk = {
/**
* Render a Talk stream
* @param {HTMLElement} element - Element to render the stream in
* @param {Object} config - Configuration options for talk
* @param {String} config.talk - Talk base URL
* @param {String} [config.title] - Title of Stream (rendered in iframe)
* @param {String} [config.asset_url] - Asset URL
* @param {String} [config.asset_id] - Asset ID
* @param {String} config.talk - URL to the Talk installation
* @param {String} [config.asset_id] - (optional) ID for the Asset
* @param {String} [config.asset_url] - (optional) URL where the Asset is located
* @param {String} [config.auth_token] - (optional) A jwt representing the session
* @param {String} [config.lazy] - (optional) If set the stream will only render lazily
* @param {String} [config.talkStaticUrl] - (optional) Static URL used to serve Talk
* @return {Object}
*
* Example:
* ```
* const embed = Talk.render(document.getElementById('talkStreamEmbed'), config);
*
* // trigger a login with optional token.
* embed.login(token);
*
* // trigger a logout.
* embed.logout();
*
* // listen to events (in this case all events).
* embed.on('**', function(value) {
* console.log(this.event, value);
* });
* ```
*/
static render(element, config) {
if (!element) {
throw new Error(
'Please provide Coral.Talk.render() the HTMLElement you want to render Talk in.'
);
}
if (typeof element !== 'object') {
throw new Error(
`Coral.Talk.render() expected HTMLElement but got ${element} (${typeof element})`
);
}
if (!config || typeof config !== 'object' || !config.talk) {
throw new Error(
'Coral.Talk.render() expected configuration with at least opts.talk as the Talk Base URL, none found'
);
}
render: (element, config) => {
// Validate the element.
validateElement(element);
// Ensure el has an id, as pym can't directly accept the HTMLElement.
if (!element.id) {
element.id = `_${Math.random()}`;
}
// Validate the configuration.
validateConfig(config);
// Compose the query to send down to the Talk API so it knows what to load.
const query = {};
// Parse the url parameters to extract some of the information.
const search = new URLSearchParams(window.location.search);
// Pull the commentID out from the query params.
const commentID = search.get('commentId') || search.get('commentID');
if (commentID) {
query.comment_id = commentID;
}
// Extract the asset id from the options.
if (config.asset_id) {
query.asset_id = config.asset_id;
}
// Parse the Asset URL.
query.asset_url = config.asset_url;
if (!query.asset_url) {
query.asset_url = parseAssetURL();
}
// Create the new Stream.
const stream = new Stream(element, config.talk, query, config);
// Return the public interface for the stream.
return new StreamInterface(stream);
}
}
// Create the new Bridge, and wrap it up.
return wrapBridge(new Bridge(element, config));
},
};
+30
View File
@@ -0,0 +1,30 @@
export default function onIntersect(el, callback) {
// Ensure that the intersection observer is available.
if (!IntersectionObserver) {
// tslint:disable-next-line:no-console
window.console.warn('IntersectionObserver not available, rendering now');
callback();
return;
}
// Create the Intersection Observer that will wait till the embed is within
// view and will then call the callback.
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting) {
// Stop receiving intersection events.
observer.disconnect();
// Fire the callback.
callback();
}
},
{
rootMargin: '100px',
threshold: 1.0,
}
);
// Start observing the element for visibility.
observer.observe(el);
}
+31
View File
@@ -0,0 +1,31 @@
export default bridge => {
// whenRendered will call the callback when the stream has rendered (or now if
// the stream is already rendered).
const whenRendered = callback => bridge.queueWhenRendered(callback);
// onlyWhenRendered will guard the callback unless the stream is rendered.
const onlyWhenRendered = callback => {
bridge.ensureRendered();
callback();
};
// Return the limited stream interface.
return {
// Directly map the on/off from the event emitter to the stream.
on: (eventName, callback) => bridge.emitter.on(eventName, callback),
off: (eventName, callback) => bridge.emitter.off(eventName, callback),
// Queue up the login operation until the stream has been rendered.
login: token => whenRendered(() => bridge.login(token)),
// Queue up the logout operation until the stream has been rendered.
logout: () => whenRendered(() => bridge.logout()),
// Remove the stream if it's already been rendered.
remove: () => onlyWhenRendered(() => bridge.remove()),
// Queue up the plugin config until the embed has rendered.
enablePluginsDebug: () => whenRendered(() => bridge.enablePluginsDebug()),
disablePluginsDebug: () => whenRendered(() => bridge.disablePluginsDebug()),
};
};
+15
View File
@@ -76,6 +76,10 @@ const CONFIG = {
// on the scraper when it makes requests.
SCRAPER_HEADERS: process.env.TALK_SCRAPER_HEADERS || '{}',
// HTTP_X_REQUEST_ID is a string which represents the request header where we
// should source the request ID from, otherwise, a new one will be generated.
HTTP_X_REQUEST_ID: process.env.TALK_HTTP_X_REQUEST_ID || null,
//------------------------------------------------------------------------------
// JWT based configuration
//------------------------------------------------------------------------------
@@ -213,6 +217,9 @@ const CONFIG = {
// messages through the websocket to keep the socket alive.
KEEP_ALIVE: process.env.TALK_KEEP_ALIVE || '30s',
// CONCURRENCY is the number of workers that will serve traffic.
CONCURRENCY: parseInt(process.env.TALK_CONCURRENCY || '1'),
//------------------------------------------------------------------------------
// Cache configuration
//------------------------------------------------------------------------------
@@ -220,6 +227,14 @@ const CONFIG = {
CACHE_EXPIRY_COMMENT_COUNT:
process.env.TALK_CACHE_EXPIRY_COMMENT_COUNT || '1hr',
// EMBED_EXPIRY_TIME is the time that the embed will be cacheable for, sent as
// the max-age= directive on the Cache-Control header.
EMBED_EXPIRY_TIME: ms(process.env.TALK_EMBED_EXPIRY || '24hr'),
// EMBED_EXPIRY_TIME is the time that the rest of the static files will be
// cacheable for, sent as the max-age= directive on the Cache-Control header.
STATIC_EXPIRY_TIME: ms(process.env.TALK_STATIC_EXPIRY || '1w'),
//------------------------------------------------------------------------------
// Recaptcha configuration
//------------------------------------------------------------------------------
+2 -2
View File
@@ -104,8 +104,8 @@ sidebar:
children:
- title: Authentication
url: /integrating/authentication/
- title: Asset Management
url: /integrating/asset-management/
- title: CMS Integration
url: /integrating/cms-integration/
- title: Asset Scraping
url: /integrating/asset-scraping/
- title: Configuring the Comment Stream
+2 -1
View File
@@ -1,3 +1,4 @@
/ /talk/
/talk/reference/server/ /talk/api/server/
/talk/reference/graphql/ /talk/api/graphql/
/talk/reference/graphql/ /talk/api/graphql/
/talk/integrating/asset-management/ /talk/integrating/cms-integration/
+13
View File
@@ -187,8 +187,10 @@ and walk through the initial setup steps.
* First, enter your **Organization Name** and **Organization Contact Email**. This will appear in emails when inviting new team members.
* Next, create your Admin user. You can specify an **Email Address**, **Username**, and **Password**
* Finally, enter your list of **Permitted Domains**, read [here](/talk/configuring-talk/#permitted-domains) about whitelisting domains
_During development, ensure you whitelist 127.0.0.1:3000 otherwise the
[http://127.0.0.1:3000/](http://127.0.0.1:3000/) page will not
load._
@@ -220,6 +222,17 @@ Once you have added the domain of these docs, you can click the button below.
<div class="mount"></div>
</div>
### Developer Endpoints
With your local instance of Talk running in development mode (env variable `NODE_ENV=development`) you should now also be able to access the following developer routes:
* [http://127.0.0.1:3000/dev](http://127.0.0.1:3000/dev]) provides a sample comment stream
* [http://127.0.0.1:3000/dev/assets](http://127.0.0.1:3000/dev/assets]) provides a list of all stories in Talk and can generate new sample assets
### Conclusion
At this point you've successfully installed, configured, and ran your very own
instance of Talk! Continue through this documentation on this site to learn more
on how to configure, develop with, and contribute to Talk!
@@ -70,5 +70,7 @@ You can now start the application by running:
yarn watch:server
```
If you are developing a custom plugin you can use `yarn watch:client` or `yarn watch` to run both client and server.
At this stage, you should refer to the [configuration](/talk/configuration/) for
configuration variables that are specific to your installation.
+3 -1
View File
@@ -23,11 +23,13 @@ permalink: /pre-launch-checklist/
- [ ] Do you need to migrate comments from a legacy system? We currently support Disqus, Livefyre, and Civil Comments.
- Use the [Talk Import](https://github.com/coralproject/talk-importer) framework
- [ ] Do you want to provide single sign-on (SSO) by integrating with an external auth system?
- See [Authenticating with Talk](/talk/integrating/authentication/)
- [ ] Do you want to integrate Talk with your CMS to automate embedding Talk Comment Stream into your site?
- See [CMS Integration](/talk/integrating/cms-integration/)
- [ ] Do you want to use Social sign-on?
- Facebook
@@ -5,13 +5,15 @@ permalink: /commenter-features/
## Signing up for Talk
There are 2 ways that newsrooms can support signup/login functionality with Talk:
There are 3 ways that newsrooms can support signup/login functionality with Talk:
* Use Talks auth plugin out of the box (supports account registration with username and password, as well as features like forgot password)
* Use 3rd party authentication provider such as FaceBook or Google. We provide plugins that support logging in with either [Facebook](/talk/plugin/talk-plugin-facebook-auth/)
or [Google](/talk/plugin/talk-plugin-google-auth/). (Note: you must provide your own Facebook App ID and Secret, which you can read more about here: [https://developers.facebook.com](https://developers.facebook.com))
* Create their own auth plugin to integrate with your own auth systems
We also provide a Facebook auth plugin that supports logging in with Facebook (you must provide your own Facebook App ID and Secret, which you can read more about here: [https://developers.facebook.com](https://developers.facebook.com))
## Comments and Replies
+10 -4
View File
@@ -3,8 +3,10 @@ title: Authenticating with Talk
permalink: /integrating/authentication/
---
You can integrate Talk with any external authentication service that will enable
seamless single sign-on for users within your organization. There are a few
Out of the box Talk supports account registration with username and password, as well as features like forgot password.
You can also integrate Talk with any external authentication service that will enable
seamless single sign-on (SSO) for users within your organization. There are a few
methods of doing so:
1. Passport Middleware
@@ -17,8 +19,12 @@ choice.
You would choose the **Passport Middleware** route when you are OK using an auth
that is triggered from inside Talk that is not connected to an external auth
state (you don't use the auth anywhere else now). A great example of this is our
[talk-plugin-facebook-auth](/talk/plugin/talk-plugin-facebook-auth/) plugin.
state (you don't use the auth anywhere else now).
Plugins are available for the following 3rd party authentication providers:
* [Facebook](/talk/plugin/talk-plugin-facebook-auth/)
* [Google](/talk/plugin/talk-plugin-google-auth/)
## Custom Token Integration
@@ -1,22 +1,81 @@
---
title: Asset Management
permalink: /integrating/asset-management/
title: CMS Integration
permalink: /integrating/cms-integration/
---
## Embedding Comments on Your Site
Talk provides an embed script that you can drop into your site where you want a comments section to appear. By default that script dynamically generate Assets
in Talk in order to make it easier for lighter installations.
You can find the embed script inside talk under `Configure > Tech Settings > Embed Script`. It should look something like this, but with your domain in place of `<TALK_ROOT_URL>`:
```
<div id="coral_talk_stream"></div>
<script src="<TALK_ROOT_URL>/static/embed.js" async onload="
Coral.Talk.render(document.getElementById('coral_talk_stream'), {
talk: '<TALK_ROOT_URL>'
});
"></script>
```
## Triggering the Comments Section (client side, i.e. Your site)
When the embed script is triggered on your page load several things are initiated inside Talk, including fetching all comments for the specified article, establishing websocket connections for this user, and checking users session for SSO/authentication.
Instead of greedily triggering the embed to render on _EVERY PAGE LOAD_, we highly recommend implementing a _“lazy”_ rendering strategy to only render the comments section if a user wants to interact with it. This will greatly improve your initial page load performance, and will be critical to managing server resources if youre running Talk on a heavy-traffic production site.
We recommend using one of these _“lazy”_ loading strategies:
#### Scroll to Comments Section
Wait for user to scroll to the comment section before triggering the embed to render.
You can pass lazy: true to the render options, like so:
```
Coral.Talk.render(document.getElementById('container'), {
talk: 'https://my-talk-installation.com',
lazy: true,
});
```
Or you can enable lazy rendering by default on all assets using ENV variable `TALK_DEFAULT_LAZY_RENDER=TRUE`
_*Note: This feature requires Talk version 4.6.8 or greater_
#### Show Comments Button
You can hide the comments section until a user clicks button, then trigger the embed to render
This example uses jQuery to render the embed on the button's click event
```
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<div id="coral_talk_stream">
<button>Show Comments</button>
</div>
<script type="text/javascript">
$('#coral_talk_stream button').on('click', function() {
$.getScript('<TALK_ROOT_URL>/static/embed.js', function() {
Coral.Talk.render(document.getElementById('coral_talk_stream'), {
talk: '<TALK_ROOT_URL>',
});
});
});
</script>
```
## Creating Assets in Talk (server side, i.e. What you need to send to Talk)
One of the most frequent questions that we get asked by organizations trying to
integrate Talk is: _How do we hook our CMS up to Talk so that articles are in
sync?_
This guide is designed to explain the steps to take your base installation of
Talk and configure it to allow only assets pushed into it from your CMS, and
keep your URL/title in sync. We won't cover here how to install the plugin, as
it is covered in our [Plugins Overview](/talk/plugins/).
### “Lazy Asset Creation
Talks provided embed script by default dynamically generates Assets in Talk based on each unique url that triggers it. The url must reference an existing Permitted Domain. If your articles/stories always have unique urls, then you will not need to modify the default behavior.
## Why do we need to create a plugin?
Assets created in this way will then be scraped to load metadata, see [Asset Scraping](/talk/integrating/asset-scraping/)
By default, Talk will use "Lazy Asset Creation" to dynamically generate Assets
in Talk in order to make it easier for lighter installations. In order to have
more strict control over this flow, we will create a plugin that will:
## Customizing the Integration with a Plugin
In order to have more strict control over the asset creation flow to allow only assets pushed into it from your CMS, and keep your URL/title in sync we will create a plugin.
We will create a plugin that will:
1. Disable "Lazy Asset Creation" by [Overriding a Resolver](#overriding-a-resolver).
2. Create Assets from our CMS by [Creating a New Asset Route](#creating-a-new-asset-route).
@@ -24,6 +83,9 @@ more strict control over this flow, we will create a plugin that will:
We will then modify our embed so that we can [Target the Asset](#target-the-asset).
This guide is designed to explain the steps to take your base installation of Talk and configure it. We won't cover here how to install the plugin, as it is covered in our [Plugins Overview](/talk/plugins/).
But first we should grab our basic plugin structure:
```sh
@@ -248,7 +310,7 @@ module.exports = router => {
};
```
As you can see from the previous step of [Creating a New Asset Route](#creating-a-New-Asset-Route)
As you can see from the previous step of [Creating a New Asset Route](#Creating%20a%20New%20Asset%20Route)
, we have added the new `PUT` route to the router. This is a simple addition
that allows your CMS to call into Talk when the asset has updated it's title,
it's url (or really anything in the [AssetSchema](https://github.com/coralproject/talk/blob/master/models/asset.js)) to keep the Talk Admin and links up to date.
+12 -2
View File
@@ -105,6 +105,8 @@ Then the application can be started as is.
### Docker
To deploy customized plugins to a production instance of Talk, we recommend using the Docker onbuild strategy outlined below.
If you deploy using Docker, you can extend from the `*-onbuild` image, an
example `Dockerfile` for your project could be:
@@ -112,14 +114,22 @@ example `Dockerfile` for your project could be:
FROM coralproject/talk:4.5-onbuild
```
Where the directory for your instance would contain a `plugins.json` file
describing the plugin requirements and a `plugins` directory containing any
Establish a private repository for your instance that includes the following:
* a `plugins.json` file
listing the plugin requirements
* a `plugins` directory containing any
other local plugins that should be included.
* a Dockerfile as outlined above
Git submodules can also be used to point to plugin directories that might be outside your primary repository.
Onbuild triggers will execute when the image is building with your custom
configuration and will ensure that the image is ready to use by building all
assets inside the image as well.
Once built, you can deploy the docker image to your architecture by tagging the image and including it in your docker-compose.yml.
For more information on the onbuild image, refer to the
[Installation from Docker](/talk/installation-from-docker/) documentation.
+1
View File
@@ -136,6 +136,7 @@ de:
code_of_conduct_summary_desc: 'Verfassen Sie eine Einleitung, die über jedem Kommentarbereich erscheint. Nützlich z.B. für Community-Richtlinien.'
include_question_here: 'Stellen Sie Ihre Frage hier:'
include_text: 'Fügen Sie Ihren Text hier ein.'
include_an_icon: 'Fügen Sie ein Icon hinzu'
moderate: Moderieren
moderation_settings: Moderations-Einstellungen
open: Öffnen
+1
View File
@@ -139,6 +139,7 @@ en:
hours: Hours
code_of_conduct_summary: 'Summary of your Code of Conduct'
code_of_conduct_summary_desc: 'This message will appear above every comments stream on your site. Click here to learn more about writing a good code.'
include_an_icon: 'Include an Icon'
include_question_here: 'Write your question here:'
include_text: 'Include your text here.'
moderate: Moderate
+3
View File
@@ -38,6 +38,9 @@ es:
name: Nombre
post: Publicar
reply: Responder
comment_history_blank:
info: 'Una historia de tus comentarios aparecerá aquí'
title: 'No has escrito ningún comentario'
comment_offensive: 'Este comentario es ofensivo'
comment_plural: Comentarios
comment_post_banned_word: 'Tu comentario contiene una o más palabras que no están permitidas en nuestro espacio, por lo que no será publicado. Si crees que es un error, por favor contactate con nuestro equipo de moderación.'
+93 -14
View File
@@ -38,6 +38,9 @@ nl_NL:
name: Naam
post: Plaats
reply: Beantwoord
comment_history_blank:
info: 'Reacties die je hebt geplaatst zullen hier verschijnen'
title: 'Je hebt nog geen reacties geplaatst'
comment_offensive: 'Deze reactie is aanstootgevend'
comment_plural: Reacties
comment_post_banned_word: 'Je reactie bevat een of meerdere termen die wij niet toestaan. Neem contact op wanneer je het er niet mee eens bent.'
@@ -45,7 +48,10 @@ nl_NL:
comment_post_notif_premod: 'Dank voor je reactie! Deze wordt zo snel mogelijk gemodereerd.'
comment_singular: Reactie
common:
contains_link: 'Bevat link'
copy: Kopieer
copied: Gekopieerd
notsupported: 'Niet ondersteund'
error: 'Er is een fout opgetreden.'
reaction: reactie
reactions: reacties
@@ -80,9 +86,11 @@ nl_NL:
spam_ads: Spam/Ads
staff: Staff
status: Status
suspended: Geschorst
username_and_email: 'Gebruikersnaam en e-mailadres'
yes_ban_user: 'Ja, verban gebruiker'
configure:
access_message: 'Je moet een admin zijn om toegang te krijgen tot de configuratie instellingen. Vraag de dichtsbijzijnde Admin om toegang!'
apply: Toepassen
banned_word_text: 'Reacties die deze woorden of zinnen bevatten (niet hoofdlettergevoelig) worden automatisch uit de conversatie verwijderd. Voer een woord in, en druk op enter of Tab om toe te passen. Je kunt ook een kommagescheiden lijst plakken.'
banned_words_title: 'Lijst met verbannen woorden'
@@ -107,6 +115,8 @@ nl_NL:
custom_css_url_desc: 'URL van CSS stylesheet die de standaard Embed Conversatie stylesheet overschrijft. Kan intern of extern zijn.'
days: Dagen
description: 'Als admin kun je de instellingen aanpassen voor de conversatie bij dit verhaal:'
disable_commenting_desc: 'Schrijf een bericht dat wordt weergeven wanneer reageren is afgesloten.'
disable_commenting_title: 'Reacties afsluiten voor de gehele site'
domain_list_text: 'Voer de domeinen in die je wil toestaan voor Talk, bijvoorbeeld je staging en productie-omgevingen (bijv. localhost:3000 staging.domeinnaam.com domeinnaam.com).'
domain_list_title: 'Toegestane domeinnamen'
edit_comment_timeframe_heading: 'Wijzig reactie tijdsduur'
@@ -132,9 +142,23 @@ nl_NL:
open: Open
open_stream: 'Open Stream'
open_stream_configuration: 'Deze converstatie is momenteel open. Door deze te sluiten kunnen geen nieuwe reacties meer worden geplaatst, maar de oude zullen nog steeds worden weergegeven.'
organization_contact_email: 'Organisatie Contact E-mailadres'
organization_info_copy: 'We gebruiken deze informatie in e-mail notificaties die door Talk worden verstuurd. Dit verbind de berichten met je organisatie en biedt gebruikers een manier om contact op te nemen mochten ze een probleem met hun account ondervinden.'
organization_info_copy_2: 'We raden aan om een algemeen e-mail account (bijvoorbeeld community@yournewsroom.com) te gebruiken voor dit doeleinde. Dit betekent dat dit adres onveranderd kan blijven en deze geen naam blootstelt die gebruikers kunnen gebruiken mocht hun account geblokkeerd worden.'
organization_information: 'Organisatie Informatie'
organization_name: 'Organisatie Naam'
suspect_or_forbidden_words_placeholder: 'Woord of uitdrukking'
product_guide_link: 'Product Handleiding'
report_bug_or_feedback: 'Rapporteer een bug of geef feedback'
require_email_verification: 'Maak e-mail verificatie vereist'
require_email_verification_text: 'Nieuwe gebruikers moeten hun e-mailadres verifiëren voordat ze door kunnen gaan.'
save_changes: 'Wijzigingen opslaan'
save_changes_dialog:
cancel: Annuleren
copy: 'Je hebt een of meerdere wijzigingen gedaan zonder op te slaan. Wil je je wijzigingen opslaan of ongedaan maken?'
discard: 'Ongedaan maken'
save_settings: 'Instellingen Opslaan'
unsaved_changes: 'Niet-opgeslagen wijzigingen'
shortcuts: Sneltoetsen
sign_out: Uitloggen
stories: Conversaties
@@ -142,7 +166,7 @@ nl_NL:
suspect_word_text: 'Reacties die deze woorden of zinsdelen bevatten (niet hoofdlettergevoelig) zullen worden gemarkeerd in de conversatie. Type een woord en druk op enter of tab om toe te voegen. Je kunt optioneel een komma-gescheiden lijst plakken.'
suspect_word_title: 'Lijst met verdachte woorden'
tech_settings: 'Technische instellingen'
title: 'Conversatie cnfigureren'
title: 'Conversatie configureren'
weeks: Weken
wordlist: 'Zwarte woordenlijst'
confirm_email:
@@ -183,6 +207,9 @@ nl_NL:
if_you_did_not: 'Wanneer jij dit niet hebt aangevraagd, kun je deze e-mail negeren.'
subject: 'Email bevestiging'
to_confirm: 'Ga naar de volgende link om het account te bevestigen:'
password_change:
body: 'Het wachtwoord voor je account is gewijzigd.\n\nAls je deze wijziging niet hebt aangevraagd, neem dan contact met ons op: {0}'
subject: '{0} wachtwoord wijziging'
password_reset:
if_you_did: 'Wanneer je dit zelf was,'
please_click: 'klik dan hier om je wachtwoord te veranderen'
@@ -192,44 +219,61 @@ nl_NL:
embed_comments_tab: Reacties
embedlink:
copy: 'Kopieer naar klembord'
copied: Gekopieerd
error:
ALREADY_EXISTS: 'Bron bestaat al'
AUTHENTICATION: 'Er is een fout opgetreden tijdens het aanmelden van je account'
CANNOT_IGNORE_STAFF: 'Kan geen Staff-leden negeren.'
COMMENT_PARENT_NOT_VISIBLE: 'De reactie waarop je probeert te reageren bestaat niet of is inmiddels verwijderd.'
COMMENT_TOO_SHORT: 'Reacties moeten meer dan één teken hebben. Herzie je reactie en probeer opnieuw.'
COMMENT_TOO_LONG: 'Tekst is te lang'
COMMENTING_CLOSED: 'Reageren is al afgesloten.'
COMMENTING_DISABLED: 'Reageren is momenteel uigeschakeld op deze site'
confirm_password: 'Wachtwoorden komen niet overeen. Controleer opnieuw.'
DELETION_NOT_SCHEDULED: 'Verwijdering was niet ingepland'
EDIT_USERNAME_NOT_AUTHORIZED: 'Je bent niet gemachtigd om je gebruikersnaam te wijzigen.'
EDIT_WINDOW_ENDED: 'Dit commentaar kan niet langer worden gewijzigd. Het tijdsbestek is verstreken.'
email: 'Geen geldig e-mailadres'
EMAIL_ALREADY_VERIFIED: 'E-mailadres is reeds geverifiëerd'
EMAIL_IN_USE: 'E-mailadres al in gebruik'
email_not_verified: 'E-mailadres {0} is niet geverifiëerd'
EMAIL_NOT_VERIFIED: 'E-mailadres is niet geverifiëerd'
email_password: 'E-mail en/of wachtwoord combinatie onjuist'
EMAIL_REQUIRED: 'Een e-mailadres is vereist'
EMAIL_VERIFICATION_TOKEN_INVALID: 'Ongeldig e-mail verificatie token'
INCORRECT_PASSWORD: 'Ongeldig wachtwoord'
INVALID_ASSET_URL: 'Ongeldige URL'
LOGIN_MAXIMUM_EXCEEDED: 'Je hebt te veel mislukte inlogpogingen gedaan. Even geduld aub.'
network_error: 'Kon geen verbinding maken met server. Controleer je internetverbinding en probeer het opnieuw.'
NO_SPECIAL_CHARACTERS: 'Gebruikersnamen kunnen alleen cijfers, letters en _ bevatten.'
NOT_AUTHORIZED: 'Je bent niet geauthoriseerd om dit te doen.'
NOT_FOUND: 'Bron niet gevonden'
organization_contact_email: 'Organisatie e-mailadres is niet geldig.'
organization_name: 'Organisatienaam kan alleen cijfers en letters bevatten.'
password: 'Wachtwoord moet minimaal 8 karakters lang zijn'
PAGE_NOT_AVAILABLE_ROLE: 'Deze pagina is enkel voor toegankelijk voor teamleden. Neem contact op met een admin als je toegang tot dit team wilt.'
PASSWORD_INCORRECT: 'Je huidige wachtwoord is incorrect ingevuld'
PASSWORD_LENGTH: 'Wachtwoord is te kort'
PASSWORD_REQUIRED: 'Je moet een wachtwoord invoeren.'
PASSWORD_RESET_TOKEN_INVALID: 'Je wachtwoord-herstel token is ongeldig.'
PROFANITY_ERROR: 'Gebruikersnamen mogen niet aanstootgevend zijn. Neem contact op met de administrator wanneer je denkt dat dit niet klopt.'
RATE_LIMIT_EXCEEDED: 'Gebruikslimiet overschreden'
required_field: 'Dit veld is verplicht'
SAME_USERNAME_PROVIDED: 'Je moet een andere gebruikersnaam invoeren.'
temporarily_suspended: 'Your account is currently suspended. It will be reactivated {0}. Please contact us if you have any questions.'
temporarily_suspended: 'Je account is momenteel geschorst. Deze zal worden gereactiveerd {0}. Neem s.v.p. contact met ons op voor vragen.'
unexpected: 'Onverwachte fout opgetreden. Sorry!'
username: 'Gebruikersnamen kunnen alleen cijfers, letters en _ bevatten.'
USERNAME_IN_USE: 'Gebruikersnaam al in gebruik'
USERNAME_REQUIRED: 'Gebruikersnaam is vereist'
flag_comment: 'Rapporteer reactie'
flag_reason: 'Reden voor rapporteren (optioneel)'
flag_reasons:
username:
impersonating: 'Deze gebruiker doet zich voor als iemand anders'
nolike: 'Ik vind deze gebruikersnaam niet leuk'
offensive: 'Deze gebruikersnaam is aanstootgevend'
other: Andere
spam: 'Dit lijkt op een advertentie/marketing'
flag_username: 'Rapporteer gebruikersnaam'
flagged_usernames:
notify_approved: '{0} heeft gebruikersnaam {1} goedgekeurd'
@@ -258,6 +302,7 @@ nl_NL:
changed_name:
msg: 'De verandering van je gebruikersnaam wordt door ons moderatieteam gecontroleerd.'
comment: reactie
comment_is_deleted: 'De reageerder heeft zijn account verwijderd.'
comment_is_hidden: 'Deze reactie is niet beschikbaar.'
comment_is_ignored: 'Deze reactie is verborgen omdat je de gebruiker negeert.'
comment_is_rejected: 'Je hebt deze reactie afgewezen.'
@@ -306,6 +351,17 @@ nl_NL:
title: 'Toegestane domeinnamen'
like: Like
loading_results: 'Resultaten worden geladen'
login:
email_address: E-mailadres
forgot_password: 'Wachtwoord vergeten?'
go_back: 'Ga terug'
sign_in: Inloggen
sign_in_button: Inloggen
sign_in_message: 'Log in om te communiceren met je community'
password: Wachtwoord
reset_password_send_button: 'Wachtwoord ophalen'
request_passowrd: 'Vraag een nieuwe aan.'
team_sign_in: 'Team login'
marketing: 'Dit lijkt op een advertentie/marketing'
moderate_this_stream: 'Modereer deze conversatie'
modqueue:
@@ -350,6 +406,8 @@ nl_NL:
show_shortcuts: 'Toon sneltoetsen'
singleview: Zen-modus
sort: Sorteer
suspend: 'Schors gebruiker'
system_withheld: 'Achtergehouden door systeem'
thismenu: 'Open dit menu'
thousand: k
toggle_search: 'Open zoekvenster'
@@ -363,6 +421,7 @@ nl_NL:
other: Ander
password_reset:
change_password: 'Verander wachtwoord'
change_password_help: 'Voer een nieuw wachtwoord in om mee in te loggen. Gebruik een veilig wachtwoord!'
confirm_new_password: 'Bevestig nieuw wachtwoord'
new_password: 'Nieuw wachtwoord'
new_password_help: 'Wachtwoord moet minimaal 8 karakters lang zijn.'
@@ -384,6 +443,13 @@ nl_NL:
username: gebruikersnaam
write_message: 'Schrijf een bericht'
yes_suspend: 'Ja, schors'
reject_username_dialog:
cancel: Annuleren
description: 'Help ons dit te begrijpen'
message: 'Reden voor rapporteren (Optioneel)'
reason: Reden
reject_username: 'Gebruikersnaam afwijzen'
title: 'Gebruikersnaam afwijzen'
reply: Beantwoord
report: Rapporteer
report_notif: 'Dank voor het rapporteren van deze reactie. Deze wordt zo snel mogelijk gemodereerd.'
@@ -443,30 +509,43 @@ nl_NL:
user_detail:
all: Alle
ban: 'Gebruiker verbannen'
email: Email
email: E-mailadres
id: ID
karma: Karma
karma_docs_link: 'https://docs.coralproject.net/talk/trust/#user-karma-score'
learn_more: 'Meer informatie'
member_since: 'Lid sinds'
reject_rate: 'Beoordeling verwijderen'
reject_username: 'Gebruikersnaam afwijzen'
rejected: Afgewezen
remove_ban: 'Verbanning verwijderen'
remove_suspension: 'Schorsing opheffen'
suspend: 'Gebruiker schorsen'
suspended: Geschorst
total_comments: 'Totaal aantal reacties'
unreliable: Onbetrouwbaar
user_history: Accountgeschiedenis
user_karma_score: 'Gebruiker Karma Score'
username: Gebruikersnaam
username_needs_approval: 'Gebruikersnaam heeft goedkeuring nodig'
username_rejected: 'Gebruikersnaam afgewezen'
user_history:
action: Action
ban_removed: 'Ban removed'
date: Date
suspended: 'Suspended, {0}'
suspension_removed: 'Suspension removed'
system: System
taken_by: 'Taken By'
user_banned: 'User banned'
username_status: 'Username {0}'
user_impersonating: 'Deze gebruiker imiteert'
action: Actie
ban_removed: 'Schorsing opgeheven'
date: Datum
suspended: 'Geschorst, {0}'
suspension_removed: 'Schorsing opgeheven'
system: Systeem
taken_by: 'Genomen door'
user_banned: 'Gebruiker geschorst'
username_status: 'Gebruikersnaam {0}'
user_impersonating: 'Deze gebruiker doet zich voor als iemand anders'
user_no_comment: 'Je hebt nog niet eerder gereageerd. Laat je mening horen!'
username_offensive: 'Dit is een aanstootgevende gebruikersnaam.'
validators:
confirm_password: 'Wachtwoorden komen niet overeen. Controleer opnieuw.'
confirm_email: 'E-mailadressen komen niet overeen. Controleer opnieuw.'
confirm_password: Wachtwoorden komen niet overeen. Controleer opnieuw.
required: 'Dit veld is verplicht'
verify_email: 'Geen geldig e-mailadres'
verify_organization_name: 'Organisatienaam kan alleen cijfers en letters bevatten.'
verify_password: 'Wachtwoord moet minimaal 8 karakters lang zijn'
+44
View File
@@ -0,0 +1,44 @@
const express = require('express');
const path = require('path');
const staticServer = require('express-static-gzip');
const { merge } = require('lodash');
const { EMBED_EXPIRY_TIME, STATIC_EXPIRY_TIME } = require('../config');
// EMBED_CACHE_CONTROL_HEADER is the header sent when the file is embed.js.
const EMBED_CACHE_CONTROL_HEADER = [
'public',
`max-age=${Math.floor(EMBED_EXPIRY_TIME / 1000)}`,
'immutable',
].join(', ');
// Define the options to be applied to all static files, the embed itself has a
// separate override.
const defaultOpts = {
maxAge: STATIC_EXPIRY_TIME,
immutable: true,
setHeaders: (res, path) => {
if (path.includes('/dist/embed.js')) {
// embed.js has a different max-age then the rest of the static files.
res.setHeader('Cache-Control', EMBED_CACHE_CONTROL_HEADER);
}
},
};
// Setup the configuration for the compression options on how to serve files.
const compressionOpts = {
indexFromEmptyFile: false,
enableBrotli: true,
customCompressions: [{ encodingName: 'deflate', fileExtension: 'zz' }],
};
// Serve the directories under ../dist.
const dist = path.resolve(path.join(__dirname, '../dist'));
/**
* middleware in production will serve compressed files if available, otherwise
* it will use express's static middleware.
*/
module.exports =
process.env.NODE_ENV === 'production'
? staticServer(dist, merge(compressionOpts, defaultOpts))
: express.static(dist, defaultOpts);
+16 -6
View File
@@ -1,11 +1,21 @@
const { HTTP_X_REQUEST_ID } = require('../config');
const uuid = require('uuid/v1');
// Trace middleware attaches a request id to each incoming request.
module.exports = (req, res, next) => {
req.id = uuid();
module.exports = HTTP_X_REQUEST_ID
? (req, res, next) => {
req.id = req.get(HTTP_X_REQUEST_ID) || uuid();
// Add the context ID to the request as an HTTP header.
res.set('X-Talk-Trace-ID', req.id);
// Add the context ID to the request as an HTTP header.
res.set('X-Talk-Trace-ID', req.id);
next();
};
next();
}
: (req, res, next) => {
req.id = uuid();
// Add the context ID to the request as an HTTP header.
res.set('X-Talk-Trace-ID', req.id);
next();
};
+5 -3
View File
@@ -1,6 +1,6 @@
{
"name": "talk",
"version": "4.6.6",
"version": "4.7.0",
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
"main": "app.js",
"private": true,
@@ -58,6 +58,7 @@
"dependencies": {
"@coralproject/gql-merge": "^0.1.0",
"@coralproject/graphql-anywhere-optimized": "^0.1.0",
"@ungap/url-search-params": "^0.1.2",
"apollo-client": "^1.9.1",
"apollo-engine": "^0.8.1",
"apollo-server-express": "^1.2.0",
@@ -105,7 +106,7 @@
"eventemitter2": "^4.1.2",
"exports-loader": "^0.6.4",
"express": "4.16.0",
"express-static-gzip": "^0.3.1",
"express-static-gzip": "^1.1.1",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^0.11.2",
"final-form": "^4.8.1",
@@ -130,6 +131,7 @@
"imports-loader": "^0.7.1",
"inquirer": "^3.2.2",
"inquirer-autocomplete-prompt": "^0.12.1",
"intersection-observer": "^0.5.1",
"ioredis": "3.1.4",
"ip": "^1.1.5",
"jest": "^23.0.0",
@@ -203,6 +205,7 @@
"style-loader": "^0.16.0",
"subscriptions-transport-ws": "^0.7.2",
"supports-color": "^4",
"throng": "^4.0.0",
"timeago.js": "^2.0.3",
"timekeeper": "^1.0.0",
"tlds": "^1.196.0",
@@ -210,7 +213,6 @@
"url-join": "^2.0.2",
"url-loader": "^0.6.0",
"url-parse": "^1.4.3",
"url-search-params": "^0.9.0",
"uuid": "^3.1.0",
"webpack": "^3.10.0",
"webpack-manifest-plugin": "^2.0.0-rc.2",
@@ -297,49 +297,58 @@ fr:
write_your_username: "Modifier votre nom d'utilisateur"
your_username: "Votre nom d'utilisateur apparaît sur chaque commentaire que vous publiez."
nl_NL:
sign_in:
email_verify_cta: "Controleer je e-mailadres."
request_new_verify_email: "Vraag nieuwe bevestigingsemail aan"
verify_email: "Bedankt voor het aanmaken van een account! We hebben een email verstuurd naar het adres dat je hebt opgegeven om je account te verifiëren."
verify_email2: "Je account moet worden geverifiëerd voordat je kunt deelnemen in de community."
not_you: "Ben je dit niet?"
logged_in_as: "Ingelogd als"
facebook_sign_in: "Inloggen met Facebook"
facebook_sign_up: "Registreren met Facebook"
logout: "Uitloggen"
sign_in: "Inloggen"
sign_in_to_join: "Log in om deel te nemen"
or: "Of"
email: "E-mailadres"
password: "Wachtwoord"
forgot_your_pass: "Wachtwoord vergeten?"
need_an_account: "Heb je een account nodig?"
register: "Registreren"
sign_up: "Aanmelden"
confirm_password: "Bevestig wachtwoord"
username: "Gebruikersnaam"
already_have_an_account: "Heb je al een account?"
recover_password: "Wachtwoord herstellen"
email_in_use: "E-mailadres is al in gebruik"
email_or_username_in_use: "E-mailadres of gebruikersnaam is al in gebruik"
required_field: "Dit is een vereist veld"
passwords_dont_match: "Wachtwoorden komen niet overeen"
special_characters: "Gebruikersnamen kunnen alleen letters, cijfers en _ bevatten"
sign_in_to_comment: "Aanmelden om te reageren"
check_the_form: "Het formulier bevat ongeldige invoer, controleer je invoer"
createdisplay:
check_the_form: "Het formulier bevat ongeldige invoer, controleer je invoer"
continue: "Doorgaan met dezelfde Facebook gebruikersnaam"
error_create: "Fout opgetreden bij het wijzigen van de gebruikersnaam"
fake_comment_body: "Dit is een voorbeeldreactie. Lezers kunnen hun gedachten en meningen met newsrooms delen in het reactie-gedeelte"
fake_comment_date: "1 minuut geleden"
if_you_dont_change_your_name: "Wanneer je je gebruikersnaam nu niet wijzigt, zal je Facebook naam bij al je reacties komen te staan."
required_field: "Vereist veld"
save: Opslaan
special_characters: "Gebruikersnamen kunnen alleen letters, cijfers en _ bevatten"
username: Gebruikersnaam
write_your_username: "Wijzig je gebruikersnaam"
your_username: "Je gebruikersnaam verschijnt bij al je reacties."
talk-plugin-auth:
login:
email_verify_cta: "Controleer je e-mailadres."
request_new_verify_email: "Vraag nieuwe bevestigingsemail aan"
verify_email: "Bedankt voor het aanmaken van een account! We hebben een email verstuurd naar het adres dat je hebt opgegeven om je account te verifiëren."
verify_email2: "Je account moet worden geverifiëerd voordat je kunt deelnemen in de community."
not_you: "Ben je dit niet?"
logged_in_as: "Ingelogd als"
logout: "Uitloggen"
sign_in: "Inloggen"
sign_in_to_join: "Log in om deel te nemen"
or: "Of"
email: "E-mailadres"
password: "Wachtwoord"
password_error: "Wachtwoord moet minstens 8 karakters bevatten."
forgot_your_pass: "Wachtwoord vergeten?"
need_an_account: "Heb je een account nodig?"
register: "Registreren"
sign_up: "Aanmelden"
confirm_password: "Bevestig wachtwoord"
username: "Gebruikersnaam"
already_have_an_account: "Heb je al een account?"
recover_password: "Wachtwoord herstellen"
email_in_use: "E-mailadres is al in gebruik"
email_or_username_in_use: "E-mailadres of gebruikersnaam is al in gebruik"
required_field: "Dit is een vereist veld"
passwords_dont_match: "Wachtwoorden komen niet overeen"
special_characters: "Gebruikersnamen kunnen alleen letters, cijfers en _ bevatten"
sign_in_to_comment: "Aanmelden om te reageren"
check_the_form: "Het formulier bevat ongeldige invoer, controleer je invoer"
set_username_dialog:
check_the_form: "Het formulier bevat ongeldige invoer, controleer je invoer"
continue: "Doorgaan met dezelfde Facebook gebruikersnaam"
error_create: "Fout opgetreden bij het wijzigen van de gebruikersnaam"
fake_comment_body: "Dit is een voorbeeldreactie. Lezers kunnen hun gedachten en meningen met newsrooms delen in het reactie-gedeelte"
fake_comment_date: "1 minuut geleden"
if_you_dont_change_your_name: "Wanneer je je gebruikersnaam nu niet wijzigt, zal je Facebook naam bij al je reacties komen te staan."
required_field: "Vereist veld"
save: Opslaan
special_characters: "Gebruikersnamen kunnen alleen letters, cijfers en _ bevatten"
username: Gebruikersnaam
write_your_username: "Wijzig je gebruikersnaam"
your_username: "Je gebruikersnaam verschijnt bij al je reacties."
change_password:
change_password: "Wachtwoord Wijzigen"
passwords_dont_match: "Wachtwoorden komen niet overeen"
required_field: "Dit veld is verplicht"
forgot_password: "Wachtwoord vergeten?"
save: "Opslaan"
cancel: "Annuleren"
edit: "Wijzigen"
changed_password_msg: "Je wachtwoord is succesvol gewijzigd"
pt_BR:
talk-plugin-auth:
login:
@@ -2,6 +2,10 @@ ar:
talk-plugin-facebook-auth:
sign_in: "تسجيل الدخول عبر حساب الفيسبوك"
sign_up: "اشترك عبر حساب الفيسبوك"
de:
talk-plugin-facebook-auth:
sign_in: "Mit Facebook anmelden"
sign_up: "Mit Facebook registrieren"
en:
talk-plugin-facebook-auth:
sign_in: "Sign in with Facebook"
@@ -14,6 +18,10 @@ fr:
talk-plugin-facebook-auth:
sign_in: "Connectez-vous avec Facebook"
sign_up: "Inscrivez-vous avec Facebook"
nl_NL:
talk-plugin-facebook-auth:
sign_in: "Inloggen met Facebook"
sign_up: "Registreren met Facebook"
zh_CN:
talk-plugin-facebook-auth:
sign_in: "使用 Facebook 帐号"
@@ -22,7 +30,3 @@ zh_TW:
talk-plugin-facebook-auth:
sign_in: "使用 Facebook 帳號"
sign_up: "使用 Facebook 帳號"
de:
talk-plugin-facebook-auth:
sign_in: "Mit Facebook anmelden"
sign_up: "Mit Facebook registrieren"
@@ -14,6 +14,10 @@ fr:
talk-plugin-google-auth:
sign_in: "Connectez-vous avec Google"
sign_up: "Inscrivez-vous avec Google"
nl_NL:
talk-plugin-google-auth:
sign_in: "Inloggen met Google"
sign_up: "Registeren met Google"
zh_CN:
talk-plugin-google-auth:
sign_in: "使用 Google 帐号"
@@ -315,8 +315,8 @@ es:
save: "Salvar"
cancel: "Cancelar"
edit: "Editar"
changed_password_msg: "Senha alterada - Sua senha foi alterada com sucesso"
forgot_password_sent: "Esqueceu a senha - Nós enviamos um email para recuperação da senha"
changed_password_msg: "Contraseña cambiada - Su contraseña ha sido cambiado"
forgot_password_sent: "Contraseña olvidada - Enviamos un email para recuperar la contraseña"
change_password: "Cambiar Contraseña"
passwords_dont_match: "Las contraseñas no coinciden"
required_field: "Este campo es requerido"
@@ -376,3 +376,79 @@ es:
description_2: "Puedes cambiar la configuración de tu cuenta visitando"
path: "Mi perfil > Configuración"
alert: "¡Correo electrónico agregado!"
nl_NL:
email:
email_change_original:
subject: E-mailadres wijziging
body: "Je e-mailadres is gewijzigd van {0} naar {1}. Als je deze wijziging niet hebt aangevraagd, neem dan s.v.p. contact op: {2}."
error:
NO_LOCAL_PROFILE: Er is geen bestaand e-mailadres geassocieerd met dit account.
LOCAL_PROFILE: Er is reeds een e-mailadres geassocieerd met dit account.
INCORRECT_PASSWORD: Het opgegeven wachtwoord is onjuist.
talk-plugin-local-auth:
change_password:
change_password: "Wachtwoord wijzigen"
passwords_dont_match: "Wachtwoorden komen niet overeen"
required_field: "Dit veld is verplicht"
forgot_password: "Wachtwoord vergeten?"
old_password: "Oud Wachtwoord"
new_password: "Nieuw Wachtwoord"
confirm_new_password: "Bevestig Nieuw Wachtwoord"
save: "Opslaan"
cancel: "Annuleren"
edit: "Wijzigen"
changed_password_msg: "Wachtwoord Gewijzigd - Je wachtwoord is succesvol gewijzigd"
forgot_password_sent: "Wachtwoord Vergeten - We hebben je een e-mail gestuurd om je wachtwoord te herstellen"
change_username:
change_username_note: "Gebruikersnamen kunnen eens per 14 dagen worden gewijzigd."
is_not_eligible: "Je kunt je gebruikersnaam momenteel niet wijzigen."
save: "Opslaan"
edit_profile: "Profiel wijzigen"
cancel: "Annuleren"
confirm_username_change: "Bevestig Gebruikersnaam Wijziging"
description: "Je probeert je gebruikersnaam te wijzigen. Je nieuwe gebruikersnaam zal verschijnen bij al je huidige en toekomstige reacties."
old_username: "Oude Gebruikersnaam"
new_username: "Nieuwe Gebruikersnaam"
re_enter: "Voer je nieuwe gebruikersnaam opnieuw in"
bottom_note: "Let op: Je kan je gebruikersnaam niet opnieuw wijzigen in de komende 14 dagen."
confirm_changes: "Bevestig Wijzigingen"
username_does_not_match: "Gebruikersnaam komt niet overeen"
cant_be_equal: "Je nieuwe {0} moet verschillen van je huidige"
changed_username_success_msg: "Gebruikersnaam Gewijzigd - Je gebruikersnaam is succesvol gewijzigd. Je kan je gebruikersnaam niet wijzigen voor de komende 14 dagen."
change_username_attempt: "Gebruikersnaam kan niet worden gewijzigd. Gebruikersnamen kunnen eens per 14 dagen worden gewijzigd."
change_email:
confirm_email_change: "Bevestig E-mailadres Wijziging"
description: "Je probeert je e-mailadres te wijzigen. Je nieuwe e-mailadres zal worden gebruikt om in te loggen en voor het ontvangen van account notificaties."
old_email: "Oude E-mailadres"
new_email: "Nieuwe E-mailadres"
enter_password: "Wachtwoord Invoeren"
incorrect_password: "Ongeldig Wachtwoord"
confirm_change: "Bevestig Wijziging"
cancel: "Annuleren"
change_email_msg: "E-mailadres Gewijzigd. Dit E-mailadres zal nu worden gebruikt om in te loggen en voor het ontvangen van account notificaties."
add_email:
add_email_address: "E-mailadres Toevoegen"
enter_email_address: "E-mailadres Invoeren:"
invalid_email_address: "Ongeldig E-mailadres"
confirm_email_address: "Bevestig E-mailadres:"
email_does_not_match: "E-mailadres komt niet overeen"
insert_password: "Wachtwoord Invoeren:"
confirm_password: "Wachtwoord Bevestigen:"
required_field: "Dit veld is verplicht"
done: "Klaar"
content:
title: "Voeg een e-mailadres toe"
description: "Voor je veiligheid vragen we gebruikers om een e-mailadres toe te voegen aan hun account. Je e-mailadres zal worden gebruikt om:"
item_1: "Updates te ontvangen omtrent wijzigingen in je account (e-mailadres, gebruikersnaam, wachtwoord, etc.)"
item_2: "Je reacties te kunnen downloaden."
item_3: "Reactie notificaties te sturen indien je hebt gekozen deze te ontvangen."
verify:
title: "Bevestig Je E-mailadres"
description: "We hebben een e-mail gestuurd aan {0} om je account te bevestigen. Je moet je e-mailadres verifiëren zodat deze gebruikt kan worden voor bevestigingen omtrent account wijzigingen en voor notificaties."
added:
title: "E-mailadres Toegevoegd"
description: "Je e-mailadres is toegevoegd aan je account."
subtitle: "Wil je je e-mailadres wijzigen?"
description_2: "Je kan je account instellingen wijzigen door te gaan naar"
path: "Mijn Profiel > Instellingen"
alert: "E-mailadres Toegevoegd!"
@@ -24,8 +24,8 @@ fr:
loved: Loved
nl_NL:
talk-plugin-love:
love: Ik hou er van
loved: Geliefd
love: Love
loved: Loved
pt_BR:
talk-plugin-love:
love: Love
@@ -18,7 +18,7 @@ fr:
member_since: "Member Since"
nl_NL:
talk-plugin-member-since:
member_since: "Gebruiker sinds"
member_since: "Lid sinds"
pt_BR:
talk-plugin-member-since:
member_since: "Membro desde"
@@ -10,3 +10,6 @@ es:
de:
talk-plugin-notifications-category-reply:
toggle_description: Jemand antwortet auf meinen Kommentar
nl_NL:
talk-plugin-notifications-category-reply:
toggle_description: Iemand antwoord op mijn reactie
@@ -10,3 +10,6 @@ es:
de:
talk-plugin-notifications-category-staff:
toggle_description: Ein Redaktionsmitglied antwortet auf meinen Kommentar
nl_NL:
talk-plugin-notifications-category-staff:
toggle_description: Een redactielid antwoord op mijn reactie
@@ -23,3 +23,9 @@ de:
staff:
subject: "Jemand hat bei {0} auf Ihren Kommentar geantwortet"
body: "{0}\n{1} arbeitet für {2} und hat auf Ihren Kommentar geantwortet: {3}"
nl_NL:
talk-plugin-notifications:
categories:
staff:
subject: "Iemand bij {0} heeft geantwoord op je reactie"
body: "{0}\n{1} werkt voor {2} en heeft geantwoord op je reactie: {3}"
@@ -14,3 +14,7 @@ de:
talk-plugin-notifications:
digest_enum:
DAILY: Einmal täglich
nl_NL:
talk-plugin-notifications:
digest_enum:
DAILY: In een dagelijkse samenvatting
@@ -14,3 +14,7 @@ de:
talk-plugin-notifications:
digest_enum:
HOURLY: Stündlich
nl_NL:
talk-plugin-notifications:
digest_enum:
HOURLY: In een uurlijkse samenvatting
@@ -70,3 +70,21 @@ de:
digest_option: Benachrichtigungen senden
digest_enum:
NONE: Sofort
nl_NL:
talk-plugin-notifications:
settings_title: Notificaties
settings_subtitle: Ontvang notificaties wanneer
turn_off_all: Ik wil geen notificaties ontvangen
banner_info:
title: E-mail bevestiging vereist
text: Je moet een geverifieerd e-mailadres hebben om e-mail notificaties te ontvangen
verify_now: Verifieer je e-mailadres nu
banner_success:
title: E-mail verificatie verzonden
text: Een e-mail met verificatielink is verstuurd naar {0}.
banner_error:
title: Fout
text: Er is een fout opgetreden tijdens het versturen van je verificatie e-mail. Probeer het later nog eens.
digest_option: Notificaties versturen
digest_enum:
NONE: Onmiddelijk
@@ -40,3 +40,17 @@ de:
confirm: "Bestätigen"
are_unsubscribed: "Sie haben haben alle Benachrichtigungen erfolgreich abbestellt."
token_invalid: "Der Abbestell-Link ist ungültig. Klicken Sie den Link einer neueren E-Mail oder gehen Sie zu einem Kommentarbereich, melden Sie sich an und ändern Sie dort Ihre Benachrichtigungseinstellungen"
nl_NL:
talk-plugin-notifications:
templates:
digest:
subject: "Je recente reactie-activiteit op {0}"
footer: "Je hebt deze notificatie ontvangen omdat je een reageerder bent op {0} en je hebt je aangemeld om notificaties te ontvangen."
links:
unsubscribe: "Afmelden voor reactie notificaties"
unsubscribe_page:
unsubscribe: "Afmelden voor reactie notificaties"
click_to_confirm: "Klik onderstaande om te bevestigen dat je je wilt afmelden voor alle notificaties"
confirm: "Bevestigen"
are_unsubscribed: "Je bent nu afgemeld voor alle notificaties."
token_invalid: "Afmeldlink is ongeldig, klik de link van een recentere e-mail of bezoek een pagina met reacties en log in om je notificatie voorkeuren te wijzigen"
+9 -20
View File
@@ -10,7 +10,7 @@ const compression = require('compression');
const plugins = require('../services/plugins');
const staticTemplate = require('../middleware/staticTemplate');
const nonce = require('../middleware/nonce');
const staticServer = require('express-static-gzip');
const staticFiles = require('../middleware/staticFiles');
const { DISABLE_STATIC_SERVER } = require('../config');
const { passport } = require('../services/passport');
const { MOUNT_PATH } = require('../url');
@@ -32,6 +32,8 @@ if (!DISABLE_STATIC_SERVER) {
/**
* Redirect old embed calls.
*
* TODO: (wyattjoh) remove this on the next minor release
*/
const oldEmbed = url.resolve(MOUNT_PATH, 'embed.js');
const newEmbed = url.resolve(MOUNT_PATH, 'static/embed.js');
@@ -43,28 +45,15 @@ if (!DISABLE_STATIC_SERVER) {
});
/**
* Serve the directories under dist.
* Setup static file serving.
*/
const dist = path.resolve(path.join(__dirname, '../dist'));
if (process.env.NODE_ENV === 'production') {
router.use(
'/static',
staticServer(dist, {
indexFromEmptyFile: false,
enableBrotli: true,
customCompressions: [
{
encodingName: 'deflate',
fileExtension: 'zz',
},
],
})
);
} else {
router.use('/static', express.static(dist));
}
router.use('/static', staticFiles);
}
//==============================================================================
// Shared Middleware
//==============================================================================
// Add the i18n middleware to all routes.
router.use(i18n);
+2 -2
View File
@@ -91,12 +91,12 @@ class Task {
static shutdown() {
debug('Shutting down the Queue');
return new Promise((resolve, reject) => {
return new Promise(resolve => {
// Shutdown and give the queue 5 seconds to shutdown before we start
// killing jobs.
getQueue().shutdown(5000, err => {
if (err) {
return reject(err);
return resolve();
}
debug('Queue shut down.');
+30 -13
View File
@@ -159,6 +159,9 @@ const config = {
'process.env': {
VERSION: `"${require('./package.json').version}"`,
NODE_ENV: `${JSON.stringify(process.env.NODE_ENV)}`,
TALK_DEFAULT_LAZY_RENDER: `${JSON.stringify(
process.env.TALK_DEFAULT_LAZY_RENDER
)}`,
},
}),
new webpack.EnvironmentPlugin({
@@ -307,19 +310,32 @@ const applyConfig = (entries, root = {}) =>
config,
{
entry: entries.reduce(
(entry, { name, path: modulePath, disablePolyfill = false }) => {
const entries = [
path.join(
__dirname,
'client/coral-framework/helpers/webpackGlobals'
),
];
if (disablePolyfill) {
entries.push(modulePath);
} else {
entries.unshift('babel-polyfill');
entries.push(modulePath);
(
entry,
{
name,
path: modulePath,
disablePolyfill = false,
disableWebpackGlobals = false,
}
) => {
// Create all the entries to be added to the final build target.
const entries = [];
if (!disablePolyfill) {
entries.push('babel-polyfill');
}
if (!disableWebpackGlobals) {
entries.push(
path.join(
__dirname,
'client/coral-framework/helpers/webpackGlobals'
)
);
}
entries.push(modulePath);
entry[name] = entries;
@@ -347,7 +363,8 @@ module.exports = [
{
name: 'embed',
path: path.join(__dirname, 'client/coral-embed/src/index'),
disablePolyfill: process.env.TALK_DISABLE_EMBED_POLYFILL === 'TRUE',
disablePolyfill: true,
disableWebpackGlobals: true,
},
],
{
+21 -9
View File
@@ -221,6 +221,11 @@
dependencies:
"@types/node" "*"
"@ungap/url-search-params@^0.1.2":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@ungap/url-search-params/-/url-search-params-0.1.2.tgz#8ba8c0527543fe675d1c29ae0a2daca842e8ee4f"
integrity sha512-WVk5+lJ+AoNLh2sIDMhnEAgLsVQuI067hWLJCzirErH1GYiy1gs09q4+XZxYWSvdAsslKsaO4q1iXXMx2c72dA==
JSONStream@^1.0.7:
version "1.3.3"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.3.tgz#27b4b8fbbfeab4e71bcf551e7f27be8d952239bf"
@@ -4277,10 +4282,10 @@ exports-loader@^0.6.4:
loader-utils "^1.0.2"
source-map "0.5.x"
express-static-gzip@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/express-static-gzip/-/express-static-gzip-0.3.2.tgz#89ede84547a5717de3146315f62dc996c071a88d"
integrity sha512-xFOW5Lxrh4xLey5i6gGWHOFznJayGCxazUau0kq7ElUh1t7q2B6IlvWv4d3UJwJej+aXEu9os/VpzPvRchdNiA==
express-static-gzip@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/express-static-gzip/-/express-static-gzip-1.1.3.tgz#345ea02637d9d5865777d6fb57ccc0884abcda65"
integrity sha512-k8Q4Dx4PDpzEb8kth4uiPWrBeJWJYSgnWMzNdjQUOsEyXfYKbsyZDkU/uXYKcorRwOie5Vzp4RMEVrJLMfB6rA==
dependencies:
serve-static "^1.12.3"
@@ -6042,6 +6047,11 @@ interpret@^1.0.0, interpret@^1.0.1:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=
intersection-observer@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.5.1.tgz#e340fc56ce74290fe2b2394d1ce88c4353ac6dfa"
integrity sha512-Zd7Plneq82kiXFixs7bX62YnuZ0BMRci9br7io88LwDyF3V43cQMI+G5IiTlTNTt+LsDUppl19J/M2Fp9UkH6g==
invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@@ -12813,6 +12823,13 @@ throat@^4.0.0:
resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=
throng@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/throng/-/throng-4.0.0.tgz#983c6ba1993b58eae859998aa687ffe88df84c17"
integrity sha1-mDxroZk7WOroWZmKpof/6I34TBc=
dependencies:
lodash.defaults "^4.0.1"
through2@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
@@ -13357,11 +13374,6 @@ url-regex@~4.1.1:
ip-regex "^1.0.1"
tlds "^1.187.0"
url-search-params@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/url-search-params/-/url-search-params-0.9.0.tgz#e71d7764a6503533cbfe9771b2963cb61ea1c225"
integrity sha1-5x13ZKZQNTPL/pdxspY8th6hwiU=
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"