+ }
{
diff --git a/client/coral-embed-stream/src/components/InactiveCommentLabel.css b/client/coral-embed-stream/src/components/InactiveCommentLabel.css
new file mode 100644
index 000000000..01ba197b5
--- /dev/null
+++ b/client/coral-embed-stream/src/components/InactiveCommentLabel.css
@@ -0,0 +1,32 @@
+.root {
+ display: inline-block;
+ color: white;
+ background: grey;
+ height: 22px;
+ box-sizing: border-box;
+ line-height: 19px;
+ padding: 2px 6px 2px 4px;
+ border-radius: 2px;
+ font-size: 12px;
+ text-transform: capitalize;
+}
+
+.icon {
+ font-size: 14px;
+ vertical-align: text-top;
+ margin: 0;
+ margin-right: 4px;
+}
+
+.label {
+ display: inline-block;
+}
+
+.premod {
+ background: #063B9A;
+}
+
+.rejected {
+ background: #d03235;
+}
+
diff --git a/client/coral-embed-stream/src/components/InactiveCommentLabel.js b/client/coral-embed-stream/src/components/InactiveCommentLabel.js
new file mode 100644
index 000000000..824692558
--- /dev/null
+++ b/client/coral-embed-stream/src/components/InactiveCommentLabel.js
@@ -0,0 +1,36 @@
+import React, {PropTypes} from 'react';
+import t from 'coral-framework/services/i18n';
+import styles from './InactiveCommentLabel.css';
+import {Icon} from 'coral-ui';
+import cn from 'classnames';
+
+const InactiveCommentLabel = ({status, className, ...rest}) => {
+ let label;
+ let icon;
+
+ switch (status) {
+ case 'PREMOD':
+ label = t('modqueue.premod');
+ icon = 'query_builder';
+ break;
+ case 'REJECTED':
+ label = t('modqueue.rejected');
+ icon = 'close';
+ break;
+ default:
+ throw new Error(`Unknown inactive status ${status}`);
+ }
+
+ return (
+
+
+ {label}
+
+ );
+};
+
+InactiveCommentLabel.propTypes = {
+ status: PropTypes.string.isRequired,
+};
+
+export default InactiveCommentLabel;
diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js
index 66ffc0fc8..421a0cf7a 100644
--- a/client/coral-embed-stream/src/components/Stream.js
+++ b/client/coral-embed-stream/src/components/Stream.js
@@ -13,6 +13,7 @@ import t, {timeago} from 'coral-framework/services/i18n';
import {getSlotComponents} from 'coral-framework/helpers/plugins';
import CommentBox from 'talk-plugin-commentbox/CommentBox';
import QuestionBox from 'talk-plugin-questionbox/QuestionBox';
+import {isCommentActive} from 'coral-framework/utils';
import {Button, TabBar, Tab, TabCount, TabContent, TabPane} from 'coral-ui';
import cn from 'classnames';
@@ -105,7 +106,9 @@ class Stream extends React.Component {
// even though the permalinked comment is the highlighted one, we're displaying its parent + replies
let highlightedComment = comment && getTopLevelParent(comment);
if (highlightedComment) {
- const isInactive = ['NONE', 'ACCEPTED'].indexOf(comment.status) === -1;
+
+ // Inactive comments can be viewed by moderators and admins (e.g. using permalinks).
+ const isInactive = !isCommentActive(comment.status);
if (comment.parent && isInactive) {
// the highlighted comment is not active and as such not in the replies, so we
diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js
index 64c274660..dd44e72cd 100644
--- a/client/coral-embed/src/index.js
+++ b/client/coral-embed/src/index.js
@@ -267,6 +267,11 @@ Talk.render = function(el, opts) {
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.'
);
+
+ if (!window.location.origin) {
+ window.location.origin = `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}`;
+ }
+
query.asset_url = window.location.origin + window.location.pathname;
}
}
diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js
index 3768b3940..96d9ed629 100644
--- a/client/coral-framework/utils/index.js
+++ b/client/coral-framework/utils/index.js
@@ -187,3 +187,7 @@ export function buildUrl({protocol, hostname, port, pathname, search, hash} = wi
export function getSlotFragmentSpreads(slots, resource) {
return `...${slots.map((s) => `TalkSlot_${capitalize(s)}_${resource}`).join('\n...')}\n`;
}
+
+export function isCommentActive(commentStatus) {
+ return ['NONE', 'ACCEPTED'].indexOf(commentStatus) >= 0;
+}
diff --git a/config.js b/config.js
index c6ea774a3..02b40681d 100644
--- a/config.js
+++ b/config.js
@@ -78,6 +78,12 @@ const CONFIG = {
// messages through the websocket to keep the socket alive.
KEEP_ALIVE: process.env.TALK_KEEP_ALIVE || '30s',
+ //------------------------------------------------------------------------------
+ // Cache configuration
+ //------------------------------------------------------------------------------
+
+ CACHE_EXPIRY_COMMENT_COUNT: process.env.TALK_CACHE_EXPIRY_COMMENT_COUNT || '1hr',
+
//------------------------------------------------------------------------------
// Recaptcha configuration
//------------------------------------------------------------------------------
diff --git a/docs/_docs/02-01-configuration.md b/docs/_docs/02-01-configuration.md
index ab644e5e3..d2e990375 100644
--- a/docs/_docs/02-01-configuration.md
+++ b/docs/_docs/02-01-configuration.md
@@ -45,6 +45,7 @@ These are only used during the webpack build.
thread. (Default `3`)
- `TALK_DEFAULT_STREAM_TAB` (_optional_) - specify the default stream tab in the
admin. (Default `all`)
+- `TALK_DISABLE_EMBED_POLYFILL` (_optional_) - when set to `TRUE`, the build process will not include the [babel-polyfill](https://babeljs.io/docs/usage/polyfill/) in the embed.js target. (Default `FALSE`)
### Database
@@ -127,6 +128,11 @@ The default could be read as:
- At the moment of writing, beheviour is not attached to the flagging
reliability, but it is recorded.
+### Cache
+
+- `TALK_CACHE_EXPIRY_COMMENT_COUNT` (_optional_) - configure the duration for which
+ comment counts are cached for. (Default `1hr`)
+
### Plugins
Plugins configuration can be found on the [Plugins]({{ "/docs/running/plugins/" | absolute_url }}) page.
\ No newline at end of file
diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js
index 16d2101f7..7a17cfabd 100644
--- a/graph/loaders/comments.js
+++ b/graph/loaders/comments.js
@@ -8,6 +8,10 @@ const {
SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS,
SEARCH_OTHERS_COMMENTS
} = require('../../perms/constants');
+const {
+ CACHE_EXPIRY_COMMENT_COUNT
+} = require('../../config');
+const ms = require('ms');
const CommentModel = require('../../models/comment');
const UsersService = require('../../services/users');
@@ -481,11 +485,11 @@ module.exports = (context) => ({
get: new DataLoader((ids) => getComments(context, ids)),
getByQuery: (query) => getCommentsByQuery(context, query),
getCountByQuery: (query) => getCommentCountByQuery(context, query),
- countByAssetID: new SharedCounterDataLoader('Comments.totalCommentCount', 3600, (ids) => getCountsByAssetID(context, ids)),
+ countByAssetID: new SharedCounterDataLoader('Comments.totalCommentCount', ms(CACHE_EXPIRY_COMMENT_COUNT), (ids) => getCountsByAssetID(context, ids)),
countByAssetIDPersonalized: (query) => getCountsByAssetIDPersonalized(context, query),
- parentCountByAssetID: new SharedCounterDataLoader('Comments.countByAssetID', 3600, (ids) => getParentCountsByAssetID(context, ids)),
+ parentCountByAssetID: new SharedCounterDataLoader('Comments.countByAssetID', ms(CACHE_EXPIRY_COMMENT_COUNT), (ids) => getParentCountsByAssetID(context, ids)),
parentCountByAssetIDPersonalized: (query) => getParentCountByAssetIDPersonalized(context, query),
- countByParentID: new SharedCounterDataLoader('Comments.countByParentID', 3600, (ids) => getCountsByParentID(context, ids)),
+ countByParentID: new SharedCounterDataLoader('Comments.countByParentID', ms(CACHE_EXPIRY_COMMENT_COUNT), (ids) => getCountsByParentID(context, ids)),
countByParentIDPersonalized: (query) => getCountByParentIDPersonalized(context, query),
genRecentReplies: new DataLoader((ids) => genRecentReplies(context, ids)),
genRecentComments: new DataLoader((ids) => genRecentComments(context, ids))
diff --git a/webpack.config.js b/webpack.config.js
index 1b3c4b763..873d20472 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -33,6 +33,7 @@ const buildEmbeds = [
const config = {
devtool: 'cheap-module-source-map',
+ target: 'web',
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/client/',
@@ -153,11 +154,15 @@ if (process.env.NODE_ENV === 'production') {
// Applies the base configuration to the following entries.
const applyConfig = (entries, root = {}) => _.merge({}, config, {
- entry: entries.reduce((entry, {name, path}) => {
- entry[name] = [
- 'babel-polyfill',
- path
- ];
+ entry: entries.reduce((entry, {name, path, disablePolyfill = false}) => {
+ if (disablePolyfill) {
+ entry[name] = path;
+ } else {
+ entry[name] = [
+ 'babel-polyfill',
+ path
+ ];
+ }
return entry;
}, {})
@@ -171,7 +176,8 @@ module.exports = [
// Load in the root embed.
{
name: 'embed',
- path: path.join(__dirname, 'client/coral-embed/src/index')
+ path: path.join(__dirname, 'client/coral-embed/src/index'),
+ disablePolyfill: process.env.TALK_DISABLE_EMBED_POLYFILL === 'TRUE'
}
], {
@@ -183,7 +189,7 @@ module.exports = [
// All framework targets/embeds/plugins.
applyConfig([
- // // Load in all the targets.
+ // Load in all the targets.
...buildTargets.map((target) => ({
name: `${target}/bundle`,
path: path.join(__dirname, 'client/', target, '/src/index')