mirror of
https://github.com/wassname/talk.git
synced 2026-09-13 13:10:43 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e464c48df | ||
|
|
fd64469c00 | ||
|
|
e488e878d0 | ||
|
|
9f9f5a8a8e | ||
|
|
77e54aab1f | ||
|
|
9109d5ab71 | ||
|
|
104b719dec | ||
|
|
9e69cecb11 | ||
|
|
791f9bdabd | ||
|
|
8d183915fa | ||
|
|
9e0d35e737 | ||
|
|
21f436255e | ||
|
|
6ba7a43cb4 | ||
|
|
331a83d1fc | ||
|
|
8d3de4b082 | ||
|
|
c4801abfbe | ||
|
|
c0861c20d3 | ||
|
|
63545a48c4 | ||
|
|
e1bfe5b608 | ||
|
|
dc096137e3 | ||
|
|
4d41d999fb | ||
|
|
b50d3dc96f | ||
|
|
8c0f55af21 | ||
|
|
ed98b218bb | ||
|
|
64800ffaee | ||
|
|
637605a002 | ||
|
|
5580e14bf3 | ||
|
|
109d9e93f5 | ||
|
|
ff72d79748 | ||
|
|
0645735d2b | ||
|
|
f1a0febd6c | ||
|
|
16d0b39ebc | ||
|
|
66c7430ff1 | ||
|
|
ac06f0d13b | ||
|
|
ca10062498 | ||
|
|
c9381c6367 | ||
|
|
13853ef87e | ||
|
|
fb2f36d0ee | ||
|
|
1a2fa73941 | ||
|
|
d3931db285 | ||
|
|
093202f9e2 | ||
|
|
60be682a5f | ||
|
|
41c9ae32b1 | ||
|
|
4445479237 | ||
|
|
aab44587a6 | ||
|
|
0af28edbd9 | ||
|
|
9db7571fe2 | ||
|
|
683adceec4 | ||
|
|
aa6058e9d1 | ||
|
|
a4c4032b0d | ||
|
|
2ea0d51613 | ||
|
|
f2564f4eda | ||
|
|
73040b1314 | ||
|
|
c3c106c7b0 | ||
|
|
8faf69ce48 | ||
|
|
23f17db0c9 | ||
|
|
1385a0b961 | ||
|
|
7fd01e5845 | ||
|
|
340052cdf0 | ||
|
|
a085e4b6f9 | ||
|
|
1575b15e36 | ||
|
|
7da9126a76 | ||
|
|
9453d207f5 | ||
|
|
869c760a8b | ||
|
|
b283595c97 | ||
|
|
d672af63f9 | ||
|
|
b4ad78fd65 | ||
|
|
713de46c2a | ||
|
|
fc1e51ed62 | ||
|
|
4eef6f4218 | ||
|
|
c8cfd7c0f8 | ||
|
|
0e4cbf5f44 | ||
|
|
448b988281 | ||
|
|
7a688495f2 | ||
|
|
b9cf7084eb | ||
|
|
b99161f0cb | ||
|
|
6faa4862de | ||
|
|
eff3f1dfd8 | ||
|
|
73aa77d7da | ||
|
|
5580ab385f | ||
|
|
40eb0e97ec | ||
|
|
b7e1ce0205 | ||
|
|
bfde9bf8c7 | ||
|
|
54f9b28086 | ||
|
|
03a5eb7f9b | ||
|
|
43e60e6544 | ||
|
|
ebd16666e6 | ||
|
|
f2129082a9 | ||
|
|
1098c068f2 | ||
|
|
f0c95d0044 | ||
|
|
74226aa6fc | ||
|
|
b6ed5b792b | ||
|
|
d31969592c | ||
|
|
794487b1a9 | ||
|
|
39e45c30ba | ||
|
|
ed734ebb08 | ||
|
|
9f059e0c4a | ||
|
|
7d369f2d35 | ||
|
|
5fb5240f89 | ||
|
|
3404578ecc | ||
|
|
48c5355a5b |
+26
-28
@@ -1,4 +1,3 @@
|
||||
|
||||
# job_environment will setup the environment for any job being executed.
|
||||
job_environment: &job_environment
|
||||
NODE_ENV: test
|
||||
@@ -54,7 +53,6 @@ integration_job: &integration_job
|
||||
when: always
|
||||
path: /tmp/circleci-test-results
|
||||
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
# npm_dependencies will install the dependencies used by all other steps.
|
||||
@@ -69,7 +67,7 @@ jobs:
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
yarn global add node-gyp &&
|
||||
yarn global add node-gyp@6.1.0 &&
|
||||
yarn install --frozen-lockfile
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ checksum "yarn.lock" }}
|
||||
@@ -223,20 +221,17 @@ jobs:
|
||||
# only execute on a deploy related job.
|
||||
filter_deploy: &filter_deploy
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- next
|
||||
tags:
|
||||
only: /v[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
|
||||
# filter_develop will add the filters for a development related commit.
|
||||
filter_develop: &filter_develop
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- next
|
||||
- /release\/.*/
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
@@ -258,10 +253,11 @@ workflows:
|
||||
<<: *filter_develop
|
||||
requires:
|
||||
- npm_dependencies
|
||||
- test_integration_chrome_local:
|
||||
<<: *filter_develop
|
||||
requires:
|
||||
- build_assets
|
||||
# TODO: uncomment when we revisit browserstack
|
||||
# - test_integration_chrome_local:
|
||||
# <<: *filter_develop
|
||||
# requires:
|
||||
# - build_assets
|
||||
# TODO: uncomment when more reliable
|
||||
# - test_integration_firefox_local:
|
||||
# <<: *filter_develop
|
||||
@@ -283,18 +279,19 @@ workflows:
|
||||
<<: *filter_deploy
|
||||
requires:
|
||||
- npm_dependencies
|
||||
- test_integration_chrome:
|
||||
<<: *filter_deploy
|
||||
requires:
|
||||
- build_assets
|
||||
- test_integration_firefox:
|
||||
<<: *filter_deploy
|
||||
requires:
|
||||
- build_assets
|
||||
- test_integration_edge:
|
||||
<<: *filter_deploy
|
||||
requires:
|
||||
- build_assets
|
||||
# TODO: uncomment when we revisit browserstack
|
||||
# - test_integration_chrome:
|
||||
# <<: *filter_deploy
|
||||
# requires:
|
||||
# - build_assets
|
||||
# - test_integration_firefox:
|
||||
# <<: *filter_deploy
|
||||
# requires:
|
||||
# - build_assets
|
||||
# - test_integration_edge:
|
||||
# <<: *filter_deploy
|
||||
# requires:
|
||||
# - build_assets
|
||||
# TODO: uncomment when more reliable
|
||||
# - test_integration_ie:
|
||||
# <<: *filter_deploy
|
||||
@@ -309,9 +306,10 @@ workflows:
|
||||
requires:
|
||||
- lint
|
||||
- test_unit
|
||||
- test_integration_chrome
|
||||
- test_integration_firefox
|
||||
- test_integration_edge
|
||||
# TODO: uncomment when we revisit browserstack
|
||||
# - test_integration_chrome
|
||||
# - test_integration_firefox
|
||||
# - test_integration_edge
|
||||
# TODO: uncomment when more reliable
|
||||
# - test_integration_ie
|
||||
# - test_integration_safari
|
||||
|
||||
+2
-1
@@ -20,7 +20,8 @@ ARG REVISION_HASH
|
||||
ENV REVISION_HASH=${REVISION_HASH}
|
||||
|
||||
# Install app dependencies and build static assets.
|
||||
RUN yarn global add node-gyp && \
|
||||
# Pin node-gyp@6.1.0 as newer versions do not build on Node 8.
|
||||
RUN yarn global add node-gyp@6.1.0 && \
|
||||
yarn install --frozen-lockfile && \
|
||||
yarn build && \
|
||||
yarn cache clean
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2018 Mozilla Foundation
|
||||
Copyright 2019 Vox Media, Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Talk · [](https://circleci.com/gh/coralproject/talk) · [](CONTRIBUTING.md#pull-requests)
|
||||
|
||||
---
|
||||
|
||||
> :construction: **NOTE:** We're currently building v5 of this platform, check it out in our [next branch](https://github.com/coralproject/talk/tree/next) :construction_worker_man: :construction_worker_woman:
|
||||
|
||||
---
|
||||
|
||||
Online comments are broken. Our open-source commenting platform, Talk, rethinks how moderation, comment display, and conversation function, creating the opportunity for safer, smarter discussions around your work. [Read more about Talk here](https://coralproject.net/talk).
|
||||
|
||||
Built with <3 by The Coral Project, a part of [Vox Media](https://www.voxmedia.com/).
|
||||
|
||||
@@ -11,7 +11,12 @@ const { HELMET_CONFIGURATION } = require('./config');
|
||||
const { MOUNT_PATH } = require('./url');
|
||||
const routes = require('./routes');
|
||||
const debug = require('debug')('talk:app');
|
||||
const { ENABLE_TRACING, APOLLO_ENGINE_KEY, PORT } = require('./config');
|
||||
const {
|
||||
ENABLE_TRACING,
|
||||
APOLLO_ENGINE_KEY,
|
||||
PORT,
|
||||
TRUST_PROXY,
|
||||
} = require('./config');
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -58,7 +63,26 @@ if (ENABLE_TRACING && APOLLO_ENGINE_KEY) {
|
||||
|
||||
// Trust the first proxy in front of us, this will enable us to trust the fact
|
||||
// that SSL was terminated correctly.
|
||||
app.set('trust proxy', 1);
|
||||
app.set(
|
||||
'trust proxy',
|
||||
(function() {
|
||||
if (!TRUST_PROXY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lowercase = TRUST_PROXY.toLowerCase();
|
||||
if (lowercase === 'true' || lowercase === 'false') {
|
||||
return lowercase === 'true';
|
||||
}
|
||||
|
||||
const parsed = Number(TRUST_PROXY);
|
||||
if (!isNaN(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return TRUST_PROXY;
|
||||
})()
|
||||
);
|
||||
|
||||
// Enable a suite of security good practices through helmet. We disable
|
||||
// frameguard to allow crossdomain injection of the embed.
|
||||
|
||||
@@ -4,13 +4,15 @@ import {
|
||||
FETCH_ASSETS_REQUEST,
|
||||
FETCH_ASSETS_SUCCESS,
|
||||
FETCH_ASSETS_FAILURE,
|
||||
LOAD_MORE_ASSETS_REQUEST,
|
||||
LOAD_MORE_ASSETS_SUCCESS,
|
||||
LOAD_MORE_ASSETS_FAILURE,
|
||||
SET_PAGE,
|
||||
SET_SEARCH_VALUE,
|
||||
SET_CRITERIA,
|
||||
UPDATE_ASSET_STATE_REQUEST,
|
||||
UPDATE_ASSET_STATE_SUCCESS,
|
||||
UPDATE_ASSET_STATE_FAILURE,
|
||||
UPDATE_ASSETS,
|
||||
} from '../constants/stories';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
@@ -21,17 +23,14 @@ import t from 'coral-framework/services/i18n';
|
||||
|
||||
// Fetch a page of assets
|
||||
// Get comments to fill each of the three lists on the mod queue
|
||||
export const fetchAssets = (query = {}) => (dispatch, _, { rest }) => {
|
||||
export const fetchAssets = (query = {}) => (dispatch, _, { rest2 }) => {
|
||||
dispatch({ type: FETCH_ASSETS_REQUEST });
|
||||
return rest(`/assets?${queryString.stringify(query)}`)
|
||||
.then(({ result, page, count, limit, totalPages }) =>
|
||||
return rest2(`/stories?${queryString.stringify(query)}`)
|
||||
.then(({ edges, pageInfo }) =>
|
||||
dispatch({
|
||||
type: FETCH_ASSETS_SUCCESS,
|
||||
assets: result,
|
||||
page,
|
||||
count,
|
||||
limit,
|
||||
totalPages,
|
||||
edges,
|
||||
pageInfo,
|
||||
})
|
||||
)
|
||||
.catch(error => {
|
||||
@@ -43,12 +42,31 @@ export const fetchAssets = (query = {}) => (dispatch, _, { rest }) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const loadMoreAssets = (query = {}) => (dispatch, _l, { rest2 }) => {
|
||||
dispatch({ type: LOAD_MORE_ASSETS_REQUEST });
|
||||
return rest2(`/stories?${queryString.stringify(query)}`)
|
||||
.then(({ edges, pageInfo }) =>
|
||||
dispatch({
|
||||
type: LOAD_MORE_ASSETS_SUCCESS,
|
||||
edges,
|
||||
pageInfo,
|
||||
})
|
||||
)
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
const errorMessage = error.translation_key
|
||||
? t(`error.${error.translation_key}`)
|
||||
: error.toString();
|
||||
dispatch({ type: LOAD_MORE_ASSETS_FAILURE, error: errorMessage });
|
||||
});
|
||||
};
|
||||
|
||||
// Update an asset state
|
||||
// Get comments to fill each of the three lists on the mod queue
|
||||
export const updateAssetState = (id, closedAt) => (dispatch, _, { rest }) => {
|
||||
dispatch({ type: UPDATE_ASSET_STATE_REQUEST, id, closedAt });
|
||||
dispatch({ type: UPDATE_ASSET_STATE_REQUEST });
|
||||
return rest(`/assets/${id}/status`, { method: 'PUT', body: { closedAt } })
|
||||
.then(() => dispatch({ type: UPDATE_ASSET_STATE_SUCCESS }))
|
||||
.then(() => dispatch({ type: UPDATE_ASSET_STATE_SUCCESS, id, closedAt }))
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
const errorMessage = error.translation_key
|
||||
@@ -58,10 +76,6 @@ export const updateAssetState = (id, closedAt) => (dispatch, _, { rest }) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const updateAssets = assets => dispatch => {
|
||||
dispatch({ type: UPDATE_ASSETS, assets });
|
||||
};
|
||||
|
||||
export const setPage = page => ({
|
||||
type: SET_PAGE,
|
||||
page,
|
||||
|
||||
@@ -73,7 +73,7 @@ class AdminLogin extends React.Component {
|
||||
this.setState({ requestPassword: true });
|
||||
}}
|
||||
>
|
||||
{t('login.request_passowrd')}
|
||||
{t('login.request_password')}
|
||||
</a>
|
||||
</p>
|
||||
{loginMaxExceeded && (
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
background-color: #696969;
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2),
|
||||
0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.header > div {
|
||||
@@ -41,7 +42,7 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 170px;
|
||||
width: 200px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -85,7 +86,7 @@
|
||||
background-color: transparent;
|
||||
transition: background-color 200ms;
|
||||
font-weight: 100;
|
||||
letter-spacing: .8;
|
||||
letter-spacing: 0.8;
|
||||
|
||||
&:hover {
|
||||
background-color: #404040;
|
||||
|
||||
@@ -79,7 +79,7 @@ class SignIn extends React.Component {
|
||||
className={styles.forgotPasswordLink}
|
||||
onClick={this.handleForgotPasswordLink}
|
||||
>
|
||||
{t('login.request_passowrd')}
|
||||
{t('login.request_password')}
|
||||
</a>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
color: #063b9a;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
letter-spacing: .5px;
|
||||
letter-spacing: 0.5px;
|
||||
margin-left: 10px;
|
||||
font-size: 13px;
|
||||
margin-left: 5px;
|
||||
@@ -109,7 +109,7 @@
|
||||
}
|
||||
|
||||
.external {
|
||||
font-size: .7em;
|
||||
font-size: 0.7em;
|
||||
text-decoration: none;
|
||||
color: #063b9a;
|
||||
cursor: pointer;
|
||||
@@ -119,7 +119,7 @@
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
opacity: .9;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
> i {
|
||||
@@ -139,3 +139,25 @@
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.bodyHistoryToggle {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
line-height: 1px;
|
||||
font-weight: 300;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.editedCommentBody {
|
||||
padding: 0 5px 5px 5px;
|
||||
}
|
||||
|
||||
.editedComment {
|
||||
margin-top: 6px;
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import TimeAgo from 'coral-framework/components/TimeAgo';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
class UserDetailComment extends React.Component {
|
||||
state = { showingEditHistory: false };
|
||||
|
||||
approve = () =>
|
||||
this.props.comment.status === 'ACCEPTED'
|
||||
? null
|
||||
@@ -29,6 +31,29 @@ class UserDetailComment extends React.Component {
|
||||
? null
|
||||
: this.props.rejectComment({ commentId: this.props.comment.id });
|
||||
|
||||
getBodyHistory = () => {
|
||||
const bodyHistory = [];
|
||||
const comment = this.props.comment;
|
||||
for (let i = 0; i < comment.body_history.length - 1; i++) {
|
||||
bodyHistory.push(
|
||||
<div key={i} className={styles.editedComment}>
|
||||
<div>
|
||||
<TimeAgo className={styles.created} datetime={comment.created_at} />
|
||||
</div>
|
||||
<div className={styles.editedCommentBody}>
|
||||
{comment.body_history[i].body}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return bodyHistory;
|
||||
};
|
||||
|
||||
toggleEditHistory = () => {
|
||||
this.setState({ showingEditHistory: !this.state.showingEditHistory });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
comment,
|
||||
@@ -81,6 +106,14 @@ class UserDetailComment extends React.Component {
|
||||
<span className={styles.editedMarker}>
|
||||
({t('comment.edited')})
|
||||
</span>
|
||||
<span
|
||||
className={styles.bodyHistoryToggle}
|
||||
onClick={this.toggleEditHistory}
|
||||
>
|
||||
{this.state.showingEditHistory
|
||||
? t('comment.hide_edit_history')
|
||||
: t('comment.show_edit_history')}
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
@@ -142,6 +175,14 @@ class UserDetailComment extends React.Component {
|
||||
</div>
|
||||
</CommentAnimatedEdit>
|
||||
</div>
|
||||
|
||||
{this.state.showingEditHistory ? (
|
||||
<div className={styles.container}>
|
||||
{t('comment.edit_history')}
|
||||
{this.getBodyHistory()}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<CommentDetails root={root} comment={comment} />
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -4,12 +4,14 @@ export const FETCH_ASSETS_REQUEST = `${prefix}_FETCH_ASSETS_REQUEST`;
|
||||
export const FETCH_ASSETS_SUCCESS = `${prefix}_FETCH_ASSETS_SUCCESS`;
|
||||
export const FETCH_ASSETS_FAILURE = `${prefix}_FETCH_ASSETS_FAILURE`;
|
||||
|
||||
export const LOAD_MORE_ASSETS_REQUEST = `${prefix}_LOAD_MORE_ASSETS_REQUEST`;
|
||||
export const LOAD_MORE_ASSETS_SUCCESS = `${prefix}_LOAD_MORE_ASSETS_SUCCESS`;
|
||||
export const LOAD_MORE_ASSETS_FAILURE = `${prefix}_LOAD_MORE_ASSETS_FAILURE`;
|
||||
|
||||
export const UPDATE_ASSET_STATE_REQUEST = `${prefix}_UPDATE_ASSET_STATE_REQUEST`;
|
||||
export const UPDATE_ASSET_STATE_SUCCESS = `${prefix}_UPDATE_ASSET_STATE_SUCCESS`;
|
||||
export const UPDATE_ASSET_STATE_FAILURE = `${prefix}_UPDATE_ASSET_STATE_FAILURE`;
|
||||
|
||||
export const UPDATE_ASSETS = `${prefix}_UPDATE_ASSETS`;
|
||||
|
||||
export const SET_PAGE = `${prefix}_SET_PAGE`;
|
||||
export const SET_SEARCH_VALUE = `${prefix}_SET_SEARCH_VALUE`;
|
||||
export const SET_CRITERIA = `${prefix}_SET_CRITERIA`;
|
||||
|
||||
@@ -42,6 +42,10 @@ export default withFragments({
|
||||
status_history {
|
||||
type
|
||||
}
|
||||
body_history {
|
||||
body
|
||||
created_at
|
||||
}
|
||||
${getSlotFragmentSpreads(slots, 'comment')}
|
||||
...${getDefinitionName(CommentLabels.fragments.comment)}
|
||||
...${getDefinitionName(CommentDetails.fragments.comment)}
|
||||
|
||||
@@ -3,57 +3,79 @@ import update from 'immutability-helper';
|
||||
|
||||
const initialState = {
|
||||
assets: {
|
||||
byId: {},
|
||||
ids: [],
|
||||
assets: [],
|
||||
edges: [],
|
||||
pageInfo: {},
|
||||
},
|
||||
searchValue: '',
|
||||
criteria: {
|
||||
asc: 'false',
|
||||
filter: 'all',
|
||||
limit: 20,
|
||||
},
|
||||
loading: true,
|
||||
loadingMore: false,
|
||||
};
|
||||
|
||||
export default function assets(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case actions.FETCH_ASSETS_SUCCESS: {
|
||||
const assets = action.assets.reduce((prev, curr) => {
|
||||
prev[curr.id] = curr;
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
case actions.FETCH_ASSETS_REQUEST: {
|
||||
return update(state, {
|
||||
loading: { $set: true },
|
||||
});
|
||||
}
|
||||
case actions.FETCH_ASSETS_FAILURE: {
|
||||
return update(state, {
|
||||
loading: { $set: false },
|
||||
});
|
||||
}
|
||||
case actions.FETCH_ASSETS_SUCCESS: {
|
||||
return update(state, {
|
||||
loading: { $set: false },
|
||||
assets: {
|
||||
totalPages: { $set: action.totalPages },
|
||||
page: { $set: action.page },
|
||||
byId: { $set: assets },
|
||||
count: { $set: action.count },
|
||||
ids: { $set: Object.keys(assets) },
|
||||
edges: { $set: action.edges },
|
||||
pageInfo: { $set: action.pageInfo },
|
||||
},
|
||||
});
|
||||
}
|
||||
case actions.UPDATE_ASSET_STATE_REQUEST:
|
||||
case actions.LOAD_MORE_ASSETS_REQUEST: {
|
||||
return update(state, {
|
||||
loadingMore: { $set: true },
|
||||
});
|
||||
}
|
||||
case actions.LOAD_MORE_ASSETS_FAILURE: {
|
||||
return update(state, {
|
||||
loadingMore: { $set: false },
|
||||
});
|
||||
}
|
||||
case actions.LOAD_MORE_ASSETS_SUCCESS: {
|
||||
return update(state, {
|
||||
loadingMore: { $set: false },
|
||||
assets: {
|
||||
edges: { $push: action.edges },
|
||||
pageInfo: {
|
||||
endCursor: { $set: action.pageInfo.endCursor },
|
||||
hasNextPage: { $set: action.pageInfo.hasNextPage },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
case actions.UPDATE_ASSET_STATE_SUCCESS:
|
||||
const index = state.assets.edges.findIndex(
|
||||
({ node: { id } }) => id === action.id
|
||||
);
|
||||
if (index < 0) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return update(state, {
|
||||
assets: {
|
||||
byId: {
|
||||
[action.id]: {
|
||||
closedAt: { $set: action.closedAt },
|
||||
edges: {
|
||||
[index]: {
|
||||
node: {
|
||||
closedAt: { $set: action.closedAt },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
case actions.UPDATE_ASSETS:
|
||||
return update(state, {
|
||||
assets: {
|
||||
assets: { $set: action.assets },
|
||||
},
|
||||
});
|
||||
case actions.SET_PAGE:
|
||||
return {
|
||||
...state,
|
||||
page: action.page,
|
||||
};
|
||||
case actions.SET_SEARCH_VALUE:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -14,6 +14,11 @@ class TechSettings extends React.Component {
|
||||
this.props.updatePending({ updater });
|
||||
};
|
||||
|
||||
updateCustomAdminCssUrl = event => {
|
||||
const updater = { customAdminCssUrl: { $set: event.target.value } };
|
||||
this.props.updatePending({ updater });
|
||||
};
|
||||
|
||||
updateDomainlist = (listName, list) => {
|
||||
this.props.updatePending({
|
||||
updater: {
|
||||
@@ -50,6 +55,14 @@ class TechSettings extends React.Component {
|
||||
onChange={this.updateCustomCssUrl}
|
||||
/>
|
||||
</ConfigureCard>
|
||||
<ConfigureCard title={t('configure.custom_admin_css_url')}>
|
||||
<p>{t('configure.custom_admin_css_url_desc')}</p>
|
||||
<input
|
||||
className={styles.customCSSInput}
|
||||
value={settings.customAdminCssUrl}
|
||||
onChange={this.updateCustomAdminCssUrl}
|
||||
/>
|
||||
</ConfigureCard>
|
||||
<Slot fill="adminTechSettings" passthrough={slotPassthrough} />
|
||||
</ConfigurePage>
|
||||
);
|
||||
|
||||
@@ -32,6 +32,7 @@ export default compose(
|
||||
settings: gql`
|
||||
fragment TalkAdmin_TechSettings_settings on Settings {
|
||||
customCssUrl
|
||||
customAdminCssUrl
|
||||
domains {
|
||||
whitelist
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
font-weight: 300;
|
||||
margin-bottom: 8px;
|
||||
overflow-wrap: break-word;
|
||||
word-break:break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.body {
|
||||
@@ -103,7 +103,7 @@
|
||||
color: #063b9a;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
letter-spacing: .5px;
|
||||
letter-spacing: 0.5px;
|
||||
margin-left: 10px;
|
||||
font-size: 13px;
|
||||
margin-left: 5px;
|
||||
@@ -111,14 +111,14 @@
|
||||
border-bottom: solid 1px;
|
||||
line-height: 16px;
|
||||
&:hover {
|
||||
opacity: .9;
|
||||
opacity: 0.9;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.username {
|
||||
color: #393B44;
|
||||
color: #393b44;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
@@ -127,12 +127,12 @@
|
||||
margin-left: -5px;
|
||||
transition: background-color 200ms ease;
|
||||
&:hover {
|
||||
background-color: #E0E0E0;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
}
|
||||
|
||||
.external {
|
||||
font-size: .7em;
|
||||
font-size: 0.7em;
|
||||
text-decoration: none;
|
||||
color: #063b9a;
|
||||
cursor: pointer;
|
||||
@@ -140,7 +140,7 @@
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
opacity: .9;
|
||||
opacity: 0.9;
|
||||
}
|
||||
i {
|
||||
font-size: 12px;
|
||||
@@ -196,3 +196,25 @@
|
||||
.commentContentFooter {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.bodyHistoryToggle {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
line-height: 1px;
|
||||
font-weight: 300;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.editedCommentBody {
|
||||
padding: 0 5px 5px 5px;
|
||||
}
|
||||
|
||||
.editedComment {
|
||||
margin-top: 6px;
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import t from 'coral-framework/services/i18n';
|
||||
class Comment extends React.Component {
|
||||
ref = null;
|
||||
|
||||
state = { showingEditHistory: false };
|
||||
|
||||
handleRef = ref => (this.ref = ref);
|
||||
|
||||
handleFocusOrClick = () => {
|
||||
@@ -45,6 +47,30 @@ class Comment extends React.Component {
|
||||
? null
|
||||
: this.props.rejectComment({ commentId: this.props.comment.id });
|
||||
|
||||
getBodyHistory = () => {
|
||||
const bodyHistory = [];
|
||||
const comment = this.props.comment;
|
||||
for (let i = 0; i < comment.body_history.length - 1; i++) {
|
||||
bodyHistory.push(
|
||||
<div key={i} className={styles.editedComment}>
|
||||
<div>
|
||||
<TimeAgo className={styles.created} datetime={comment.created_at} />
|
||||
</div>
|
||||
<div className={styles.editedCommentBody}>
|
||||
{comment.body_history[i].body}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return bodyHistory;
|
||||
};
|
||||
|
||||
toggleEditHistory = () => {
|
||||
this.setState({ showingEditHistory: !this.state.showingEditHistory });
|
||||
this.props.clearHeightCache && this.props.clearHeightCache();
|
||||
};
|
||||
|
||||
componentDidUpdate(prev) {
|
||||
if (!prev.selected && this.props.selected) {
|
||||
this.ref.focus();
|
||||
@@ -137,6 +163,14 @@ class Comment extends React.Component {
|
||||
<span className={styles.editedMarker}>
|
||||
({t('comment.edited')})
|
||||
</span>
|
||||
<span
|
||||
className={styles.bodyHistoryToggle}
|
||||
onClick={this.toggleEditHistory}
|
||||
>
|
||||
{this.state.showingEditHistory
|
||||
? t('comment.hide_edit_history')
|
||||
: t('comment.show_edit_history')}
|
||||
</span>
|
||||
</span>
|
||||
) : null}
|
||||
<div className={styles.adminCommentInfoBar}>
|
||||
@@ -201,6 +235,14 @@ class Comment extends React.Component {
|
||||
</div>
|
||||
</CommentAnimatedEdit>
|
||||
</div>
|
||||
|
||||
{this.state.showingEditHistory ? (
|
||||
<div className={styles.container}>
|
||||
{t('comment.edit_history')}
|
||||
{this.getBodyHistory()}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<CommentDetails
|
||||
root={root}
|
||||
comment={comment}
|
||||
|
||||
@@ -52,6 +52,10 @@ export default withFragments({
|
||||
status_history {
|
||||
type
|
||||
}
|
||||
body_history {
|
||||
body
|
||||
created_at
|
||||
}
|
||||
hasParent
|
||||
${getSlotFragmentSpreads(slots, 'comment')}
|
||||
...${getDefinitionName(CommentLabels.fragments.comment)}
|
||||
|
||||
@@ -77,7 +77,6 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
@@ -95,3 +94,11 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.loadMore {
|
||||
margin: 20px auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loadMoreSpinner {
|
||||
margin: 20px auto 50px;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@ import React, { Component } from 'react';
|
||||
import cn from 'classnames';
|
||||
import { Link } from 'react-router';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Dropdown, Option, Paginate, Icon } from 'coral-ui';
|
||||
import { DataTable, TableHeader, RadioGroup, Radio } from 'react-mdl';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import styles from './Stories.css';
|
||||
import { Dropdown, Option, Icon, Spinner } from 'coral-ui';
|
||||
import EmptyCard from 'coral-admin/src/components/EmptyCard';
|
||||
import LoadMore from 'coral-admin/src/components/LoadMore';
|
||||
|
||||
import styles from './Stories.css';
|
||||
|
||||
class Stories extends Component {
|
||||
renderDate = date => {
|
||||
@@ -39,10 +42,11 @@ class Stories extends Component {
|
||||
filter,
|
||||
onSearchChange,
|
||||
onSettingChange,
|
||||
onPageChange,
|
||||
asc,
|
||||
onLoadMore,
|
||||
loading,
|
||||
loadingMore,
|
||||
} = this.props;
|
||||
const rows = assets.ids.map(id => assets.byId[id]);
|
||||
const rows = assets.edges.map(({ node }) => node);
|
||||
|
||||
return (
|
||||
<div className={cn('talk-admin-stories', styles.container)}>
|
||||
@@ -74,60 +78,58 @@ class Stories extends Component {
|
||||
<Radio value="open">{t('streams.open')}</Radio>
|
||||
<Radio value="closed">{t('streams.closed')}</Radio>
|
||||
</RadioGroup>
|
||||
<div className={styles.optionHeader}>{t('streams.sort_by')}</div>
|
||||
<RadioGroup
|
||||
name="sortBy"
|
||||
value={asc}
|
||||
childContainer="div"
|
||||
onChange={onSettingChange('asc')}
|
||||
className={styles.radioGroup}
|
||||
>
|
||||
<Radio value="false">{t('streams.newest')}</Radio>
|
||||
<Radio value="true">{t('streams.oldest')}</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
{rows.length ? (
|
||||
<div className={styles.mainContent}>
|
||||
<DataTable className={styles.streamsTable} rows={rows}>
|
||||
<TableHeader name="title" cellFormatter={this.renderTitle}>
|
||||
{t('streams.article')}
|
||||
</TableHeader>
|
||||
<TableHeader
|
||||
name="publication_date"
|
||||
cellFormatter={this.renderDate}
|
||||
>
|
||||
{t('streams.pubdate')}
|
||||
</TableHeader>
|
||||
<TableHeader
|
||||
name="closedAt"
|
||||
cellFormatter={this.renderStatus}
|
||||
className={styles.status}
|
||||
>
|
||||
{t('streams.status')}
|
||||
</TableHeader>
|
||||
</DataTable>
|
||||
<Paginate
|
||||
pageCount={assets.totalPages}
|
||||
page={assets.page - 1}
|
||||
onPageChange={onPageChange}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyCard>{t('streams.empty_result')}</EmptyCard>
|
||||
)}
|
||||
<div className={styles.mainContent}>
|
||||
{loading ? (
|
||||
<Spinner />
|
||||
) : rows.length ? (
|
||||
<div>
|
||||
<DataTable className={styles.streamsTable} rows={rows}>
|
||||
<TableHeader name="title" cellFormatter={this.renderTitle}>
|
||||
{t('streams.article')}
|
||||
</TableHeader>
|
||||
<TableHeader
|
||||
name="publication_date"
|
||||
cellFormatter={this.renderDate}
|
||||
>
|
||||
{t('streams.pubdate')}
|
||||
</TableHeader>
|
||||
<TableHeader
|
||||
name="closedAt"
|
||||
cellFormatter={this.renderStatus}
|
||||
className={styles.status}
|
||||
>
|
||||
{t('streams.status')}
|
||||
</TableHeader>
|
||||
</DataTable>
|
||||
{loadingMore ? (
|
||||
<Spinner className={styles.loadMoreSpinner} />
|
||||
) : (
|
||||
<LoadMore
|
||||
showLoadMore={assets.pageInfo.hasNextPage}
|
||||
loadMore={onLoadMore}
|
||||
className={styles.loadMore}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyCard>{t('streams.empty_result')}</EmptyCard>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Stories.propTypes = {
|
||||
loading: PropTypes.bool,
|
||||
loadingMore: PropTypes.bool,
|
||||
assets: PropTypes.object,
|
||||
searchValue: PropTypes.string,
|
||||
asc: PropTypes.string,
|
||||
filter: PropTypes.string,
|
||||
onLoadMore: PropTypes.func.isRequired,
|
||||
onStatusChange: PropTypes.func.isRequired,
|
||||
onSearchChange: PropTypes.func.isRequired,
|
||||
onPageChange: PropTypes.func.isRequired,
|
||||
onSettingChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import { bindActionCreators } from 'redux';
|
||||
import {
|
||||
fetchAssets,
|
||||
updateAssetState,
|
||||
setPage,
|
||||
setSearchValue,
|
||||
setCriteria,
|
||||
loadMoreAssets,
|
||||
} from 'coral-admin/src/actions/stories';
|
||||
import Stories from '../components/Stories';
|
||||
|
||||
@@ -35,13 +35,11 @@ class StoriesContainer extends Component {
|
||||
};
|
||||
|
||||
fetchAssets = query => {
|
||||
const { searchValue, asc, filter, limit } = this.props;
|
||||
const { searchValue: value, filter } = this.props;
|
||||
|
||||
this.props.fetchAssets({
|
||||
value: searchValue,
|
||||
asc,
|
||||
value,
|
||||
filter,
|
||||
limit,
|
||||
...query,
|
||||
});
|
||||
};
|
||||
@@ -51,30 +49,39 @@ class StoriesContainer extends Component {
|
||||
|
||||
try {
|
||||
await updateAssetState(id, closeStream ? Date.now() : null);
|
||||
this.fetchAssets();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
onPageChange = ({ selected }) => {
|
||||
const page = selected + 1;
|
||||
this.props.setPage(page);
|
||||
this.fetchAssets({ page });
|
||||
onLoadMore = async () => {
|
||||
const {
|
||||
searchValue: value,
|
||||
filter,
|
||||
assets: {
|
||||
pageInfo: { endCursor: cursor },
|
||||
},
|
||||
loadMoreAssets,
|
||||
} = this.props;
|
||||
try {
|
||||
await loadMoreAssets({ cursor, value, filter });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Stories
|
||||
loading={this.props.loading}
|
||||
loadingMore={this.props.loadingMore}
|
||||
assets={this.props.assets}
|
||||
searchValue={this.props.searchValue}
|
||||
asc={this.props.asc}
|
||||
filter={this.props.filter}
|
||||
limit={this.props.limit}
|
||||
onPageChange={this.onPageChange}
|
||||
onStatusChange={this.onStatusChange}
|
||||
onSettingChange={this.onSettingChange}
|
||||
onSearchChange={this.onSearchChange}
|
||||
onLoadMore={this.onLoadMore}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -82,34 +89,34 @@ class StoriesContainer extends Component {
|
||||
|
||||
const mapStateToProps = ({ stories }) => ({
|
||||
assets: stories.assets,
|
||||
loading: stories.loading,
|
||||
loadingMore: stories.loadingMore,
|
||||
searchValue: stories.searchValue,
|
||||
asc: stories.criteria.asc,
|
||||
filter: stories.criteria.filter,
|
||||
limit: stories.criteria.limit,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
setPage,
|
||||
setCriteria,
|
||||
setSearchValue,
|
||||
fetchAssets,
|
||||
updateAssetState,
|
||||
loadMoreAssets,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
StoriesContainer.propTypes = {
|
||||
loading: PropTypes.bool,
|
||||
loadingMore: PropTypes.bool,
|
||||
assets: PropTypes.object,
|
||||
searchValue: PropTypes.string,
|
||||
asc: PropTypes.string,
|
||||
filter: PropTypes.string,
|
||||
limit: PropTypes.number,
|
||||
setPage: PropTypes.func.isRequired,
|
||||
setCriteria: PropTypes.func.isRequired,
|
||||
setSearchValue: PropTypes.func.isRequired,
|
||||
fetchAssets: PropTypes.func.isRequired,
|
||||
loadMoreAssets: PropTypes.func.isRequired,
|
||||
updateAssetState: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import LinkifyIt from 'linkify-it';
|
||||
import tlds from 'tlds';
|
||||
|
||||
export function createLinkify() {
|
||||
const linkify = new LinkifyIt();
|
||||
linkify.tlds(tlds);
|
||||
return linkify;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ class EmbedContainer extends React.Component {
|
||||
},
|
||||
}
|
||||
) => {
|
||||
notify('info', t('your_username_has_been_rejected'));
|
||||
notify('info', t('your_username_has_been_rejected_not_in_line'));
|
||||
props.updateStatus(state.status);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -48,7 +48,7 @@ class ChangeUsername extends Component {
|
||||
return (
|
||||
<RestrictedMessageBox>
|
||||
<div className="talk-change-username">
|
||||
<span>{t('framework.edit_name.msg')}</span>
|
||||
<span>{t('framework.edit_name.rejected')}</span>
|
||||
<div className={styles.alert}>{alert}</div>
|
||||
<label htmlFor="username" className="screen-reader-text">
|
||||
{t('framework.edit_name.label')}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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 {
|
||||
@@ -92,6 +93,21 @@ function viewportDimensions() {
|
||||
};
|
||||
}
|
||||
|
||||
function parseAMPHash(opts) {
|
||||
const result = { ...opts };
|
||||
const query = window.location.hash.length && window.location.hash.substr(1);
|
||||
if (query) {
|
||||
const parsed = queryString.parse(query);
|
||||
if (parsed.asset_url && !result.asset_url) {
|
||||
result.asset_url = parsed.asset_url;
|
||||
}
|
||||
if (parsed.asset_id && !result.asset_id) {
|
||||
result.asset_id = parsed.asset_id;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default class Bridge {
|
||||
constructor(
|
||||
element,
|
||||
@@ -110,19 +126,23 @@ export default class Bridge {
|
||||
lazy = process.env.TALK_DEFAULT_LAZY_RENDER === 'TRUE',
|
||||
// Any additional options are extracted to be sent to the embed via the
|
||||
// pym bridge.
|
||||
amp,
|
||||
...opts
|
||||
}
|
||||
) {
|
||||
this.pym = null;
|
||||
this.element = element;
|
||||
this.opts = opts;
|
||||
this.amp = amp;
|
||||
this.lazy = !amp && lazy;
|
||||
|
||||
// Parse amp hash.
|
||||
this.opts = amp ? parseAMPHash(opts) : opts;
|
||||
this.query = buildQuery(this.opts);
|
||||
this.emitter = new EventEmitter({ wildcard: true });
|
||||
this.snackBar = new SnackBar(snackBarStyles || {});
|
||||
this.snackBar = amp ? null : 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.
|
||||
@@ -179,6 +199,16 @@ export default class Bridge {
|
||||
if (height !== cachedHeight) {
|
||||
this.pym.el.firstChild.style.height = `${height}px`;
|
||||
cachedHeight = height;
|
||||
if (this.amp) {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
sentinel: 'amp',
|
||||
type: 'embed-size',
|
||||
height,
|
||||
},
|
||||
'*'
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -267,8 +297,10 @@ export default class Bridge {
|
||||
// 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 (this.snackBar) {
|
||||
// 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);
|
||||
@@ -315,7 +347,10 @@ export default class Bridge {
|
||||
this.emitter.removeAllListeners();
|
||||
|
||||
// Remove the snackbar.
|
||||
this.snackBar.remove();
|
||||
if (this.snackBar) {
|
||||
this.snackBar.remove();
|
||||
this.snackBar = null;
|
||||
}
|
||||
|
||||
// Remove the pym parent.
|
||||
this.pym.remove();
|
||||
|
||||
@@ -56,6 +56,7 @@ export const Talk = {
|
||||
* @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
|
||||
* @param {Boolean} [config.amp] - (optional) Run Talk in AMP mode
|
||||
* @return {Object}
|
||||
*/
|
||||
render: (element, config) => {
|
||||
|
||||
+7
-7
@@ -7,7 +7,6 @@ import { createReduxEmitter } from './events';
|
||||
import { createRestClient } from './rest';
|
||||
import thunk from 'redux-thunk';
|
||||
import { loadTranslations } from './i18n';
|
||||
import bowser from 'bowser';
|
||||
import noop from 'lodash/noop';
|
||||
import { BASE_PATH } from 'coral-framework/constants/url';
|
||||
import { createPluginsService } from './plugins';
|
||||
@@ -65,12 +64,7 @@ const getAuthToken = (store, storage) => {
|
||||
}
|
||||
|
||||
return token;
|
||||
} else if (
|
||||
!bowser.safari &&
|
||||
!bowser.ios &&
|
||||
storage &&
|
||||
storage.getItem('token')
|
||||
) {
|
||||
} else if (storage && storage.getItem('token')) {
|
||||
// Use local storage auth tokens where there's a stable api.
|
||||
return storage.getItem('token');
|
||||
}
|
||||
@@ -159,6 +153,11 @@ export async function createContext({
|
||||
token,
|
||||
});
|
||||
|
||||
const rest2 = createRestClient({
|
||||
uri: `${BASE_PATH}api/v2`,
|
||||
token,
|
||||
});
|
||||
|
||||
const staticConfig = getStaticConfiguration();
|
||||
let { LIVE_URI: liveUri, BASE_ORIGIN: origin } = staticConfig;
|
||||
if (liveUri == null) {
|
||||
@@ -193,6 +192,7 @@ export async function createContext({
|
||||
plugins,
|
||||
eventEmitter,
|
||||
rest,
|
||||
rest2,
|
||||
graphql,
|
||||
notification,
|
||||
localStorage,
|
||||
|
||||
@@ -85,6 +85,9 @@ const CONFIG = {
|
||||
// as report CSP violations.
|
||||
ENABLE_STRICT_CSP: process.env.TALK_ENABLE_STRICT_CSP === 'TRUE',
|
||||
|
||||
// TRUST_PROXY allows control over the `trust proxy` configuration on express.
|
||||
TRUST_PROXY: process.env.TALK_TRUST_PROXY || '1',
|
||||
|
||||
// LOGGING_LEVEL specifies the logging level used by the bunyan logger.
|
||||
LOGGING_LEVEL: ['fatal', 'error', 'warn', 'info', 'debug', 'trace'].includes(
|
||||
process.env.TALK_LOGGING_LEVEL
|
||||
|
||||
+149
-98
@@ -6,7 +6,7 @@
|
||||
title: Talk Documentation
|
||||
subtitle:
|
||||
description: >
|
||||
Documentation and guides for Talk from The Coral Project.
|
||||
Documentation and guides for Coral by Vox Media
|
||||
google_analytics: UA-73335347-5
|
||||
author:
|
||||
language: en
|
||||
@@ -47,7 +47,7 @@ highlight:
|
||||
# per_page: Posts displayed per page. (0 = disable pagination)
|
||||
# order_by: Posts order. (Order by date descending by default)
|
||||
index_generator:
|
||||
path: ''
|
||||
path: ""
|
||||
|
||||
# Category & Tag
|
||||
default_category: uncategorized
|
||||
@@ -78,108 +78,159 @@ sidebar:
|
||||
- title: Docker
|
||||
url: https://hub.docker.com/r/coralproject/talk/
|
||||
side:
|
||||
- title: Installation
|
||||
- title: Version 5+
|
||||
children:
|
||||
- title: Talk Quickstart
|
||||
url: /
|
||||
- title: Installation from Docker
|
||||
url: /installation-from-docker/
|
||||
- title: Installation from Source
|
||||
url: /installation-from-source/
|
||||
- title: Planning your Talk Architecture
|
||||
url: /planning-architecture/
|
||||
- title: Pre-Launch Checklist
|
||||
url: /pre-launch-checklist/
|
||||
- title: Configuration
|
||||
- title: Getting started
|
||||
children:
|
||||
- title: Installing
|
||||
url: /
|
||||
- title: Configuration
|
||||
url: /v5/configuration/
|
||||
- title: Admin settings
|
||||
url: /v5/administration/
|
||||
- title: Developing
|
||||
url: /v5/developing/
|
||||
- title: CLI Tools
|
||||
url: /v5/configuration-cli-tools/
|
||||
- title: Integration
|
||||
children:
|
||||
- title: CMS integration
|
||||
url: /v5/integrating/cms/
|
||||
- title: SSO
|
||||
url: /v5/integrating/sso/
|
||||
- title: Other auth options
|
||||
url: /v5/integration/auth/
|
||||
- title: Custom CSS styles
|
||||
url: /v5/css/
|
||||
- title: Emails & notifications
|
||||
url: /v5/notifications/
|
||||
- title: GDPR
|
||||
url: /v5/integrating/gdpr/
|
||||
- title: Comment count
|
||||
url: /v5/integrating/counts/
|
||||
- title: Moderating via Slack
|
||||
url: /v5/integrating/slack/
|
||||
- title: Event tracking
|
||||
url: /v5/integrating/events/
|
||||
- title: SEO & iframes
|
||||
url: /v5/seo/
|
||||
- title: API
|
||||
children:
|
||||
- title: GraphQL Overview
|
||||
url: /v5/api/overview/
|
||||
- title: Version 4
|
||||
children:
|
||||
- title: Required Configuration
|
||||
url: /configuration/
|
||||
- title: Advanced Configuration
|
||||
url: /advanced-configuration/
|
||||
- title: Configuration with CLI tools
|
||||
url: /configuration-cli-tools/
|
||||
- title: Integrating
|
||||
children:
|
||||
- title: Authentication
|
||||
url: /integrating/authentication/
|
||||
- title: CMS Integration
|
||||
url: /integrating/cms-integration/
|
||||
- title: Asset Scraping
|
||||
url: /integrating/asset-scraping/
|
||||
- title: Configuring the Comment Stream
|
||||
url: /integrating/configuring-comment-stream/
|
||||
- title: Configuring the Admin
|
||||
url: /integrating/configuring-admin/
|
||||
- title: Notifications
|
||||
url: /integrating/notifications/
|
||||
- title: Event Tracking and Metrics
|
||||
url: /integrating/event-tracking-metrics/
|
||||
- title: Styling and CSS
|
||||
url: /integrating/styling-css/
|
||||
- title: Translations and i18n
|
||||
url: /integrating/translations-i18n/
|
||||
- title: GDPR Compliance
|
||||
url: /integrating/gdpr/
|
||||
- title: Product Guide
|
||||
children:
|
||||
- title: How Talk Works
|
||||
url: /how-talk-works/
|
||||
- title: Commenter Features
|
||||
url: /commenter-features/
|
||||
- title: Moderator Features
|
||||
url: /moderator-features/
|
||||
- title: User Roles in Talk
|
||||
url: /roles/
|
||||
- title: Trust
|
||||
url: /trust/
|
||||
- title: Toxic Comments
|
||||
url: /toxic-comments/
|
||||
- title: Configuring Talk
|
||||
url: /configuring-talk/
|
||||
- title: Plugins
|
||||
children:
|
||||
- title: Plugins Overview
|
||||
url: /plugins/
|
||||
- title: Plugin Directory
|
||||
url: /plugins-directory/
|
||||
- title: Plugin Recipes
|
||||
url: /plugin-recipes/
|
||||
- title: API
|
||||
children:
|
||||
- title: GraphQL Overview
|
||||
url: /api/overview/
|
||||
- title: GraphQL Reference
|
||||
url: /api/graphql/
|
||||
- title: Server Plugin API
|
||||
url: /api/server/
|
||||
- title: Client Plugin API
|
||||
url: /api/client/
|
||||
- title: Plugin Slots API
|
||||
url: /api/slots/
|
||||
- title: Tutorials
|
||||
children:
|
||||
- title: Creating a Basic Plugin
|
||||
url: /building-basic-plugin/
|
||||
- title: Customizing Plugins with Coral UI
|
||||
url: /customizing-plugins-coral-ui/
|
||||
- title: When You've Installed Talk
|
||||
url: /when-youve-installed-talk/
|
||||
- title: Embedding Talk in Native Mobile Apps
|
||||
url: /embedding-in-native-mobile-apps/
|
||||
- title: Installation
|
||||
children:
|
||||
- title: Talk Quickstart
|
||||
url: /installation-quickstart
|
||||
- title: Installation from Docker
|
||||
url: /installation-from-docker/
|
||||
- title: Installation from Source
|
||||
url: /installation-from-source/
|
||||
- title: Planning your Talk Architecture
|
||||
url: /planning-architecture/
|
||||
- title: Pre-Launch Checklist
|
||||
url: /pre-launch-checklist/
|
||||
- title: Configuration
|
||||
children:
|
||||
- title: Required Configuration
|
||||
url: /configuration/
|
||||
- title: Advanced Configuration
|
||||
url: /advanced-configuration/
|
||||
- title: Configuration with CLI tools
|
||||
url: /configuration-cli-tools/
|
||||
- title: Integrating
|
||||
children:
|
||||
- title: Authentication
|
||||
url: /integrating/authentication/
|
||||
- title: CMS Integration
|
||||
url: /integrating/cms-integration/
|
||||
- title: Asset Scraping
|
||||
url: /integrating/asset-scraping/
|
||||
- title: Configuring the Comment Stream
|
||||
url: /integrating/configuring-comment-stream/
|
||||
- title: Configuring the Admin
|
||||
url: /integrating/configuring-admin/
|
||||
- title: Notifications
|
||||
url: /integrating/notifications/
|
||||
- title: Event Tracking and Metrics
|
||||
url: /integrating/event-tracking-metrics/
|
||||
- title: Styling and CSS
|
||||
url: /integrating/styling-css/
|
||||
- title: Translations and i18n
|
||||
url: /integrating/translations-i18n/
|
||||
- title: GDPR Compliance
|
||||
url: /integrating/gdpr/
|
||||
- title: Accelerated Mobile Page
|
||||
url: /integrating/amp/
|
||||
- title: Product Guide
|
||||
children:
|
||||
- title: How Talk Works
|
||||
url: /how-talk-works/
|
||||
- title: Commenter Features
|
||||
url: /commenter-features/
|
||||
- title: Moderator Features
|
||||
url: /moderator-features/
|
||||
- title: User Roles in Talk
|
||||
url: /roles/
|
||||
- title: Trust
|
||||
url: /trust/
|
||||
- title: Toxic Comments
|
||||
url: /toxic-comments/
|
||||
- title: Configuring Talk
|
||||
url: /configuring-talk/
|
||||
- title: Plugins
|
||||
children:
|
||||
- title: Plugins Overview
|
||||
url: /plugins/
|
||||
- title: Plugin Directory
|
||||
url: /plugins-directory/
|
||||
- title: Plugin Recipes
|
||||
url: /plugin-recipes/
|
||||
- title: API
|
||||
children:
|
||||
- title: GraphQL Overview
|
||||
url: /api/overview/
|
||||
- title: GraphQL Reference
|
||||
url: /api/graphql/
|
||||
- title: Server Plugin API
|
||||
url: /api/server/
|
||||
- title: Client Plugin API
|
||||
url: /api/client/
|
||||
- title: Plugin Slots API
|
||||
url: /api/slots/
|
||||
- title: Tutorials
|
||||
children:
|
||||
- title: Creating a Basic Plugin
|
||||
url: /building-basic-plugin/
|
||||
- title: Customizing Plugins with Coral UI
|
||||
url: /customizing-plugins-coral-ui/
|
||||
- title: When You've Installed Talk
|
||||
url: /when-youve-installed-talk/
|
||||
- title: Embedding Talk in Native Mobile Apps
|
||||
url: /embedding-in-native-mobile-apps/
|
||||
- title: Migrating
|
||||
children:
|
||||
- title: Migrating from v3.x.x
|
||||
url: /migration/3/
|
||||
- title: Migrating to v4.0.0
|
||||
url: /migration/4/
|
||||
- title: Migrating to v4.1.0
|
||||
url: /migration/4.1/
|
||||
- title:
|
||||
children:
|
||||
- title: Migrating from v3.x.x
|
||||
url: /migration/3/
|
||||
- title: Migrating to v4.0.0
|
||||
url: /migration/4/
|
||||
- title: Migrating to v4.1.0
|
||||
url: /migration/4.1/
|
||||
- title: Migrating to v5.0.0
|
||||
url: /migration/5.0/
|
||||
- title: Migrating to v6.0.0
|
||||
url: /migration/6.0/
|
||||
- title: FAQ & Troubleshooting
|
||||
children:
|
||||
- title: FAQ
|
||||
url: /faq/
|
||||
- title: Troubleshooting Tips
|
||||
url: /troubleshooting-tips/
|
||||
- children:
|
||||
- title: FAQ
|
||||
url: /faq/
|
||||
- title: Troubleshooting Tips
|
||||
url: /troubleshooting-tips/
|
||||
- title: Contact
|
||||
url: /contact/
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
/ /talk/
|
||||
/talk/reference/server/ /talk/api/server/
|
||||
/talk/reference/graphql/ /talk/api/graphql/
|
||||
/talk/integrating/asset-management/ /talk/integrating/cms-integration/
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
title: Installing Coral
|
||||
permalink: /
|
||||
---
|
||||
|
||||
Online comments are broken. Our open-source commenting platform, Coral, reimagines
|
||||
moderation, comment display, and conversation. Use Coral to add smarter, safer discussions to your site without giving away your data.
|
||||
|
||||
More than 130 newsrooms in 16 countries trust Coral to power their on-site communities, including The Washington Post, The Wall Street Journal, and Der Spiegel. [Read more about Coral here](https://coralproject.net/).
|
||||
|
||||
<div class="callout">
|
||||
We offer hosting and support packages for Coral, as well as exclusive, customer-only features. [Contact us for more information.](https://coralproject.net/pricing/)
|
||||
</div>
|
||||
|
||||
Built with ❤️ by Coral by [Vox Media](https://product.voxmedia.com/).
|
||||
|
||||
Try a test version of Coral by running it via a Heroku App:
|
||||
|
||||
[](https://heroku.com/deploy?template=https://github.com/coralproject/talk)
|
||||
|
||||
## Requirements
|
||||
|
||||
- MongoDB >=4.2
|
||||
- Redis >=3.2
|
||||
- NodeJS >=12
|
||||
- NPM >=6.7
|
||||
|
||||
## Running
|
||||
|
||||
You can install Coral using Docker or via Source. We recommend Docker, as it
|
||||
provides the easiest deployment solution going forward, as all the dependencies
|
||||
are baked and shipped with the provided
|
||||
[coralproject/talk:6](https://hub.docker.com/r/coralproject/talk) image.
|
||||
|
||||
### Docker
|
||||
|
||||
The easiest way to get started with Coral is through our published Docker image
|
||||
and provided example `docker-compose.yml` file. The following assumes that you
|
||||
have Docker and Docker Compose installed on your local machine:
|
||||
|
||||
- Install Docker: https://docs.docker.com/install/
|
||||
- Install Docker Compose: https://docs.docker.com/compose/install/ (this is typically included in the Docker Desktop editions already)
|
||||
|
||||
```bash
|
||||
# Create directories to persist the data in MongoDB and Redis.
|
||||
mkdir -p data/{mongo,redis}
|
||||
|
||||
# Create the `docker-compose.yml` file to get started.
|
||||
cat > docker-compose.yml <<EOF
|
||||
version: "2"
|
||||
services:
|
||||
talk:
|
||||
image: coralproject/talk:6
|
||||
restart: always
|
||||
ports:
|
||||
- "127.0.0.1:3000:5000"
|
||||
depends_on:
|
||||
- mongo
|
||||
- redis
|
||||
environment:
|
||||
- MONGODB_URI=mongodb://mongo:27017/coral
|
||||
- REDIS_URI=redis://redis:6379
|
||||
- SIGNING_SECRET=<replace me with something secret>
|
||||
mongo:
|
||||
image: mongo:4.2
|
||||
volumes:
|
||||
- ./data/mongo:/data/db
|
||||
redis:
|
||||
image: redis:3.2
|
||||
volumes:
|
||||
- ./data/redis:/data
|
||||
EOF
|
||||
|
||||
# Start up Coral using Docker.
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Then head on over to http://localhost:3000 to install Coral!
|
||||
|
||||
### Source
|
||||
|
||||
Coral requires NodeJS >=12, we recommend using `nvm` to help manage node
|
||||
versions: https://github.com/creationix/nvm.
|
||||
|
||||
```bash
|
||||
# Clone and cd into the Coral directory.
|
||||
git clone https://github.com/coralproject/talk.git
|
||||
cd talk
|
||||
|
||||
# Install dependencies.
|
||||
npm install
|
||||
|
||||
# Build the application dependencies.
|
||||
# This might take a while.
|
||||
npm run build
|
||||
```
|
||||
|
||||
This should output all the compiled application code to `./dist`.
|
||||
|
||||
Running Coral with default settings assumes that you have:
|
||||
|
||||
- MongoDB >=4.2 running on `127.0.0.1:27017`
|
||||
- Redis >=3.2 running on `127.0.0.1:6379`
|
||||
|
||||
If you don't already have these databases running, you can execute the following
|
||||
assuming you have Docker installed on your local machine:
|
||||
|
||||
```bash
|
||||
docker run -d -p 27017:27017 --restart always --name mongo mongo:4.2
|
||||
docker run -d -p 6379:6379 --restart always --name redis redis:3.2
|
||||
```
|
||||
|
||||
Then start Coral with:
|
||||
|
||||
```bash
|
||||
# Start the server in development mode.
|
||||
npm run start:development
|
||||
```
|
||||
|
||||
Then head on over to http://localhost:3000 to install Coral!
|
||||
|
||||
Note that if you want to run Coral in production from source, you'll need to
|
||||
create a secret for signing that differs from the unsafe default. You can do
|
||||
this by using something like `openssl rand -base64 32`. You can then place this
|
||||
secret in a `.env` file in your `talk` directory as such:
|
||||
|
||||
```bash
|
||||
SIGNING_SECRET=<replace me with something secret>
|
||||
```
|
||||
|
||||
You can then run Coral with the production command instead:
|
||||
|
||||
```bash
|
||||
# Start the server in production mode.
|
||||
npm run start
|
||||
```
|
||||
@@ -0,0 +1,193 @@
|
||||
---
|
||||
title: Configuring Version 5+
|
||||
permalink: /v5/configuration/
|
||||
toc: true
|
||||
---
|
||||
|
||||
The following environment variables can be set to configure the Coral Server. You
|
||||
can expose them in your shell via `export NODE_ENV=development` or by placing
|
||||
the variables in a `.env` file in the root of the project in a simple
|
||||
`NODE_ENV=development` format delimited by newlines.
|
||||
|
||||
## Required Configuration Variables
|
||||
|
||||
### `PORT`
|
||||
|
||||
The port to listen for HTTP and Websocket requests. (Default `3000`)
|
||||
|
||||
### `MONGODB_URI`
|
||||
|
||||
The MongoDB database URI to connect to. (Default `mongodb://127.0.0.1:27017/coral`)
|
||||
|
||||
### `REDIS_URI`
|
||||
|
||||
The Redis database URI to connect to. (Default `redis://127.0.0.1:6379`)
|
||||
|
||||
### `SIGNING_SECRET`
|
||||
|
||||
The shared secret to use to sign JSON Web Tokens (JWT) with the selected signing
|
||||
algorithm. (Default: `keyboard cat`)
|
||||
|
||||
Note: While there is a default for this so development can be simplified, Coral
|
||||
will throw a runtime error in the event it's started with `NODE_ENV=production`
|
||||
and the `SIGNING_SECRET="keyboard cat"` to prevent insecure installations. This
|
||||
_must_ be set in production to something long and secure. You can use `openssl`
|
||||
to help with that:
|
||||
|
||||
```sh
|
||||
openssl rand -base64 45
|
||||
```
|
||||
|
||||
## Advanced Configuration Variables
|
||||
|
||||
### `NODE_ENV`
|
||||
|
||||
Can be one of `production` or `development`. All production deployments should
|
||||
use `production`. Defaults to `production` when ran with `npm run start`, or
|
||||
with Docker deployments. Defaults to `development` when run with
|
||||
`npm run start:development`.
|
||||
|
||||
### `REDIS_OPTIONS`
|
||||
|
||||
A JSON string with optional configuration options to be used when connecting to
|
||||
Redis as specified in the [ioredis](https://github.com/luin/ioredis/blob/1dac50a63753c2afc969315cfe38faf0edc50bc5/API.md#new_Redis_new) documentation. (Default: `{}`)
|
||||
|
||||
### `SIGNING_ALGORITHM`
|
||||
|
||||
The signing algorithm to use for signing tokens. (Default `HS256`).
|
||||
|
||||
Supported algorithms are:
|
||||
|
||||
- `HS256`
|
||||
- `HS384`
|
||||
- `HS512`
|
||||
- `RS256`
|
||||
- `RS384`
|
||||
- `RS512`
|
||||
- `ES256`
|
||||
- `ES384`
|
||||
- `ES512`
|
||||
|
||||
### `LOCALE`
|
||||
|
||||
Specify the default locale to use for all requests without a locale specified. (Default `en-US`)
|
||||
|
||||
### `LOGGING_LEVEL`
|
||||
|
||||
The logging level that can be set to one of `fatal`,
|
||||
`error`, `warn`, `info`, `debug`, or `trace`. (Default `info`)
|
||||
|
||||
### `FORCE_SSL`
|
||||
|
||||
Forces SSL in production by redirecting all HTTP requests to HTTPS, and sending
|
||||
[HSTS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) headers. (Default `false`)
|
||||
|
||||
Coral does not provide or manage HTTPS certificates. If you want to enable
|
||||
HTTPS, you must configure a proxy in front of Coral such as
|
||||
[Caddy](https://caddyserver.com/).
|
||||
|
||||
**Troubleshooting**: If you are seeing redirect loops when trying to access
|
||||
pages like the admin, you may need to configure [`TRUST_PROXY`](#trust-proxy) to
|
||||
tell Coral which upstream proxies to trust.
|
||||
|
||||
**Warning**: When `FORCE_SSL=true`, Coral will send [HSTS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)
|
||||
headers that will force web browsers to connect via HTTPS for the next 60 days.
|
||||
By forcing SSL use you'll need to provide a secure connection to your Coral
|
||||
instance for at least the next 60 days.
|
||||
|
||||
### `DISABLE_LIVE_UPDATES`
|
||||
|
||||
When `true`, the comment stream will not create a websocket connection to get
|
||||
live comment updates. This applies across all tenants on the installation, and
|
||||
cannot be turned back on via the interface. (Default `false`)
|
||||
|
||||
### `DISABLE_LIVE_UPDATES_TIMEOUT`
|
||||
|
||||
Stories that have not received a comment within this time frame will pause live
|
||||
updates automatically. Once a single comment is received on these stories, live
|
||||
updates will be re-enabled until the story sits idle for the timeout value,
|
||||
parsed by [ms](https://www.npmjs.com/package/ms). (Default `2 weeks`)
|
||||
|
||||
### `DISABLE_TENANT_CACHING`
|
||||
|
||||
When `true`, all tenants will be loaded from the database when needed rather than keeping a in-memory copy in sync via published events on Redis. (Default `false`)
|
||||
|
||||
### `ENABLE_GRAPHIQL`
|
||||
|
||||
When `true`, it will enable the interactive GraphQL developer environment at the
|
||||
`/graphiql` route. This will also disable persisted (Default `false`)
|
||||
|
||||
Note: We do not recommend using this in production environments as it disables
|
||||
many safety features used by the application to provide it.
|
||||
|
||||
### `METRICS_USERNAME`
|
||||
|
||||
The username for _Basic Authentication_ at the `/metrics` route. If not
|
||||
provided with `METRICS_PASSWORD`, no authentication will be added to this route.
|
||||
|
||||
### `METRICS_PASSWORD`
|
||||
|
||||
The password for _Basic Authentication_ at the `/metrics` route. If not
|
||||
provided with `METRICS_USERNAME`, no authentication will be added to this route.
|
||||
|
||||
### `METRICS_PORT`
|
||||
|
||||
[Prometheus](https://prometheus.io/docs/introduction/overview/) metrics are
|
||||
provided at this port under `/metrics` route. (Default `9000`)
|
||||
|
||||
### `SCRAPE_TIMEOUT`
|
||||
|
||||
The request timeout for scraping operations, parsed by [ms](https://www.npmjs.com/package/ms).
|
||||
(Default `10 seconds`)
|
||||
|
||||
### `SCRAPE_MAX_RESPONSE_SIZE`
|
||||
|
||||
The maximum size (in bytes) to allow for scraping responses. (Default `10e6`)
|
||||
|
||||
### `STATIC_URI`
|
||||
|
||||
The URI that static assets can be accessed from. This URI can be to a proxy that
|
||||
uses this Coral server on `PORT` as the upstream. Disabled by default.
|
||||
|
||||
### `TRUST_PROXY`
|
||||
|
||||
When provided, it configures the "trust proxy" settings for Express. If you are
|
||||
encountering issues where urls in the administration are showing with a `http`
|
||||
instead of `https`, you may need to set the `TRUST_PROXY` setting. Refer to
|
||||
https://expressjs.com/en/guide/behind-proxies.html for possible values of this
|
||||
configuration variable as it pertains to your setup.
|
||||
|
||||
### `WEBSOCKET_KEEP_ALIVE_TIMEOUT`
|
||||
|
||||
The interval that should be used to send keep alive messages over websocket to
|
||||
keep the socket open, parsed by [ms](https://www.npmjs.com/package/ms).
|
||||
(Default `30s`)
|
||||
|
||||
### `WORD_LIST_TIMEOUT`
|
||||
|
||||
The length of time that a given request to test a comment against a given word
|
||||
list, parsed by [ms](https://www.npmjs.com/package/ms). (Default `100ms`)
|
||||
|
||||
### `PERSPECTIVE_TIMEOUT`
|
||||
|
||||
The length of time that a given request should wait for a response when
|
||||
interacting with the Perspective API, parsed by [ms](https://www.npmjs.com/package/ms).
|
||||
(Default `800ms`)
|
||||
|
||||
## Development Configuration Variables
|
||||
|
||||
The following configuration variables are only enabled when the server has been
|
||||
started in development mode (where `NODE_ENV=development`).
|
||||
|
||||
### `DEV_PORT`
|
||||
|
||||
The port where the Webpack Development server is running on. (Default `8080`)
|
||||
|
||||
### `DISABLE_CLIENT_ROUTES`
|
||||
|
||||
Disables mounting of client routes for developing with Webpack Dev Server.
|
||||
(Default `false`)
|
||||
|
||||
### `DISABLE_RATE_LIMITERS`
|
||||
|
||||
Used to disable the rate limiters used in Coral. (Default `false`)
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: Admin Settings
|
||||
permalink: /v5/administration/
|
||||
---
|
||||
|
||||
Upon completing the installation setup wizard, you will be able to login to Coral with the ADMIN user you created during setup.
|
||||
|
||||
## Moderation queues
|
||||
All moderation queues are sorted by Most Recent at the top.
|
||||
|
||||
### Reported
|
||||
This is the most urgent queue – these are comments that are still on the page, might be a problem, and you haven't made a moderation decision about them yet. These comments have either been Reported by a user, or reported by the system because they contain suspect words or phrases.
|
||||
|
||||
### Pending
|
||||
These are comments that have been held back by the system for some reason. These are the reasons that comments are held back in Pending:
|
||||
|
||||
* The article or site is set to pre-moderate all comments (except for Staff/Moderator/Admin/Expert comments)
|
||||
* The commenter has been manually set to 'Always Pre-moderate' by a moderator
|
||||
* The comment has been ruled as 'Likely to be toxic' by the AI (optional)
|
||||
* The commenter's recent behavior has included a higher percentage of rejected comment than the threshhold you set in Configure ('Recent History') (optional)
|
||||
* The commenter has entered an identical comment twice in a row, anywhere on the site
|
||||
* The site is set to hold back for review the first comments by new commenters (optional)
|
||||
* The comment is marked as likely to spam by our spam detection system (optional)
|
||||
|
||||
Users who login with the ADMIN role are able to access the Configure tab, and from here change settings that control the functionality of your Coral instance.
|
||||
|
||||
### Unmoderated
|
||||
These are comments of any kind - Reported, Published, Pending - that a moderator hasn't yet accepted or rejected.
|
||||
|
||||
### Approved
|
||||
These are comments that a moderator has already approved. The name of the moderator is included on the card.
|
||||
|
||||
### Rejected
|
||||
These are comments that a moderator has already rejected. The name of the moderator is included on the card.
|
||||
|
||||
There is also a clock icon in the top right of the page. When you click the icon, you will see a list of the comments that you have recently moderated, with links to review your decision.
|
||||
|
||||
## Configure Menu Settings
|
||||
|
||||
### Language
|
||||
You can set the default language Coral uses in **Admin** > **Configure** > **General**.
|
||||
|
||||
You can see what languages Coral currently supports here: https://github.com/coralproject/talk/tree/master/src/locales
|
||||
|
||||
### Comment Stream Settings
|
||||
|
||||
Also located in **Admin** > **Configure** > **General** are the setting to control the comment stream. These settings are site-wide and will affect all of your comment streams.
|
||||
|
||||
#### **Community guidelines summary**
|
||||
This will appear above the comments sitewide. You can format the text using Markdown.
|
||||
|
||||
#### **Sitewide closed comments message**
|
||||
A message that will be displayed when comment streams are closed sitewide.
|
||||
|
||||
#### **Comment length**
|
||||
Set minimum and maximum comment length requirements. Blank spaces at the beginning and the end of a comment will be trimmed. Some newsrooms we’ve worked with prefer a limit between 2000 and 5000
|
||||
characters. Commenters will be alerted that they have gone over that number and won’t be able to submit their comment until they’ve edited it. This can be a useful tool to ensure commenters are concise with their comments.
|
||||
|
||||
#### **Comment editing**
|
||||
Set a limit on how long commenters have to edit their comments sitewide. Edited comments are marked as (Edited) on the comment stream and the moderation panel.
|
||||
|
||||
#### **Closing comment streams**
|
||||
Set comment streams to close after a defined period of time after a story’s publication. When this feature is enabled, existing stories older than the defined period that have not been manually opened or closed will also be updated and set as closed. Manually opening or closing a story will permantenly override the auto-close functionality.
|
||||
|
||||
#### **Closed comment stream message**
|
||||
A message to appear when a story is closed for commenting.
|
||||
|
||||
### Reactions
|
||||
Coral comes with a `Respect` button out of the box. Why a “Respect” button, you
|
||||
ask?
|
||||
[Read more here](https://mediaengagement.org/research/engagement-buttons/).
|
||||
|
||||
You can also create your own custom reaction
|
||||
buttons by modifying the Reaction label.
|
||||
|
||||
### Badges & Staff Member Badge
|
||||
|
||||
Badges differentiate users on the comment stream. By default, the `Staff` user badge displays when a commenter has an Admin, Moderator, or Staff role.
|
||||
|
||||
You can customize the badge label used for Staff roles by providing your own custom badge text. *i.e.: "NewsSite Team"*
|
||||
|
||||
Custom user badges are also available with SSO integrations. See [Single Sign On](/talk/v5/integrating/sso/)
|
||||
|
||||
|
||||
## Moderation
|
||||
These options can be found under **Admin** > **Configure** > **Moderation**.
|
||||
|
||||
### Pre-moderation
|
||||
When pre-moderation is turned on, comments will not be published unless approved by a moderator.
|
||||
|
||||
### Recent History
|
||||
Prevents repeat offenders from publishing comments without approval. When a commenter's rejection rate is above the threshold, their comments are sent to the Pending queue for moderation. This does not apply to Staff comments.
|
||||
|
||||
Rejection rate time period determines the look back period of commenter activity that will be considered in performing the calculation.
|
||||
|
||||
Rejection rate threshold is calculated by dividing
|
||||
number of Rejected comments by (rejected comments + published comments) over the time period specified, and is displayed as a percentage. It does not include comments pending for toxicity, spam or pre-moderation, only moderated comments are considred.
|
||||
|
||||
### Toxic Comment Filter
|
||||
|
||||
Using Google's Perspective API, the Toxic Comment Filter warns users when comments exceed the predefined toxicity threshold. You can read more about Google’s Perspective API and/or request an API key here: http://perspectiveapi.com/.
|
||||
|
||||
Comments with a toxicity score above the threshold will not be published and are placed in the Pending Queue for review by a moderator. Only if approved by a moderator, then the comment will be published.
|
||||
|
||||
If a comment exceeds the threshold, the commenter is warned that their comment may be toxic, and are given the chance to modify their comment before posting. If the revised comment is below the Toxicity Threshold, it is posted and displayed normally; however if the revised comment still exceeds the Toxicity Threshold, it is not displayed on the stream and instead is sent to the Pending queue for moderation.
|
||||
|
||||
Perspective API uses machine learning based on existing databases of accepted/rejected comments to guess the probability that a comment is abusive and/or toxic. English is the default language, but experimental models are available in multiple languages. Sites can also work with Jigsaw to create an individualized data set specifically trained on their own comment history. Read more about Coral’s take on toxicity [on our blog](https://coralproject.net/blog/toxic-avenging/).
|
||||
|
||||
### Spam Detection Filter
|
||||
|
||||
Enables spam detection from [Akismet](https://akismet.com/). Comments will be passed to the Akismet API for spam detection. If a comment is determined to be spam, it will prompt the user, indicating that the comment might be considered spam. If the user continues after this point with the still spam-like comment, the comment will be reported as containing spam, and sent for moderator approval.
|
||||
|
||||
*Note: Akismet is a premium service, charges may apply.*
|
||||
|
||||
## Banned & Suspect Words and Phrases
|
||||
Located in **Admin** > **Configure** > **Banned words and phrases**.
|
||||
|
||||
**Banned words** are a list of words or phrases that will trigger a comment to be automatically **Rejected**.
|
||||
|
||||
Comments containing a word or phrase in the **Suspect Words** List are placed into the **Reported** queue for moderator review and are published (if comments are not pre-moderated).
|
||||
|
||||
Lists of words/phrases are not case sensitive, and are separated by new lines.
|
||||
|
||||
|
||||
## Email SMTP Settings
|
||||
These settings can be found under **Admin** > **Configure** > **Email**.
|
||||
|
||||
You can configure a **From Name** as well as a **From Email Address** to appear on all outgoing emails.
|
||||
|
||||
Coral will use your SMTP provider for all outgoing mail including user invites, email verifications, password resets, and notifications. Specify your SMTP provider's settings for Host, Port, and authentication to send emails from Coral.
|
||||
|
||||
The `Invite` (users) button on the Community tab will only be available if Email settings have been configured.
|
||||
|
||||
## Advanced
|
||||
Located in **Admin** > **Configure** > **Advanced**.
|
||||
|
||||
### Embed Code
|
||||
This is the unique script that is used to embed Coral comment streams on your website. For more information about using your Embed code see [CMS Integration](/talk/v5/integrating/cms/).
|
||||
|
||||
### Custom CSS
|
||||
The link to your custom stylesheet. This will override any default styles, so you can style the comment embed to match your site.
|
||||
|
||||
### Live Updates
|
||||
When enabled, there will be real-time loading and updating of comments via subscriptions
|
||||
(specifically GraphQL Subscriptions). When disabled, users will have to refresh the page to see new comments.
|
||||
|
||||
Coral enables this via “Load More” buttons for both top-level comments (this
|
||||
button appears at the top of the stream), and within conversation threads (this
|
||||
button appears in situ for replies).
|
||||
|
||||
We’ve decided to go this route in order to make the viewing experience as smooth
|
||||
as possible, so that the feed of comments doesn’t change as you’re reading just
|
||||
because new comments are coming in. This could be especially disruptive on
|
||||
breaking news and/or controversial stories with very active discussions.
|
||||
|
||||
This option can be disabled by setting the environment variable: [DISABLE_LIVE_UPDATES](/talk/v5/configuration/#disable-live-updates)
|
||||
|
||||
|
||||
### Permitted Domains
|
||||
|
||||
List of domains where your Coral instance is allowed to be embedded. List each domain and subdomain in use separately and include the scheme for example: `http://localhost:3000`, `https://staging.yourdomain.com`, `http://yourdomain.com`, `https://yourdomain.com`, etc.
|
||||
|
||||
### Story Creation Settings
|
||||
Advanced settings for how stories are created within Coral; such as if stories are to be automatically created when they are published from your CMS.
|
||||
|
||||
See CMS Integration secions [Story Creation](/talk/v5/integrating/cms/#story-creation) and [Story Scraping](/talk/v5/integrating/cms/#story-scraping) for more details.
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
title: Developing
|
||||
permalink: /v5/developing/
|
||||
---
|
||||
|
||||
Running Coral for development is very similar to installing Coral via Source as
|
||||
described above.
|
||||
|
||||
Coral requires NodeJS >=12, we recommend using `nvm` to help manage node
|
||||
versions: https://github.com/creationix/nvm.
|
||||
|
||||
```bash
|
||||
# Clone and cd into the Coral directory.
|
||||
git clone https://github.com/coralproject/talk.git
|
||||
cd talk
|
||||
|
||||
# Install dependencies.
|
||||
npm install
|
||||
```
|
||||
|
||||
Running Coral with default settings assumes that you have:
|
||||
|
||||
- MongoDB >=4.2 running on `127.0.0.1:27017`
|
||||
- Redis >=3.2 running on `127.0.0.1:6379`
|
||||
|
||||
If you don't already have these databases running, you can execute the following
|
||||
assuming you have Docker installed on your local machine:
|
||||
|
||||
```bash
|
||||
docker run -d -p 27017:27017 --restart always --name mongo mongo:4.2
|
||||
docker run -d -p 6379:6379 --restart always --name redis redis:3.2
|
||||
```
|
||||
|
||||
We recommend installing [watchman](https://facebook.github.io/watchman/docs/install.html) for better watch
|
||||
performance.
|
||||
|
||||
```bash
|
||||
# On macOS, you can run the following with Homebrew.
|
||||
brew update
|
||||
brew install watchman
|
||||
```
|
||||
|
||||
Then start Coral with:
|
||||
|
||||
```bash
|
||||
# Run the server in development mode in order to facilitate auto-restarting and
|
||||
# rebuilding when file changes are detected. This might take a while to fully run.
|
||||
npm run watch
|
||||
```
|
||||
|
||||
When the client code has been built, navigate to http://localhost:8080/install
|
||||
to start the installation wizard. **Note: Ensure `localhost:8080` is used in the permitted domains list.**
|
||||
|
||||
To see the comment stream goto http://localhost:8080/.
|
||||
|
||||
To run linting and tests use the following commands:
|
||||
|
||||
```bash
|
||||
# Run the linters.
|
||||
npm run lint
|
||||
|
||||
# Run our unit and integration tests.
|
||||
npm run test
|
||||
```
|
||||
|
||||
## Email
|
||||
|
||||
To test out the email sending functionality, you can run [inbucket](https://www.inbucket.org/)
|
||||
which provides a test SMTP server that can visualize emails in the browser:
|
||||
|
||||
```bash
|
||||
docker run -d --name inbucket --restart always -p 2500:2500 -p 9000:9000 inbucket/inbucket
|
||||
```
|
||||
|
||||
You can then configure the email server on Coral
|
||||
by setting the email settings in
|
||||
`Configure -> Email` in the admin:
|
||||
|
||||
| Field | Value |
|
||||
| -------------- | -------------------- |
|
||||
| From Address | `community@test.com` |
|
||||
| Secure | `No` |
|
||||
| Host | `localhost` |
|
||||
| Port | `2500` |
|
||||
| Authentication | `No` |
|
||||
|
||||
Navigate to http://localhost:9000, click the "Monitor" tab. New emails received
|
||||
on this screen.
|
||||
|
||||
## Design Language System (UI Components)
|
||||
|
||||
We use [docz](https://docz.site) to document and develop our Design Language System. To start docz run:
|
||||
|
||||
```bash
|
||||
# Make sure CSS types are generated.
|
||||
# This is not required when `npm run watch` is already running.
|
||||
npm run generate:css-types
|
||||
|
||||
# Run docz in development.
|
||||
npm run docz -- dev
|
||||
```
|
||||
|
||||
After compilation has finished you can access docz at http://localhost:3030/.
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions from the open source community!
|
||||
|
||||
To get started contributing, check out our [Contribution Guidelines](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md).
|
||||
|
||||
### Contributing a Translation
|
||||
|
||||
We’re so proud to have received submissions from a lot of 3rd party contributors translating Coral into their own languages.
|
||||
|
||||
You can see what languages Coral currently supports here: https://github.com/coralproject/talk/tree/master/src/locales
|
||||
|
||||
You can set the default language Coral uses in **Admin** > **Configure** > **General**.
|
||||
|
||||
We use the fluent library and store our translations in FTL files in `src/locales/` and `src/core/server/locales/`. To add a new Coral translation, strings are added or removed from localization bundles in the translation files as needed.
|
||||
|
||||
Strings MUST NOT be changed after they've been committed and pushed to master. Changing a string requires creating a new ID with a new name (preferably descriptive instead of incremented) and deletion of the obsolete ID. It's often useful to add a comment above the string with info about how and where the string is used.
|
||||
|
||||
If you are a developer contributing a new language, you’ll need to add the required i18n support in the i18n files (or you can leave that to us if you like). If you’re a non-developer, you can submit the translation via GitHub if you feel comfortable doing that, or feel free to email it to us via our Support: support@coralproject.net
|
||||
|
||||
If you want to suggest a new language or put a placeholder for a translation you’re working on, feel free to create a GitHub issue: https://github.com/coralproject/talk/issues/new
|
||||
|
||||
## Compatibility with IE11
|
||||
|
||||
We strive to make the Comment Stream Embed usable in IE11 without being pixel-perfect. If you develop on the Comment Stream you need to be aware of the following:
|
||||
|
||||
### CSS Variables
|
||||
|
||||
In order to support IE11 on the Comment Stream, every time new CSS is loaded, you need to call `polyfillCSSVarsForIE11()`.
|
||||
|
||||
```ts
|
||||
import { polyfillCSSVarsForIE11 } from "coral-framework/helpers";
|
||||
|
||||
const loadProfileContainer = () =>
|
||||
import("./ProfileContainer" /* webpackChunkName: "profile" */).then(x => {
|
||||
// New css is loaded, take care of polyfilling those css vars for IE11.
|
||||
polyfillCSSVarsForIE11();
|
||||
return x;
|
||||
});
|
||||
```
|
||||
|
||||
### CSS Calc
|
||||
|
||||
Various bugs exists around `calc` support in IE11 (see https://caniuse.com/#feat=calc). We work around most of them by pre-transforming `calc` values using `postcss-calc-function`. Some css attributes might have an issue if you use `css-variables` inside `calc`.
|
||||
@@ -0,0 +1,230 @@
|
||||
---
|
||||
title: Configuration with CLI tools in Version 5
|
||||
permalink: /v5/configuration-cli-tools/
|
||||
toc: true
|
||||
---
|
||||
|
||||
Coral’s CLI tooling has been reinvented in version 5. We’ve replaced the former `./bin/cli` tools with a completely new CLI application which is available on GitHub at: https://github.com/coralproject/coral-cli
|
||||
|
||||
The new CLI was built on `oclif` ([View on GitHub](https://github.com/oclif)), the open source CLI Framework originally built by Heroku, and offers an extensible platform for interacting with Coral via CLI.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with the new CLI run:
|
||||
```
|
||||
npm install -g @coralproject/coral-cli
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Once installed, run:
|
||||
```
|
||||
coral-cli [COMMAND]
|
||||
```
|
||||
Below is a list of commands available:
|
||||
```
|
||||
CLI to interact with Coral
|
||||
|
||||
VERSION
|
||||
@coralproject/coral-cli/0.2.0 darwin-x64 node-v11.9.0
|
||||
|
||||
USAGE
|
||||
$ coral-cli [COMMAND]
|
||||
|
||||
COMMANDS
|
||||
help display help for coral-cli
|
||||
login grabs a token for interacting with Coral
|
||||
logout removes credentials for logging in with Coral
|
||||
plugins list installed plugins
|
||||
story manage stories on Coral
|
||||
tenant creates a new tenant
|
||||
token creates tokens on the current user
|
||||
```
|
||||
|
||||
### `coral-cli help [COMMAND]`
|
||||
Displays help for `coral-cli`, or for the command specified.
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli help [COMMAND]
|
||||
|
||||
ARGUMENTS
|
||||
COMMAND command to show help for
|
||||
|
||||
OPTIONS
|
||||
--all see all commands in CLI
|
||||
```
|
||||
|
||||
### `coral-cli login`
|
||||
Grabs a session token for interacting with a specific Coral installation. Can be used to create a session prior to running other commands.
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli login
|
||||
|
||||
OPTIONS
|
||||
-d, --domain=domain (required) domain for tenant to run command against
|
||||
```
|
||||
|
||||
### `coral-cli logout`
|
||||
Removes credentials for logging in with Coral and terminates current login session.
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli logout
|
||||
|
||||
OPTIONS
|
||||
-d, --domain=domain (required) domain for tenant to run command against
|
||||
```
|
||||
|
||||
### `coral-cli plugins`
|
||||
Not to be confused with "Talk Plugins" that were available in prior versions of Coral, `coral-cli plugins` are extensions to the `coral-cli` application. See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins). Use this command to the list installed plugins.
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli plugins
|
||||
|
||||
OPTIONS
|
||||
--core show core plugins
|
||||
|
||||
EXAMPLE
|
||||
$ coral-cli plugins
|
||||
```
|
||||
|
||||
### `coral-cli plugins:install [PLUGIN]`
|
||||
Installs a plugin into the CLI. Can be from npm or a git url. See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins)
|
||||
|
||||
*NOTE: Installation of a user-installed plugin will override a core plugin. e.g. If you have a core plugin that has a `hello` command, installing a user-installed plugin with a `hello` command will override the core plugin implementation.*
|
||||
```bash
|
||||
USAGE
|
||||
$ coral-cli plugins:install PLUGIN
|
||||
|
||||
ARGUMENTS
|
||||
PLUGIN plugin to install
|
||||
|
||||
OPTIONS
|
||||
-f, --force yarn install with force flag
|
||||
-h, --help show CLI help
|
||||
-v, --verbose
|
||||
|
||||
ALIASES
|
||||
$ coral-cli plugins:add
|
||||
|
||||
EXAMPLES
|
||||
$ coral-cli plugins:install myplugin
|
||||
$ coral-cli plugins:install https://github.com/someuser/someplugin
|
||||
$ coral-cli plugins:install someuser/someplugin
|
||||
```
|
||||
|
||||
### `coral-cli plugins:link PLUGIN`
|
||||
Links a plugin into the CLI for development.
|
||||
See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins)
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli plugins:link PLUGIN
|
||||
|
||||
ARGUMENTS
|
||||
PATH [default: .] path to plugin
|
||||
|
||||
OPTIONS
|
||||
-h, --help show CLI help
|
||||
-v, --verbose
|
||||
|
||||
EXAMPLE
|
||||
$ coral-cli plugins:link myplugin
|
||||
```
|
||||
|
||||
### `coral-cli plugins:uninstall PLUGIN`
|
||||
Removes a plugin from the CLI. See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins)
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli plugins:uninstall PLUGIN
|
||||
|
||||
ARGUMENTS
|
||||
PLUGIN plugin to uninstall
|
||||
|
||||
OPTIONS
|
||||
-h, --help show CLI help
|
||||
-v, --verbose
|
||||
|
||||
ALIASES
|
||||
$ coral-cli plugins:unlink
|
||||
$ coral-cli plugins:remove
|
||||
```
|
||||
|
||||
### `coral-cli plugins:update`
|
||||
Update installed plugins. See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins).
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli plugins:update
|
||||
|
||||
OPTIONS
|
||||
-h, --help show CLI help
|
||||
-v, --verbose
|
||||
```
|
||||
|
||||
### `coral-cli story:get`
|
||||
Fetches a story by `id` or `url`, useful to view Story metadata obtained by the scraper.
|
||||
```USAGE
|
||||
$ coral-cli story:get
|
||||
|
||||
OPTIONS
|
||||
-d, --domain=domain (required) domain for tenant to run command against
|
||||
--id=id find a story by ID
|
||||
--url=url find a story by URL
|
||||
```
|
||||
|
||||
### `coral-cli story:merge`
|
||||
Merge duplicate stories and their comments into a single story. Story metadata & url are maintained on destination.
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli story:merge
|
||||
|
||||
OPTIONS
|
||||
-d, --domain=domain (required) domain for tenant to run command against
|
||||
--from=from (required) source Story ID that will be merged from
|
||||
--into=into (required) destination Story ID that will be merged into
|
||||
```
|
||||
|
||||
### `coral-cli story:update`
|
||||
Target a story by `id` to update its `url`.
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli story:update
|
||||
|
||||
OPTIONS
|
||||
-d, --domain=domain (required) domain for tenant to run command against
|
||||
--id=id (required) the ID of the story to update
|
||||
--url=url (required) the new URL to update the story to
|
||||
```
|
||||
|
||||
### `coral-cli token:create`
|
||||
Creates a persistent token for the current user (based on running `coral-cli login` above). The token generated can be used to authenticate API requests as the associated user.
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli token:create
|
||||
|
||||
OPTIONS
|
||||
-d, --domain=domain (required) domain for tenant to run command against
|
||||
--name=name (required) name of the token
|
||||
```
|
||||
|
||||
### `coral-cli token:list`
|
||||
Lists tokens on the current user (based on running `coral-cli login` above).
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli token:list
|
||||
|
||||
OPTIONS
|
||||
-d, --domain=domain (required) domain for tenant to run command against
|
||||
```
|
||||
|
||||
### `coral-cli token:revoke`
|
||||
Revokes token by `id`. Token must be on the current user (based on running `coral-cli login` above).
|
||||
```
|
||||
USAGE
|
||||
$ coral-cli token:revoke
|
||||
|
||||
OPTIONS
|
||||
-d, --domain=domain (required) domain for tenant to run command against
|
||||
--id=id (required) id of the token to revoke
|
||||
```
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: Integrating on your site
|
||||
permalink: /v5/integrating/cms/
|
||||
---
|
||||
|
||||
With Coral setup and running locally you can find your **Embed code** under **Configure** > **Organization** > **Site Details** (when logged in as an ADMIN). It should look something like this, but with your domain in place of `CORAL_DOMAIN_NAME`. You can test placing the comment stream embed on your page with this sample embed script:
|
||||
|
||||
```
|
||||
<div id="coral_thread"></div>
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var d = document, s = d.createElement('script');
|
||||
var url = '{{ CORAL_DOMAIN_NAME }}';
|
||||
s.src = '//' + url + '/assets/js/embed.js';
|
||||
s.async = false;
|
||||
s.defer = true;
|
||||
s.onload = function() {
|
||||
Coral.createStreamEmbed({
|
||||
id: "coral_thread",
|
||||
autoRender: true,
|
||||
rootURL: '//' + url,
|
||||
});
|
||||
};
|
||||
(d.head || d.body).appendChild(s);
|
||||
})();
|
||||
</script>`;
|
||||
```
|
||||
|
||||
> **NOTE:** Replace the value of `{% raw %}{{ CORAL_DOMAIN_NAME }}{% endraw %}` with the location of your running instance of Coral.
|
||||
|
||||
|
||||
## Story Creation
|
||||
|
||||
Lazy `Story` Creation enables stories to be automatically created when they are published from your CMS. Triggering the embed script above renders the comment stream iFrame on your page. By default that script dynamically generates `Stories` in Coral for seamless integration.
|
||||
|
||||
### storyURL
|
||||
|
||||
If you do not specify a `storyURL` when rendering the embed, the `storyURL` is first inferred from the Canonical link element, which takes the form of a <link> element in your <head> of the page:
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="canonical" href="https://example.com/page" />
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
If this tag is not present, or if the canonical URL references a different url than your site such as a wire service, you can specify the `storyURL` parameter in the render function.
|
||||
|
||||
The url will be used by Coral to build user facing links, and should reference the location where you would direct a user back to this particular story or article.
|
||||
|
||||
### storyID
|
||||
|
||||
To more tightly couple Coral with your CMS you can provide your CMS's unique identifier to Coral by including a `storyID` parameter in the render function. Doing so will allow you to target the `Story` for later updates via Coral's Graphql API, such as updating the URL if it changes.
|
||||
|
||||
## Integration via API
|
||||
|
||||
Story creation can also be controlled by direct calls to Coral's API. When Lazy Story Creation is disabled embed streams can only be created by data migration or API POST request.
|
||||
|
||||
See [GraphQL API Overview](/talk/v5/api/overview/) for help with the API.
|
||||
|
||||
## Story Scraping
|
||||
|
||||
By default, stories have their metadata scraped when they are loaded. This provides the easiest way for newsrooms to integrate their CMS’s into Coral in a simple way. We use the following meta tags on the target pages that allow us to extract some properties.
|
||||
|
||||
Metadata scraping is performed by the `scraper` job which is enabled by default.
|
||||
|
||||
If your production site is behind a paywall or otherwise prevents scraping, you might need to confiugre a **Scraper Proxy URL**. When specified it allows scraping requests to use the provided proxy. All requests are then passed through the appropriote proxy as parsed by the npm proxy-agent package.
|
||||
|
||||
| Asset Property | Selector |
|
||||
|--------------------|----------|
|
||||
| `title` | See [`metascraper-title`](https://github.com/microlinkhq/metascraper/blob/dc664c37ea1b238b1e3e9d5342edfacc9027892c/packages/metascraper-title/index.js) |
|
||||
| `description` | See [`metascraper-description`](https://github.com/microlinkhq/metascraper/blob/dc664c37ea1b238b1e3e9d5342edfacc9027892c/packages/metascraper-description/index.js) |
|
||||
| `image` | See [`metascraper-image`](https://github.com/microlinkhq/metascraper/blob/dc664c37ea1b238b1e3e9d5342edfacc9027892c/packages/metascraper-image/index.js) |
|
||||
| `author` | See [`metascraper-author`](https://github.com/microlinkhq/metascraper/blob/dc664c37ea1b238b1e3e9d5342edfacc9027892c/packages/metascraper-author/index.js) |
|
||||
| `publication_date` | See [`metascraper-date`](https://github.com/microlinkhq/metascraper/blob/dc664c37ea1b238b1e3e9d5342edfacc9027892c/packages/metascraper-date/index.js) |
|
||||
| `modified_date` | `meta[property="article:modified"]` |
|
||||
| `section` | `meta[property="article:section"]` |
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
---
|
||||
title: Single Sign On
|
||||
permalink: /v5/integrating/sso/
|
||||
---
|
||||
|
||||
In order to allow seamless connection to an existing authentication system,
|
||||
Coral utilizes the industry standard [JWT Token](https://jwt.io/) to connect. To
|
||||
learn more about how to create a JWT token, see [this introduction](https://jwt.io/introduction/).
|
||||
|
||||
1. Visit: `https://{% raw %}{{ CORAL_DOMAIN_NAME }}{% endraw %} /admin/configure/auth`
|
||||
2. Scroll to the `Login with Single Sign On` section
|
||||
3. Enable the Single Sign On Authentication Integration
|
||||
4. Enable `Allow Registration`
|
||||
5. Copy the string in the `Secret` box
|
||||
6. Click Save
|
||||
|
||||
> **NOTE:** Replace the value of `{% raw %}{{ CORAL_DOMAIN_NAME }}{% endraw %}` with the location of your running instance of Coral.
|
||||
|
||||
You will then have to generate a JWT with the following claims:
|
||||
|
||||
- `jti` _(optional)_ - A unique ID for this particular JWT token. We recommend
|
||||
using a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier)
|
||||
for this value. This claim controls the logout functionality on the embed stream. To disable the “Sign Out” links in the embed, remove this claim, and disable “Login with email authentication” on the Comment Stream. You can then call `logout()` on the embed, or expire the token when the SSO user should be signed out of Coral.
|
||||
- `exp` _(optional)_ - When the given SSO token should expire. This is
|
||||
specified as a unix time stamp in seconds. Once the token has expired, a new
|
||||
token should be generated and passed into Coral. Without this parameter you will need to call `logout()` on the embed itself.
|
||||
- `iat` _(optional)_ - When the given SSO token was issued. This is required to
|
||||
utilize the automatic user detail update system. If this time is newer than
|
||||
the time we received the last update, the contents of the token will be used
|
||||
to update the user. Any claims not present on the jwt will be ignored on update, and will not overwrite existing values.
|
||||
- `user.id` **(required)** - the unique ID of the user from your authentication system.
|
||||
This is required to connect the user in your system to allow a seamless
|
||||
connection to Coral.
|
||||
- `user.email` **(required)** - the unique email address of the user from your
|
||||
authentication system. This is required to facilitate notification email's
|
||||
about status changes on a user account such as bans or suspensions.
|
||||
- `user.username` **(required)** - the username that should be used when being
|
||||
presented inside Coral to moderators and other users. There are no username validations or restrictions enforced by Coral when you're using SSO.
|
||||
- `user.badges` _(optional)_ - array of strings to be displayed as badges beside
|
||||
username inside Coral, visible to other users and moderators. For example, to indicate
|
||||
a user's subscription status. If you include the claim, but you are not passing a badge value, then use an empty array instead of null.
|
||||
- `user.role` _(optional)_ - one of "COMMENTER", "STAFF", "MODERATOR", "ADMIN". Will create/update
|
||||
Coral user with this permission level. When users have both an assigned role greather than COMMENTER and a badge, both will be displayed.
|
||||
- `user.url` _(optional)_ - url for user account management, where a user will
|
||||
be able to perform account management tasks such as changing password or
|
||||
deleting data. If provided, user will be able to access this URL by clicking
|
||||
on the "account" tab from the stream.
|
||||
|
||||
An example of the claims for this token would be:
|
||||
|
||||
```json
|
||||
{
|
||||
"jti": "151c19fc-ad15-4f80-a49c-09f137789fbb",
|
||||
"exp": 1572172094,
|
||||
"iat": 1562172094,
|
||||
"user": {
|
||||
"id": "628bdc61-6616-4add-bfec-dd79156715d4",
|
||||
"email": "bob@example.com",
|
||||
"username": "bob"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With the claims provided, you can sign them with the `Secret` obtained from the
|
||||
Coral administration panel in the previous steps with a `HS256` algorithm. This
|
||||
token can be provided in the above mentioned embed code by adding it to the
|
||||
`createStreamEmbed` function:
|
||||
|
||||
```js
|
||||
Coral.createStreamEmbed({
|
||||
// Don't forget to include the parameters from the
|
||||
// "Embed On Your Site" section.
|
||||
accessToken: "{{ SSO_TOKEN }}"
|
||||
});
|
||||
```
|
||||
|
||||
Or by calling the `login/logout` method on the embed object:
|
||||
|
||||
```js
|
||||
var embed = Coral.createStreamEmbed({
|
||||
// Don't forget to include the parameters from the
|
||||
// "Embed On Your Site" section.
|
||||
});
|
||||
|
||||
// Login the current embed with the generated SSO token.
|
||||
embed.login("{{ SSO_TOKEN }}");
|
||||
|
||||
// Logout the user.
|
||||
embed.logout();
|
||||
```
|
||||
|
||||
## External Integrations
|
||||
|
||||
You can integrate directly with the Coral GraphQL API in order to facilitate
|
||||
account updates for your users when using Coral SSO. The relevant mutations are
|
||||
as follows:
|
||||
|
||||
- `updateUserUsername` lets you update a given user with a new username using
|
||||
an admin token.
|
||||
- `updateUserEmail` lets you update a given user with a new email address
|
||||
using an admin token.
|
||||
- `deleteUser` lets you delete a given account using an admin token.
|
||||
Note that even with an admin token, you may not delete yourself via
|
||||
this method, and instead must use the `requestAccountDeletion`
|
||||
mutation instead. This differs from the `requestAccountDeletion` as
|
||||
it does the operation immediately instead of scheduling it as
|
||||
`requestAccountDeletion` does.
|
||||
- `requestUserCommentsDownload` lets you retrieve a given account's comments download. This mutation will provide you with a `archiveURL` that can be used to download a ZIP file containing the user's comment export.
|
||||
|
||||
If you're unsure on how to call GraphQL API's, refer to the section here on [Making your first GraphQL request](/talk/v5/api/overview/#making-your-first-request).
|
||||
|
||||
## Login Prompts
|
||||
|
||||
In order to handle login prompts (e.g. a user clicks on the sign in button) you can listen to the `loginPrompt` event.
|
||||
|
||||
```js
|
||||
var embed = Coral.createStreamEmbed({
|
||||
// Don't forget to include the parameters from the
|
||||
// "Embed On Your Site" section.
|
||||
events: function(events) {
|
||||
events.on("loginPrompt", function() {
|
||||
// Redirect user to a login page.
|
||||
location.href = "http://example.com/login";
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
## Troubleshooting JWT Validation Errors
|
||||
|
||||
In addition to the uniqueness constraints on User `id` and `email` values, each `user.id`/`user.email` combination must also be unique inside Coral. This is true for both Single Sign On users created by JWT tokens, as well as users that register/login using Coral’s built in “Login with email authentication”. You can not share authentication strategies for a single user; thus if a user logs in with SSO, they can not also login with email and vice versa. When you attempt to authenticate a JWT token with an email address that already exists in Coral with a different `user.id` than was passed on the token Coral will throw a Duplicate User error.
|
||||
|
||||
Any JWT validation errors thrown can be found in Coral's server logs.
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Social and Email Authentication
|
||||
permalink: /v5/integration/auth/
|
||||
---
|
||||
|
||||
## Authentication Strategies
|
||||
|
||||
There are several strategies to choose from when it comes to user authentication. Login methods can be enabled or disabled for both the **Coral Admin** and the **Comment Stream** separately thus allowing you to use different methods for different user groups.
|
||||
|
||||
You can also use "Allow Registration" to restrict or allow users that have not signed up before to register and create new user accounts.
|
||||
|
||||
To configure authentication for your instance of Coral, go to **Configure** > **Authentication**.
|
||||
|
||||
## Login with email authentication
|
||||
Best when you want Coral to manage all user registration, creation and authentication. Requires the least amount of setup and configuration. Users register by creating a Username (must be unique across users, no spaces or special chars), Email, and Password. All user details are stored in Coral's database.
|
||||
|
||||
By Default - users are sent a verification email and are prompted to verify email addresss, however completing the email verification step is not required to submit comments.
|
||||
|
||||
|
||||
## Login with OpenID Connect
|
||||
|
||||
Allows integration with your existing OpenID Connect authentication provider. When enabled this strategy adds a "Login with ___", button to Coral's login modal with the value you've specified for `Provider Name`.
|
||||
|
||||
With OpenID Connect authentication is handled by passing an access token parameter to the comment stream embed when placing the call to render it. To learn more please visit: https://openid.net/connect/
|
||||
|
||||
## Login with Single Sign On
|
||||
|
||||
When you want users to authenticate via your existing identity system, and be automatically registered/logged into Coral. Users register, login, and manage their user details on your existing site, your user database maintains the source of truth for all user information. Users are created and updated in Coral via jwt tokens passed from your auth service to Coral.
|
||||
|
||||
Requires advanced configuration, please see: [Single Sign On](/talk/v5/integrating/sso/)
|
||||
|
||||
## Login with Google & Facebook
|
||||
Allows users to use existing social media account to sign in. Requires you to setup API keys with social sign on provider, and configure your API keys in Coral.
|
||||
|
||||
Users register and login via a *“Sign in with ….”*, and are not required to set a password if Email based authentication is not enabled. Users with social accounts are prompted to associate an email address with their social profile to enable notifications.
|
||||
|
||||
|
||||
## Commenter Account Management Features
|
||||
|
||||
Optional features you can enable or disable to allows users to:
|
||||
* change their usernames (once every 14 days)
|
||||
* download their comments
|
||||
* delete their account details.
|
||||
|
||||
## Session settings
|
||||
|
||||
Determines length of user sessions when not using SSO or when `exp` claim is not provided on jwt. Default: `90 Days`.
|
||||
|
||||
## Username Restrictions
|
||||
|
||||
Coral usernames are subject to the following validations:
|
||||
* USERNAME_REGEX =
|
||||
`(/^[a-zA-Z0-9_.]+$/)`
|
||||
* USERNAME_MAX_LENGTH = `30`
|
||||
* USERNAME_MIN_LENGTH = `3`
|
||||
|
||||
*NOTE: SSO users are not subject to username restricitons*
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Customizing Styles with CSS
|
||||
permalink: /v5/css/
|
||||
---
|
||||
|
||||
You can add your own stylesheet in **Admin** > **Configure** > **Advanced** > **Custom CSS**.
|
||||
|
||||
### Use CSS Variables
|
||||
|
||||
We recommend using CSS Variables to broadly apply changes to certain styling aspects like color or fonts.
|
||||
|
||||
Here is a CSS example that modifies some of the CSS Variables.
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Change primary font */
|
||||
--font-family-primary: 'Verdana';
|
||||
|
||||
/* Remove round corners */
|
||||
--round-corners: 0px;
|
||||
}
|
||||
```
|
||||
|
||||
You can find all CSS Variables using the Web Inspector or navigate to https://github.com/coralproject/talk/blob/master/CSS_VARIABLES.md for a comprehensive list and additional information.
|
||||
|
||||
> **NOTE:** Before 6.3.0 Coral uses a different set of CSS Variables. Navigate to the link above to get information on upgrading.
|
||||
|
||||
### Use stable CSS Class Names
|
||||
|
||||
If you would like to change the styling of any elements of the comment embed, we provide global classnames. Most elements will be tagged with either `.coral` or `.coral-stream`.
|
||||
|
||||
The easiest way to find the classname for the element you're looking for is to use the web inspector, and then update your stylesheet accordingly.
|
||||
|
||||
You can also navigate to https://github.com/coralproject/talk/blob/master/src/core/client/stream/classes.ts to see available stable class names.
|
||||
|
||||
### Custom body class for theming
|
||||
|
||||
You can set the class name of the `<body>` tag inside the embed by using the `bodyClassName` parameter when calling `Coral.createStreamEmbed`:
|
||||
|
||||
```js
|
||||
Coral.createStreamEmbed({
|
||||
bodyClassName: "pink"
|
||||
});
|
||||
```
|
||||
|
||||
This will allow your styles to include variations:
|
||||
|
||||
```css
|
||||
.pink button.coral {
|
||||
background: pink;
|
||||
}
|
||||
```
|
||||
|
||||
### Reaction styling
|
||||
|
||||
As of Coral 6.3.0, Coral has support for styling based on the number of
|
||||
reactions that a given comment has received. It does so via the:
|
||||
|
||||
```sh
|
||||
.coral-reacted-{{ n }}
|
||||
```
|
||||
|
||||
Where `{{ n }}` is the number of reactions the comment has received. You can
|
||||
invert this when creating CSS to allow you to highlight comments that have at
|
||||
least `{{ n }}` reactions. For example, if you wanted to add a coral color to
|
||||
comments with at least 3 reactions, you could write:
|
||||
|
||||
```css
|
||||
.coral-comment .coral-indent {
|
||||
background-color: coral;
|
||||
}
|
||||
|
||||
.coral-reacted-0 .coral-indent,
|
||||
.coral-reacted-1 .coral-indent,
|
||||
.coral-reacted-2 .coral-indent {
|
||||
background-color: transparent;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: Emails & Notifications
|
||||
permalink: /v5/notifications/
|
||||
---
|
||||
|
||||
There are several email notifications included with Coral by default that will be sent to users based on user activity.
|
||||
|
||||
_NOTE: Notifications are only supported for users that have a valid email address! If you are using authenticate via Facebook, Google, or OpenIDConnect, user emails are not automatically required._
|
||||
|
||||
### Configuring SMTP
|
||||
|
||||
You must setup SMTP to send email notifications.
|
||||
See our documentation on [Email SMTP Settings](/talk/v5/administration/#email-smtp-settings) for reference.
|
||||
|
||||
When running in production we recommend using a 3rd party mail service provider like SendGrid or MailGun.
|
||||
|
||||
|
||||
### Notification Types
|
||||
|
||||
In addition to the comment activity notifications, Coral also provides users with several transactional notifications about their user profile.
|
||||
|
||||
Comment activity notifications are set to OFF for individual users by default, and commenters must opt-in to enable activity notifications. Commenters cannot enable notifications until they have verified their email, and are not able to opt-out of transactional notifications such as when a user has been Banned or Suspended.
|
||||
|
||||
|
||||
#### Type: User Profile Notifications
|
||||
|
||||
* When a new Staff, Moderator, or Admin user is invited, sends invitation
|
||||
* When any user registers, sends confirmation and email verification link
|
||||
* When user requests password reset, sends reset link
|
||||
* When user changes username, sends change confirmation
|
||||
* When user changes their email address, sends change confirmation and email verification link
|
||||
* When a user is banned or suspended, sends notice
|
||||
* When comment history is ready for download (only one link can be generated every 7 days, and the link is valid for 24 hours), sends download link
|
||||
* When user requests account deletion, cancels account deletion, or account deletion has been completed, sends confirmation
|
||||
|
||||
_NOTE: Users can not opt-out of User Profile Notifications_
|
||||
|
||||
|
||||
#### Type: Comment Activity Notifications
|
||||
|
||||
Commenter users can subscribe to the following comment activity notificaitons:
|
||||
|
||||
* When one of my comments receives a reply
|
||||
* When a Staff, Moderator, or Admin user replies to one of my comments
|
||||
* When a Moderator Features one of my comments
|
||||
* When one of my pending comments has been APPROVED or REJECTED
|
||||
|
||||
|
||||
Commenters have 3 options for activity notification frequency:
|
||||
* Immediately
|
||||
* Hourly
|
||||
* Daily
|
||||
|
||||
Immediately will send one email notification per occurance, versus sending an hourly or daily summary with all occurances listed.
|
||||
|
||||
### Email Templates
|
||||
|
||||
Email templates are text based and support translations. Many of the transactional emails can be modified by Moderators at the time of sending to provide additional context to users.
|
||||
|
||||
Coral does not currently support customizing email templates beyond the default templates.
|
||||
|
||||
To view the current email text strings for US English visit: https://github.com/coralproject/talk/blob/master/src/core/server/locales/en-US/email.ftl
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: GDPR Compliance
|
||||
permalink: /v5/integrating/gdpr/
|
||||
---
|
||||
|
||||
In order to facilitate compliance with the
|
||||
[EU General Data Protection Regulation (GDPR)](https://www.eugdpr.org/), Coral
|
||||
provides features so your users can change and manage their own data.
|
||||
|
||||
Even if GDPR will not apply to you, it is recommended to enable these
|
||||
features as a best practice to provide your users with control over their own
|
||||
data.
|
||||
|
||||
## GDPR Feature Overview
|
||||
|
||||
Integrating our GDPR tools will give your users and organizations the following benefits:
|
||||
|
||||
- **Download my comment data**: Users can request a download of their comments. An email with a link is emailed to them to download a CSV with each comment they've made, what story it was made on, and the comment's ID and timestamp.
|
||||
- **Delete my account**: Users can request deletion of their account. Deleted account requests are pending for 24 hours to allow the user to download their comments, or to change their mind and reactivate their account before the expiry. Account deletions remove all of their comments from the site, all their comments and actions from the database, and their account info from our system.
|
||||
- **Add an email to an OAuth/external account**: Users are prompted to add an email to their non-Coral account (Facebook, Google, external, etc) so that they can take part in GDPR and other features requiring email communication.
|
||||
- **Change my username**: Users can update their username. This is capped at once every 2 weeks.
|
||||
- **Change my email**: Users can change their email.
|
||||
- **Change my password**: Users can change their password.
|
||||
|
||||
## GDPR with SSO
|
||||
|
||||
As many newsrooms often implement their own [SSO solutions](/talk/v5/integrating/sso/),
|
||||
we also provide API support to manage GDPR features directly from your own Account or My Profile page.
|
||||
|
||||
We provide the following GraphQL mutations designed to allow you to integrate it into your existing user
|
||||
interfaces or exports.
|
||||
|
||||
- `requestUserCommentsDownload` - lets you grab the direct link to download a users
|
||||
account in a zip format. From there, you can integrate it into your existing
|
||||
data export or simply proxy it to the user to allow them to download it
|
||||
elsewhere in your UI.
|
||||
- `deleteUserAccount` - lets you delete the specified user
|
||||
|
||||
**Note: These mutations require an administrative token**
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Comment Count Integration
|
||||
permalink: /v5/integrating/counts/
|
||||
---
|
||||
|
||||
Add the `count.js` script to your `html` tree. On a page that includes the _Stream Embed_ this is done for you automatically, however for best performance we recommend to include it into the `<head>` tag.
|
||||
|
||||
```html
|
||||
<script class="coral-script" src="//{{ CORAL_DOMAIN_NAME }}/assets/js/count.js" defer></script>
|
||||
```
|
||||
|
||||
> **NOTE:** Replace the value of `{% raw %}{{ CORAL_DOMAIN_NAME }}{% endraw %}` with the location of your running instance of Coral.
|
||||
|
||||
> **NOTE:** `class="coral-script"` is needed to support IE11.
|
||||
|
||||
Insert an html element with the class `coral-count` and configure it using `data-coral-*` attributes.
|
||||
|
||||
```html
|
||||
<span class="coral-count" data-coral-url="http://example.com/blog-entry-1/"></span>
|
||||
```
|
||||
|
||||
After successful injection it will become:
|
||||
|
||||
```html
|
||||
<span class="coral-count" data-coral-url="http://example.com/blog-entry-1/">
|
||||
<span class="coral-count-number">5</span>
|
||||
<span class="coral-count-text">Comments</span>
|
||||
</span>
|
||||
```
|
||||
|
||||
### Available `data-coral-*` attributes
|
||||
|
||||
Set the class of your html element to `coral-count` in order to get story counts. The following `data-coral-*` attributes will configure the output:
|
||||
|
||||
- `data-coral-id` – The id of the story of which counts should be injected.
|
||||
- `data-coral-url` – The url of the story of which counts should be injected.
|
||||
- `data-coral-notext` – If set to `"true"`, only the count number will be injected
|
||||
|
||||
Either `data-coral-id` or `data-coral-url` should be set. If none are provided the story url
|
||||
will be retrieved from the [canonical url reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Choosing_between_www_and_non-www_URLs#Using_%3Clink_relcanonical%3E) `<link rel="canonical" href="...">` or inferred using the current page url.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Slack
|
||||
permalink: /v5/integrating/slack/
|
||||
---
|
||||
|
||||
Coral version 5.4+ supports built-in Slack integration to help you forward comments from your moderation queues into appropriate Slack channels.
|
||||
|
||||
## Creating a Slack App
|
||||
|
||||
To enable web hooks that we will use to forward comments, you'll need to create an App and give it permissions over a channel.
|
||||
|
||||
For details on how to create a Slack app with webhooks, please go to:
|
||||
|
||||
https://slack.com/intl/en-ca/help/articles/115005265063-incoming-webhooks-for-slack
|
||||
|
||||
After you have created a Slack app with a webhook, you can use it in your Coral configuration.
|
||||
|
||||
1. Sign into the administration side of your Coral deployment.
|
||||
2. Select _Configure_ from the top navigation.
|
||||
3. Select _Slack_ from the side navigation for the configuration area.
|
||||
4. Here you can configure a Slack channel.
|
||||
5. Paste in the webhook URL you created for your Slack app and select which comment categories you want to receive notifications for.
|
||||
|
||||
## I need to find the webhook URL again, where is it?
|
||||
|
||||
Webhooks options are tied to a Slack app and can be found under your app settings at:
|
||||
|
||||
https://api.slack.com/apps
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Event Tracking
|
||||
permalink: /v5/integrating/events/
|
||||
---
|
||||
|
||||
The Coral embed script can emit events to the analytics tool of your choice. Common user actions, such as successfully posting a comment, posting a reaction, or changing a setting are all capturable with events.
|
||||
|
||||
## Analytics Tool Integration
|
||||
|
||||
To enable event tracking, modify your embed script to subscribe to the events hook. You will also need to add code to your page that sends the events to your analytics system. The particular way you send this will depend on what tool you’re using. Refer to your tool’s API and docs to determine this.
|
||||
|
||||
In this example, we’re logging all events to the console as well as sending the `createComment.success` event.
|
||||
|
||||
```js
|
||||
<script>
|
||||
const CoralStreamEmbed = Coral.createStreamEmbed({
|
||||
events: function(events) {
|
||||
events.onAny(function(eventName, data) {
|
||||
console.log(eventName, data);
|
||||
if (eventName === 'createComment.success') {
|
||||
my_event_tracker.send('createComment', data);
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Available Events
|
||||
|
||||
A complete list of trackable events is available on GitHub: https://github.com/coralproject/talk/blob/master/CLIENT_EVENTS.md
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: SEO & iframes
|
||||
permalink: /v5/seo/
|
||||
---
|
||||
|
||||
Since Coral is embedded in an article page using an iframe, we often get asked what affect,
|
||||
if any, iframes have on a publisher’s SEO. We asked Hart Van Santvoord, Vox Media’s resident
|
||||
SEO expert to give us some insight to help answer these questions.
|
||||
|
||||
We can confidently say that today's modern crawlers are able to crawl and index content in
|
||||
iframes and that they do their best to associate the two and identify which version is preferred.
|
||||
This isn’t as relevant however for content that is complementary to an article, such as comments
|
||||
here in Coral’s case, or with other embedded content like documents or live blogs. It’s is more
|
||||
important for products or people who are iframing entire pages, such as for syndication.
|
||||
|
||||
[Here's commentary on that from John Mueller of Google, probably the most trusted
|
||||
Google technical SEO resource we have.](https://www.seroundtable.com/google-iframes-seo-24098.html )
|
||||
|
||||
When newsrooms and organizations come to us with SEO questions, it usually falls into
|
||||
2 use cases.
|
||||
|
||||
1. We need our comments to be indexed to help our SEO!
|
||||
|
||||
For this case, we are very confident that the way Coral is currently handling comments
|
||||
via iframe provides the SEO benefit publishers are looking for:
|
||||
|
||||
* Google can crawl it and associate it with the container article/page
|
||||
* They're certainly seeing commenting activity as a positive sign of engagement which
|
||||
Google cares about A LOT
|
||||
* Ongoing commenting also indicates to google that the page is evolving/updating,
|
||||
another strong signal
|
||||
* Serving via iframe typically improves performance as opposed to including the content
|
||||
directly on the parent page, and faster/better speed and UX is yet another strong positive
|
||||
signal to Google
|
||||
|
||||
For an example of how the Vox Media SEO team has analyzed iframe content outside of Coral,
|
||||
we can can look The Verge. The Verge leverages live blogging for certain types of events,
|
||||
[such as iPhone events](https://www.theverge.com/2019/9/10/20840715/apple-iphone-11-event-live-blog-announcement-photos).
|
||||
In this case, similar to Coral, the live blog content itself is embedded on The Verge article
|
||||
via iframe but no doubt leads to a more engaged audience who stick around longer, refreshes,
|
||||
clicks load more, etc. We see these articles perform well in search in a super competitive
|
||||
space on event days.
|
||||
|
||||
That brings us to our 2nd use case:
|
||||
|
||||
2. Please, make sure my comments are NOT indexed!
|
||||
|
||||
In this request, the use case is slightly more complicated due to the fact we know
|
||||
Google can crawl and index comment content. But, given that Coral does not also
|
||||
create creating a page just with the comments, we don’t see a chance Google would
|
||||
Surface just comment content in search results outside of the article in which they’re
|
||||
embedded.
|
||||
|
||||
If you’re a publisher in this scenario and are worried about the content in comments
|
||||
reflecting poorly on your site/brand then this seems to be as much as moderation issue
|
||||
As a Google one, and again, Coral does not provide a way for Google to crawl, index,
|
||||
and serve up commenting content outside the context of the article regardless.
|
||||
|
||||
If you are a newsroom doing creative things with your comments and perhaps would
|
||||
Have the need to create a separate page with only comments, then you can certainly explore
|
||||
Using tools like a canonical tag to redirect that page back to the container page, and other
|
||||
SEO best practices for this and other edge cases.
|
||||
@@ -0,0 +1,215 @@
|
||||
---
|
||||
title: GraphQL API Overview
|
||||
permalink: /v5/api/overview/
|
||||
---
|
||||
|
||||
## GraphQL API
|
||||
|
||||
Our API is generally served via GraphQL at `/api/graphql` on your Coral installation. If you're running Coral locally, this would be https://localhost:8080/api/graphql.
|
||||
|
||||
## Enabling & Using `/graphiql`
|
||||
|
||||
It is not required to enable the `/graphiql` playground to use Coral’s GraphQL API. The playground simply provides an easy way to explore and interact with Coral’s GraphQL schema. You can enable the GraphiQL interface at https://localhost:3000/graphiql (Note the port number here is not 8080, this is because this route is directly served by the server, and not the webpack development server) when running in development. You can do this by setting `ENABLE_GRAPHIQL=true`.
|
||||
|
||||
**(🚨 Note 🚨) we do not recommend using this in production environments as it disables many safety features used by the application**.
|
||||
|
||||
When submitting requests in the `/graphiql` playground, it is necessary to include an ADMIN level bearer token to authenticate your requests. When signed into Coral as an ADMIN level user, you can grab your current access token from your browser’s local storage. Add the following to the HTTP Headers section of your GraphiQL call.
|
||||
```sh
|
||||
{“authorization”:”Bearer <token>”}
|
||||
```
|
||||
|
||||
|
||||
## Making your first request
|
||||
|
||||
To learn a bit about how to interact with Coral, we'll query for comments on a
|
||||
page of Coral.
|
||||
|
||||
The GraphQL endpoint we have can be used with any HTTP client available, but our
|
||||
examples below will use the common `curl` tool:
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--url "http://localhost:8080/api/graphql" \
|
||||
--header "content-type: application/json" \
|
||||
--data '{"query":"query GetComments($url: String!) {story(url: $url) { id metadata { title } url comments { nodes { id body author { id username } } } } }","variables":{"url":"http://localhost:8080/"},"operationName":"GetComments"}'
|
||||
```
|
||||
|
||||
When you unpack that, it's really quite simple. We're executing a `POST` request
|
||||
to the `/api/graphql` route of the local Talk server with the GraphQL
|
||||
request we want to make. It's composed of the `query`, `variables`, and
|
||||
`operationName`.
|
||||
|
||||
```graphql
|
||||
query GetComments($url: String!) {
|
||||
story(url: $url) {
|
||||
metadata {
|
||||
title
|
||||
}
|
||||
url
|
||||
comments {
|
||||
nodes {
|
||||
body
|
||||
author {
|
||||
username
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We are grabbing the asset with the specified `$url`, and grabbing it's title and the comments under it.
|
||||
|
||||
We can then also specify our variables to the query being executed (in this
|
||||
case, the url for the page where we have comments on our local install of Coral):
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "http://localhost:8080/"
|
||||
}
|
||||
```
|
||||
|
||||
It's also sometimes common to have multiple queries within a query, which is
|
||||
where the `operationName` comes into play, where we simply specify the named
|
||||
query that we want to execute (in this case, `GetComments`).
|
||||
|
||||
To get a deeper understanding of GraphQL queries, read up on
|
||||
[GraphQL Queries and Mutations](http://graphql.org/learn/queries/).
|
||||
|
||||
## Understanding the response
|
||||
|
||||
Once you completed the above GraphQL query with `curl`, you'll get a response
|
||||
sort of like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"story": {
|
||||
"metadata": {
|
||||
"title": "Coral 5.0 – Embed Stream"
|
||||
},
|
||||
"url": "http://localhost:8080/",
|
||||
"comments": {
|
||||
"nodes": [
|
||||
{
|
||||
"body": "First comment!",
|
||||
"author": {
|
||||
"username": "wyatt.johnson"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All of the parameters you requested should be available under the `data`
|
||||
property. Any errors that you get would appear in a `errors` array at the top
|
||||
level, like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "The specified story URL does not exist in the permitted domains list.",
|
||||
"locations": [
|
||||
{
|
||||
"line": 2,
|
||||
"column": 3
|
||||
}
|
||||
],
|
||||
"path": ["story"],
|
||||
"extensions": {
|
||||
"code": "STORY_URL_NOT_PERMITTED",
|
||||
"id": "e255e860-d3ab-11e9-acdf-b9e9700f06fa",
|
||||
"type": "INVALID_REQUEST_ERROR",
|
||||
"message": "The specified story URL does not exist in the permitted domains list."
|
||||
}
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"story": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You should know that any property that is marked with a `!` is considered
|
||||
required, and non-nullable, which means you can always guarantee on it being
|
||||
there in your request if there were no errors.
|
||||
|
||||
## Authorizing a request
|
||||
|
||||
Some queries you may notice seem to return an error of
|
||||
`USER_NOT_ENTITLED`. It's likely the case that you are making a request to a
|
||||
route that requires authorization. You can perform authorization a few ways in
|
||||
Talk.
|
||||
|
||||
Essentially, you need to get access to a JWT token that you can use to authorize
|
||||
your requests.
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--url http://localhost:3000/api/auth/local \
|
||||
--header 'content-type: application/json' \
|
||||
--data '{ "email": "${EMAIL}", "password": "${PASSWORD}"}'
|
||||
```
|
||||
|
||||
Which returns a response similar to:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "${TOKEN}"
|
||||
}
|
||||
```
|
||||
|
||||
Where `${EMAIL}` is the email address of an admin user, and `${PASSWORD}` is the password for that admin user. This will generate a short term token (valid for 90 days). To generate a long lived access token (or Personal Access Token), you have to exchange the token generated above to create a new long lived token:
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--url "http://localhost:3000/api/graphql" \
|
||||
--header 'authorization: Bearer ${TOKEN}' \
|
||||
--header 'content-type: application/json' \
|
||||
--data '{"query":"mutation CreateAccessToken { createToken(input: { clientMutationId: \"\", name: \"My PAT\" }) { signedToken }}","operationName":"CreateAccessToken"}'
|
||||
```
|
||||
|
||||
Which returns a response similar to:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"createToken": {
|
||||
"signedToken": "${TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Where `${TOKEN}` in the request being from the previous set of login steps and returning a new `signedToken` as `${TOKEN}` which can be used instead of the previous `${TOKEN}` value in the below examples.
|
||||
|
||||
Once you have your access token, you can substitute it as `${TOKEN}` in your
|
||||
`curl` request as follows:
|
||||
|
||||
### Bearer Token
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--url "http://localhost:8080/api/graphql" \
|
||||
--header "content-type: application/json" \
|
||||
--header "Authorization: Bearer ${TOKEN}" \
|
||||
--data '{"query":"query GetComments($url: String!) {story(url: $url) { id metadata { title } url comments { nodes { id body author { id username } } } } }","variables":{"url":"http://localhost:8080/"},"operationName":"GetComments"}'
|
||||
```
|
||||
|
||||
### Cookie
|
||||
|
||||
```sh
|
||||
curl --request POST \
|
||||
--url "http://localhost:8080/api/graphql" \
|
||||
--header "content-type: application/json" \
|
||||
--cookie "authorization=${TOKEN}"
|
||||
--data '{"query":"query GetComments($url: String!) {story(url: $url) { id metadata { title } url comments { nodes { id body author { id username } } } } }","variables":{"url":"http://localhost:8080/"},"operationName":"GetComments"}'
|
||||
```
|
||||
|
||||
## Persisted Queries
|
||||
|
||||
You might see an error like `RAW_QUERY_NOT_AUTHORIZED`. This means that you attempted to use either no token, or a token from a user without admin privileges. In Coral, we whitelist the GraphQL mutations and queries that are performed by the applications for security reasons meaning that only admin users can make arbitrary queries against the GraphQL API.
|
||||
@@ -1,67 +0,0 @@
|
||||
---
|
||||
title: FAQ
|
||||
permalink: /faq/
|
||||
---
|
||||
|
||||
## How can I get help, submit a bug, or suggest a feature?
|
||||
|
||||
There are a few avenues to get in touch with us and others in the community for help.
|
||||
|
||||
To log a bug or request a feature, submit a Support ticket ([support@coralproject.net](mailto:support@coralproject.net)) and someone from our team will get back to you.
|
||||
|
||||
You can also request help on Github by [submitting an issue](https://github.com/coralproject/talk/issues). This also increases your chances of having someone from the community respond to help.
|
||||
|
||||
## How can our dev team contribute to Talk?
|
||||
|
||||
We are lucky to work with newsroom dev teams and individual contributors who span the world, and come from newsrooms of all sizes. You can read our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md) to get started, but feel free to reach out to us via Github too.
|
||||
|
||||
## What if we want to add a feature you don't have?
|
||||
|
||||
Talk is open source, so you're free to develop additional functionality and [submit a pull request](https://github.com/coralproject.net/talk).
|
||||
|
||||
## Do you have GDPR features?
|
||||
|
||||
Yes! Please read our [GDPR documentation](/talk/integrating/gdpr/) for more information and instructions to get started.
|
||||
|
||||
## Can I import my existing comments?
|
||||
|
||||
Yes! We have a community-supported [import framework](https://github.com/coralproject/talk-importer) that you can use to migrate your existing comments.
|
||||
|
||||
## What support is available?
|
||||
|
||||
Our team is small, so it's difficult for us to provide support packages. However, you can always email us at [support@coralproject.net](mailto:support@coralproject.net), and we can help answer your questions. In some cases, we can provide premium support packages either with our team, or through partners. You can inquire about this via the support email address above.
|
||||
|
||||
## Is there a hosted version I can purchase by monthly subscription?
|
||||
|
||||
Yes! [Visit our hosting page](https://coralproject.net/pricing/) to submit information and receive a quote.
|
||||
|
||||
## Where is our data when we use Talk?
|
||||
|
||||
If you are hosting Talk on your own:
|
||||
|
||||
* Your data is stored in a MongoDB database that you provide
|
||||
* The Coral team doesn’t have any access to your data
|
||||
|
||||
If you are using our Hosted SaaS version of Talk:
|
||||
|
||||
* Your data is stored in a dedicated MongoDB database that is provisioned for you
|
||||
* Your data is completely isolated from other customers' data
|
||||
* The Coral Team and its third-party database hosting providers use strict access controls and auditing to protected your data from unauthorized access
|
||||
|
||||
## Does Talk have any automated moderation features to protect against spam and trolling?
|
||||
|
||||
As well as basic word/phrase filtering and user reputation scores, Talk offers optional advanced features via third-party services:
|
||||
|
||||
* The [Toxic Comments plugin](/talk/plugin/talk-plugin-toxic-comments) integrates with the [Perspective API from Google](https://www.perspectiveapi.com/) to detect the likelihood of toxicity of comments in real-time
|
||||
* The [Akismet plugin](/talk/plugin/talk-plugin-akismet) detects and blocks spam comments
|
||||
|
||||
## How much can I customize Talk?
|
||||
|
||||
* The CSS of the Talk comment stream can be customized by [adding your own CSS via an external stylesheet](/talk/integrating/styling-css/)
|
||||
* The functionality of Talk can be [extended through the plugin framework](/talk/plugins/)
|
||||
|
||||
## How much does Talk cost?
|
||||
|
||||
* The Talk software is freely available under the Apache 2.0 open source license
|
||||
* Associated costs are those for the infrastructure required to run Talk (i.e. cloud hosting fee or bare-metal server costs)
|
||||
* The Coral Project offers a SaaS/hosted version of Talk. Please [get in touch with us](https://coralproject.net/pricing/) to discuss pricing for your requirements.
|
||||
@@ -3,10 +3,10 @@ title: Contact
|
||||
permalink: /contact/
|
||||
---
|
||||
|
||||
## How can I get help integrating Talk into my newsroom?
|
||||
## How can I get help integrating Coral into my newsroom?
|
||||
|
||||
We're here to help with newsrooms of all sizes. Connect with us [here](https://coralproject.net/contact/) to see how we can help you!
|
||||
|
||||
We're here to help with newsrooms of all sizes. Email our Support Team
|
||||
([support@coralproject.net](mailto:support@coralproject.net)) to set up a meeting.
|
||||
|
||||
## How do I request a feature or submit a bug?
|
||||
|
||||
|
||||
+19
-24
@@ -1,16 +1,16 @@
|
||||
---
|
||||
title: Talk Quickstart
|
||||
permalink: /
|
||||
permalink: /installation-quickstart/
|
||||
---
|
||||
|
||||
Online comments are broken. Our open-source Talk tool rethinks how moderation,
|
||||
comment display, and conversation function, creating the opportunity for safer,
|
||||
smarter discussions around your work.
|
||||
smarter discussions around your work.
|
||||
|
||||
More than 60 newsrooms use Talk to run their on-site communities, including the Washington Post, the Wall Street Journal, and IGN.
|
||||
More than 60 newsrooms use Talk to run their on-site communities, including the Washington Post, the Wall Street Journal, and IGN.
|
||||
|
||||
[Read more about our product features and
|
||||
goals here](https://coralproject.net/talk).
|
||||
goals here](https://coralproject.net/).
|
||||
|
||||
<div class="callout">
|
||||
We offer hosting and support packages for Talk. [Contact us for more information.](https://coralproject.net/pricing/)
|
||||
@@ -31,7 +31,7 @@ to persist data. The following versions are supported:
|
||||
- MongoDB 3.2+
|
||||
- Redis 3.2.5+
|
||||
|
||||
You can run Talk (and its dependencies) locally or from [Docker](https://www.docker.com/community-edition#/download) containers. Docker is used in the local example below for the database and cache, however it is possible to run Talk without Docker by configuring your own MongoDB and Redis instances. We have tested Talk
|
||||
You can run Talk (and its dependencies) locally or from [Docker](https://www.docker.com/community-edition#/download) containers. Docker is used in the local example below for the database and cache, however it is possible to run Talk without Docker by configuring your own MongoDB and Redis instances. We have tested Talk
|
||||
and this documentation with Docker versions 17.06.2+.
|
||||
|
||||
An optional dependency for Talk is
|
||||
@@ -44,7 +44,7 @@ and this documentation with versions 1.14.0+.
|
||||
### Installation from Docker
|
||||
|
||||
To use Talk without major customization you can run the application using our
|
||||
provided docker image.
|
||||
provided docker image.
|
||||
|
||||
Start by making a new directory and create a file called `docker-compose.yml` and copy the following:
|
||||
|
||||
@@ -52,7 +52,7 @@ Start by making a new directory and create a file called `docker-compose.yml` an
|
||||
# For details on the syntax of docker-compose.yml files, check out:
|
||||
# https://docs.docker.com/compose/compose-file/compose-file-v2/
|
||||
|
||||
version: '2'
|
||||
version: "2"
|
||||
services:
|
||||
talk:
|
||||
image: coralproject/talk:4
|
||||
@@ -89,7 +89,6 @@ volumes:
|
||||
The environment variables listed above are the bare minimum needed to run the demo, for more configuration
|
||||
variables, check out the [Configuration](/talk/configuration/) section.
|
||||
|
||||
|
||||
And you can then start it with:
|
||||
|
||||
```bash
|
||||
@@ -121,7 +120,6 @@ redis_1 docker-entrypoint.sh redis ... Up 6379/tcp
|
||||
talk_1 yarn start Up 0.0.0.0:3000->3000/tcp
|
||||
```
|
||||
|
||||
|
||||
You now have a Talk instance up and running! Continue on to the [Setup](#setup) section for details on how to complete the
|
||||
initial setup and get started using Talk.
|
||||
|
||||
@@ -157,7 +155,6 @@ Didn't work? Sometimes you may already have a container running on these ports,
|
||||
run `docker ps` to see what other containers you have running and running
|
||||
`docker stop <id>` on those containers to stop them.
|
||||
|
||||
|
||||
_This documentation assumes that you will be running MongoDB on
|
||||
`127.0.0.1:27017` and Redis on `127.0.0.1:6379`. The above Docker commands bind
|
||||
MongoDB and Redis on these interfaces for you._
|
||||
@@ -188,27 +185,26 @@ Continue onto the [Setup](#setup) section for details on how to complete the
|
||||
installation and get started using Talk.
|
||||
|
||||
## Setup
|
||||
|
||||
### Create Admin Account
|
||||
|
||||
With Talk running, you can now navigate to
|
||||
[http://127.0.0.1:3000/admin/install](http://127.0.0.1:3000/admin/install)
|
||||
and walk through the initial setup steps.
|
||||
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
|
||||
- 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._
|
||||
|
||||
Once the setup wizard has been completed you can log into Talk ([http://127.0.0.1:3000/](http://127.0.0.1:3000/)) using the email address and password for the Admin user account that you just created.
|
||||
|
||||
From here you can test out features in Talk, see comments in the admin interface where you can do moderation, and configure the user experience.
|
||||
In the next step you'll create some user comments to moderate.
|
||||
Once the setup wizard has been completed you can log into Talk ([http://127.0.0.1:3000/](http://127.0.0.1:3000/)) using the email address and password for the Admin user account that you just created.
|
||||
|
||||
From here you can test out features in Talk, see comments in the admin interface where you can do moderation, and configure the user experience.
|
||||
In the next step you'll create some user comments to moderate.
|
||||
|
||||
### Demo Embedded Comments
|
||||
|
||||
@@ -221,7 +217,7 @@ In order for the demo to work, you must add
|
||||
`https://docs.coralproject.net/` to your
|
||||
permitted domains list. You can do this by visiting
|
||||
[http://127.0.0.1:3000/admin/configure](http://127.0.0.1:3000/admin/configure)
|
||||
now and selecting *Tech Settings* from the sidebar.
|
||||
now and selecting _Tech Settings_ from the sidebar.
|
||||
|
||||
Once you have added the domain of these docs, you can click the button below.
|
||||
|
||||
@@ -235,13 +231,12 @@ Once you have added the domain of these docs, you can click the button below.
|
||||
|
||||
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](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
|
||||
- [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!
|
||||
|
||||
|
||||
+2
-1
@@ -77,7 +77,8 @@ permalink: /pre-launch-checklist/
|
||||
- See [our blog for more information](https://coralproject.net/blog/slacking-on/)
|
||||
|
||||
|
||||
- [ ] Has your community team configured Talk to match your community strategy?
|
||||
- [ ] Have you configured Talk’s admin settings and determined your community strategy?
|
||||
- See [our tutorial for more information](https://docs.coralproject.net/talk/when-youve-installed-talk/)
|
||||
- See [Configuring Talk](/talk/configuring-talk/)
|
||||
|
||||
|
||||
@@ -81,12 +81,17 @@ Usage: cli-assets [options] [command]
|
||||
Commands:
|
||||
|
||||
list [options] list all the assets in the database
|
||||
debug <url> prints the scraped metadata from that URL
|
||||
refresh [age] queues the assets that exceed the age requested
|
||||
update-url <assetID> <url> update the URL of an asset
|
||||
merge <srcID> <dstID> merges two assets together by moving comments from src to dst and deleting the src asset
|
||||
rewrite [options] <search> <replace> rewrites asset url's using the provided regex replacement pattern
|
||||
```
|
||||
|
||||
When using the `refresh` command, the `age` value specifies how far back in time to re-scrape assets; i.e. to re-scrape everything that was scraped in the last week use `1w` or `7d`. Supports ms (milliseconds), s (seconds), m (minutes), h (hours), d (days) and w (weeks). Assets that have not been scraped will also be queued for scraping.
|
||||
|
||||
See also, [Asset Scraping](/talk/integrating/asset-scraping/) for more details about asset scraping.
|
||||
|
||||
## Setting up the application
|
||||
You can also run a setup wizard to setup the wizard using `./bin/cli setup`. Below is a list of additional options available for this command:
|
||||
```
|
||||
+1
-1
@@ -57,7 +57,7 @@ The timeframe in seconds in which commenters have to edit their comment.
|
||||
|
||||
#### Close Comments After
|
||||
|
||||
Default time after which all comment streams will close.
|
||||
Default time after which all comment streams will close. Applies to assets created in Talk after this configuration is saved, and does not update existing assets.
|
||||
|
||||
### Moderation Settings
|
||||
|
||||
+2
@@ -87,6 +87,8 @@ The most important predictors of the success of an online community are:
|
||||
|
||||
#### Effectively
|
||||
|
||||
* Keep conversations fresh and reduce moderation overhead by configuring Talk to automatically close commenting after a specified time window. Should comments be open for a day, a week, or a month? Set a manageable window and automatically close stories for commenting after that time period.
|
||||
|
||||
* If you're using the Toxic Comments plugin, make sure that its threshold is set at the level that catches most comments with fewest false positives (default is 80%). You can see the Likely to be Toxic level of every comment by clicking "More Details" on the comment card in the moderation view.
|
||||
|
||||
* Publicly discourage behavior in the comments that doesn't cross the line but suggests that the tone or focus could shift quickly in a direction you don't want. Point to relevant sections of your community guidelines. [Read more about defining and discouraging this kind of behavior here.](https://guides.coralproject.net/manage-a-successful-community/)
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Migrating to v5
|
||||
permalink: /migration/5.0/
|
||||
---
|
||||
|
||||
## **⏰It is currently not possible to upgrade directly from 4.x.x versions to version 5.x** ⏰
|
||||
|
||||
**Trying will break your install of Coral!**
|
||||
|
||||
Coral version 5.0.0 is more than just a version bump, it includes a fundamental rewrite of underlying core components, changes to the database schema, and the introduction of Typescript strong data types. This means migrating is a bit more complicated than running a simple database migration.
|
||||
|
||||
### Migration Path
|
||||
|
||||
A step by step migration path is currently in development, and we will be announcing it here once it’s ready.
|
||||
|
||||
The process for migration will include:
|
||||
|
||||
- Create a mongodump export of your 4.x.x database
|
||||
- Use an import tool to transform the data for v5, and import it to a new mongo database
|
||||
- Deploy & configure v5 on your production infrastructure (pointing to new DB)
|
||||
- Update your embed script to the new version
|
||||
|
||||
Downtime will be required to complete the migration, and you should carefully plan your migration accordingly depending on your infrastructure setup, and amount of data to be migrated.
|
||||
|
||||
### Migrating Plugins
|
||||
|
||||
Plugins are no longer supported and will not work with any instance of Coral >= v5. Most of the optional `talk-plugin` features have been built right into core so you no longer need install, manage, and worry about plugins. If you were using plugins to achieve custom functionality, please contact us to learn about some of the new ways to solve custom use cases in v5 and beyond.
|
||||
|
||||
### Supporting your Migration
|
||||
|
||||
We would love to help!
|
||||
|
||||
If you’re currently running any open source version 4.x/5.x and would like
|
||||
assistance with planning and/or executing a migration please reach out to us at
|
||||
support@coralproject.net.
|
||||
|
||||
We can help you figure out the best path forward, and give you early access to
|
||||
the migration tools we’re working on.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: Migrating to v6
|
||||
permalink: /migration/6.0/
|
||||
---
|
||||
|
||||
Coral 6.0.0 introduces new features to assist with large communities using
|
||||
Coral. To migrate from any 5.x.x release, ensure you first upgrade to 5.5.1
|
||||
and allow all migrations to run. This means that you cannot upgrade from any
|
||||
version below 5.5.1 to 6.0.0 directly.
|
||||
|
||||
### Supporting your Migration
|
||||
|
||||
We would love to help!
|
||||
|
||||
If you’re currently running any open source version 4.x/5.x and would like
|
||||
assistance with planning and/or executing a migration please reach out to us at
|
||||
support@coralproject.net.
|
||||
|
||||
We can help you figure out the best path forward, and give you early access to
|
||||
the migration tools we’re working on.
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: FAQ
|
||||
permalink: /faq/
|
||||
---
|
||||
|
||||
## How can I get help, submit a bug, or suggest a feature?
|
||||
|
||||
There are a few avenues to get in touch with us and others in the community for help.
|
||||
|
||||
To log a bug or request a feature, submit a Support ticket ([support@coralproject.net](mailto:support@coralproject.net)) and someone from our team will get back to you.
|
||||
|
||||
You can also request help on Github by [submitting an issue](https://github.com/coralproject/talk/issues). This also increases your chances of having someone from the community respond to help.
|
||||
|
||||
## How can our dev team contribute to Coral?
|
||||
|
||||
We are lucky to work with newsroom dev teams and individual contributors who span the world, and come from newsrooms of all sizes. You can read our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md) to get started, but feel free to reach out to us via Github too.
|
||||
|
||||
## What if we want to add a feature you don't have?
|
||||
|
||||
Coral is open source, so you're free to develop additional functionality and [submit a pull request](https://github.com/coralproject.net/talk).
|
||||
|
||||
## Do you have GDPR features?
|
||||
|
||||
Yes! Please read our GDPR documentation for your version for more information and instructions to get started.
|
||||
* [version 4.x.x](/talk/integrating/gdpr/)
|
||||
* [version 5.x.x](/talk/v5/integrating/gdpr/)
|
||||
|
||||
## Can I import my existing comments?
|
||||
|
||||
Yes! For version 4.x.x we have a community-supported [import framework](https://github.com/coralproject/talk-importer) that you can use to migrate your existing comments.
|
||||
|
||||
Import tools are still in development for version 5, but will be coming soon! Contact us at [support@coralproject.net](mailto:support@coralproject.net) if you need an importer for version 5.x.x.
|
||||
|
||||
## What support is available?
|
||||
|
||||
You can always email us at [support@coralproject.net](mailto:support@coralproject.net), and if we are able to help we will answer your questions. In some cases, we are able to offer paid support packages. You can inquire about this via the support email address above.
|
||||
|
||||
## Is there a hosted version I can purchase by monthly subscription?
|
||||
|
||||
Yes! [Visit our hosting page](https://coralproject.net/pricing/) to submit information and receive a quote.
|
||||
|
||||
## Where is our data when we use Coral?
|
||||
|
||||
If you are hosting Coral on your own:
|
||||
|
||||
* Your data is stored in a MongoDB database that you provide
|
||||
* The Coral team doesn’t have any access to your data
|
||||
|
||||
If you are using our Hosted SaaS version of Coral:
|
||||
|
||||
* Your data is stored in a MongoDB cluster that is provisioned for your instance
|
||||
* Your data is logically isolated from other customers' data
|
||||
* The Coral Team and its third-party database hosting providers use strict access controls and auditing to protected your data from unauthorized access
|
||||
|
||||
## Does Coral have any automated moderation features to protect against spam and trolling?
|
||||
|
||||
As well as basic word/phrase filtering and user recent history scores, Coral offers optional advanced features via third-party services:
|
||||
|
||||
* The [Toxic Comments filter](/talk/v5/administration/#toxic-comment-filter) integrates with the [Perspective API from Google](https://www.perspectiveapi.com/) to detect the likelihood of toxicity of comments in real-time
|
||||
* The [Akismet Spam filter](/talk/v5/administration/#spam-detection-filter) detects and blocks spam comments
|
||||
|
||||
## How much can I customize Coral?
|
||||
|
||||
The CSS of the embedded comment stream can be customized by adding your own CSS via an external stylesheet. See the doc's for your version:
|
||||
* [version 4.x.x](/talk/integrating/styling-css/)
|
||||
* [version 5.x.x](/talk/v5/css/)
|
||||
|
||||
## How much does Coral cost?
|
||||
|
||||
* The Coral software is freely available under the Apache 2.0 open source license
|
||||
* Associated costs are those for the infrastructure required to run Coral (i.e. cloud hosting fee or bare-metal server costs)
|
||||
* The Coral Project offers a SaaS/hosted version of Coral. Please [get in touch with us](https://coralproject.net/pricing/) to discuss pricing for your requirements.
|
||||
+10
-14
@@ -5,11 +5,13 @@ permalink: /troubleshooting-tips/
|
||||
|
||||
## How do I find out what version I'm running?
|
||||
|
||||
If you visit https://<YOUR TALK INSTANCE>/api/v1, it will return the version you're running and the hash for the latest commit.
|
||||
If you visit one of the following endpionts, Coral will return the version you're running and the hash for the latest commit.
|
||||
* `/api/v1` (works for 4.x.x versions)
|
||||
* `/api/version` (works for 5.x.x versions)
|
||||
|
||||
## I've installed Talk but I can't see the comment stream appear on my articles
|
||||
## I've installed Coral but I can't see the comment stream appear on my articles
|
||||
|
||||
* Make sure you've adding the correct domains to your Permitted Domains in Configure > Tech Settings
|
||||
* Make sure you've adding the correct domains to your Permitted Domains
|
||||
* Make sure you've correctly added the embed via your CMS to your article pages
|
||||
* Check the console for any errors and you can file a bug via [support](mailto:support@coralproject.net) if you can't resolve the issue
|
||||
|
||||
@@ -21,23 +23,17 @@ If you visit https://<YOUR TALK INSTANCE>/api/v1, it will return the version you
|
||||
* You could also try asking them to clear their browser cache
|
||||
|
||||
|
||||
## My commenters are reporting that they cannot login to Talk
|
||||
## My commenters are reporting that they cannot login to Coral
|
||||
|
||||
If you're using your own custom auth plugin:
|
||||
This depends greatly on what version of Coral you are using and which authentication strategies you've enabled. Some things to check are:
|
||||
|
||||
* Review the code and your server logs to ensure your plugin is working correctly. Check [our auth docs](/talk/integrating/authentication/) for more tips.
|
||||
* Ensure that your JWT token settings, especially expiry, is being set correctly. You can troubleshoot JWT related issues with the [JWT Debugger](https://jwt.io/).
|
||||
* If using 4.x.x, review your `plugins.json` and server logs to ensure your auth plugins are enabled and working correctly. Check [our auth docs](/talk/integrating/authentication/) for more tips.
|
||||
* If using SSO, ensure that your JWT token settings, especially expiry, is being set correctly. You can troubleshoot JWT related issues with the [JWT Debugger](https://jwt.io/).
|
||||
* See if you can isolate if it's a particular group of users that are experiencing this issue, e.g. mods, admins, subscribers? Confirm they have the appropriate permissions to comment.
|
||||
* Note if this is a new issue that happened after an upgrade - did you read the [migration docs](/talk/migration/3/) and the [release notes](https://github.com/coralproject/talk/releases)? This might help you resolve the issue
|
||||
* Note if this is a new issue that happened after an upgrade - did you read the [release notes](https://github.com/coralproject/talk/releases) and confirm that any required manual or automatic database migrations run?
|
||||
* Confirm that users who are affected have the correct `username.status`. If users have status `UNSET`, this is related to a bug with upgrading from 3.x to 4.x that has affected some organizations. Read more here about [upgrading from 3.x to 4.x](/talk/migration/3/).
|
||||
* If you're still experiencing issues, log a [support ticket](mailto:support@coralproject.net) so we can help diagnose the issue
|
||||
|
||||
|
||||
If you're using `talk-plugin-auth`:
|
||||
|
||||
* See if you can isolate if it's a particular group of users that are experiencing this issue, e.g. mods, admins, subscribers? Confirm they have the appropriate permissions to comment.
|
||||
* Note if this is a new issue that happened after an upgrade - did you read the [migration docs](/talk/migration/3/) and the [release notes](https://github.com/coralproject/talk/releases)? This might help you resolve the issue.
|
||||
* If you're still experiencing issues, log a [support ticket](mailto:support@coralproject.net) so we can help diagnose the issue
|
||||
|
||||
If a user has been locked out due to too many failed login attempts:
|
||||
* How long does the user have to wait before they will be allowed to login? 10 mins
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Accelerated Mobile Page
|
||||
permalink: /integrating/amp/
|
||||
---
|
||||
|
||||
[AMP](https://amp.dev/) is a light-weight, stripped down HTML page that aims to improve reader experience. _Talk v4.9.0+_ comes with [AMP](https://amp.dev/) support. The current caveat however is that _toast notifications_ are not being rendered when viewing inside AMP.
|
||||
|
||||
# How to integrate
|
||||
Put the following code into your _AMP_ page and replace `$TALK_URL` and `$ASSET_URL` with the
|
||||
corresponding values. You can also pass `asset_id` instead of `asset_url`.
|
||||
|
||||
```html
|
||||
<amp-iframe
|
||||
width=600 height=140
|
||||
layout="responsive"
|
||||
sandbox="allow-scripts allow-same-origin allow-modals allow-popups allow-forms"
|
||||
resizable
|
||||
src="https://$TALK_URL/embed/amp#asset_url=$ASSET_URL">
|
||||
<div placeholder></div>
|
||||
<div overflow tabindex=0 role=button aria-label="Read more">Read more</div>
|
||||
</amp-iframe>
|
||||
```
|
||||
|
||||
## Single Sign-On
|
||||
For SSO integration you need to create a page with the following output and replace `$TALK_URL` and `$AUTH_TOKEN` with the appropriate values. Inject your SSO auth scripts to get the `$AUTH_TOKEN` for the current user. Integrating with [amp-access](https://amp.dev/documentation/components/amp-access) is recommended which opens a 1st-party popup to not have browsers block your cookies. This page is then used in `src` of `<amp-iframe>` above. It must be accessed over `https` and live in a different domain than the `amp` page.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<title>Coral Talk Amp Embed</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id='coralStreamEmbed'></div>
|
||||
<script src="https://$TALK_URL/static/embed.js"></script>
|
||||
<script>
|
||||
window.TalkEmbed = Coral.Talk.render(document.getElementById('coralStreamEmbed'), {
|
||||
talk: '$TALK_URL',
|
||||
auth_token: '$AUTH_TOKEN',
|
||||
amp: true,
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
+9
-2
@@ -9,6 +9,9 @@ in a simple way. We use the following
|
||||
[meta tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta) on
|
||||
the target pages that allow us to extract some properties.
|
||||
|
||||
Asset scraping is performed by the `scraper` job which is enabled by default when you launch Talk. If your production site is behind a paywall or otherwise prevents scraping, you might need to confiugre a [TALK_SCRAPER_PROXY_URL](/talk/advanced-configuration/#talk-scraper-proxy-url) or custom [TALK_SCRAPER_HEADERS](/talk/advanced-configuration/#talk-scraper-headers).
|
||||
|
||||
|
||||
| Asset Property | Selector |
|
||||
|--------------------|----------|
|
||||
| `title` | See [`metascraper-title`](https://github.com/microlinkhq/metascraper/blob/dc664c37ea1b238b1e3e9d5342edfacc9027892c/packages/metascraper-title/index.js) |
|
||||
@@ -19,7 +22,7 @@ the target pages that allow us to extract some properties.
|
||||
| `modified_date` | `meta[property="article:modified"]` |
|
||||
| `section` | `meta[property="article:section"]` |
|
||||
|
||||
You can use the `./bin/cli assets debug <url>` to print the scraped metadata
|
||||
You can use the `./bin/cli assets debug <url>` command to print the scraped metadata
|
||||
from that URL. For example:
|
||||
|
||||
```bash
|
||||
@@ -41,4 +44,8 @@ from that URL. For example:
|
||||
├──────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ section │ │
|
||||
└──────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
|
||||
You can use the `./bin/cli assets refresh [age]` to trigger scraping or rescrape assets where the scraper job was unsuccessful.
|
||||
+120
-52
@@ -4,47 +4,87 @@ 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>`:
|
||||
```
|
||||
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}`:
|
||||
|
||||
```html
|
||||
<div id="coral_talk_stream"></div>
|
||||
<script src="<TALK_ROOT_URL>/static/embed.js" async onload="
|
||||
<script
|
||||
src="${TALK_ROOT_URL}/static/embed.js"
|
||||
async
|
||||
onload="
|
||||
Coral.Talk.render(document.getElementById('coral_talk_stream'), {
|
||||
talk: '<TALK_ROOT_URL>'
|
||||
talk: '${TALK_ROOT_URL}'
|
||||
});
|
||||
"></script>
|
||||
"
|
||||
></script>
|
||||
```
|
||||
|
||||
The URL for the asset is first inferred from the _Canonical link element_, which
|
||||
takes the form of a `<link>` element in your `<head>` of the page:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="canonical" href="https://example.com/page" />
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
If this tag is not present, you can also pass a `asset_url` parameter into the
|
||||
render function as:
|
||||
|
||||
```js
|
||||
Coral.Talk.render(document.getElementById("coral_talk_stream"), {
|
||||
talk: "${TALK_ROOT_URL}",
|
||||
asset_url: "https://example.com/page"
|
||||
});
|
||||
```
|
||||
|
||||
Which will explicitly force Talk to reference a particular url. This is
|
||||
recommended if your canonical url does not match the current url.
|
||||
|
||||
> **NOTE:** If the canonical link tag or `asset_url` parameter are not present, Talk will use the current URL excluding the query and hash elements. This may lead to undesired behavior, and it is recommended to use one of the above methods of specifying the URL.
|
||||
|
||||
## 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 user’s session for SSO/authentication.
|
||||
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 user’s 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 you’re running Talk on a heavy-traffic production site.
|
||||
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 you’re 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.
|
||||
|
||||
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,
|
||||
|
||||
```js
|
||||
Coral.Talk.render(document.getElementById("container"), {
|
||||
talk: "${TALK_ROOT_URL}",
|
||||
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_
|
||||
_\*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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
```html
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
|
||||
|
||||
<div id="coral_talk_stream">
|
||||
@@ -52,10 +92,10 @@ This example uses jQuery to render the embed on the button's click event
|
||||
</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>',
|
||||
$("#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}"
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -63,17 +103,20 @@ This example uses jQuery to render the embed on the button's click event
|
||||
```
|
||||
|
||||
## 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?_
|
||||
|
||||
### “Lazy Asset Creation”
|
||||
Talk’s 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.
|
||||
|
||||
Talk’s 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.
|
||||
|
||||
Assets created in this way will then be scraped to load metadata, see [Asset Scraping](/talk/integrating/asset-scraping/)
|
||||
|
||||
## 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.
|
||||
|
||||
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:
|
||||
|
||||
@@ -83,7 +126,6 @@ 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:
|
||||
@@ -129,8 +171,8 @@ module.exports = {
|
||||
|
||||
// Send the asset back.
|
||||
return asset;
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@@ -158,14 +200,14 @@ We'll replace the contents of the `router.js` file with the following:
|
||||
// This file we'll create routes that will facilitate asset creation and
|
||||
// updates.
|
||||
|
||||
const authz = require('middleware/authorization');
|
||||
const authz = require("middleware/authorization");
|
||||
|
||||
module.exports = router => {
|
||||
// We'll respond to a POST request on the following route where the request
|
||||
// must have a valid ADMIN access token.
|
||||
router.post(
|
||||
'/api/v1/plugin/asset-manager-example',
|
||||
authz.needed('ADMIN'),
|
||||
"/api/v1/plugin/asset-manager-example",
|
||||
authz.needed("ADMIN"),
|
||||
async (req, res, next) => {
|
||||
// Get the graph context from the request.
|
||||
const { context } = req;
|
||||
@@ -173,7 +215,11 @@ module.exports = router => {
|
||||
// Grab from the graph context, the AssetModel that we can use to create
|
||||
// the new Asset. Lots of object destructuring here, but this lets us keep
|
||||
// the important business logic cleaner.
|
||||
const { connectors: { models: { Assets } } } = context;
|
||||
const {
|
||||
connectors: {
|
||||
models: { Assets }
|
||||
}
|
||||
} = context;
|
||||
|
||||
try {
|
||||
// Now we can create the asset that was passed to us in the body of the
|
||||
@@ -208,20 +254,24 @@ CMS using the Talk cli tool:
|
||||
|
||||
You can attach the generated token to the request a few ways:
|
||||
|
||||
1. HTTP Header:
|
||||
1. HTTP Header:
|
||||
|
||||
curl ${TALK_ROOT_URL}/api/v1/plugin/asset-manager-example \
|
||||
-XPOST \
|
||||
-H "Authorization: Bearer ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${ASSET_JSON}"
|
||||
```sh
|
||||
curl ${TALK_ROOT_URL}/api/v1/plugin/asset-manager-example \
|
||||
-XPOST \
|
||||
-H "Authorization: Bearer ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${ASSET_JSON}"
|
||||
```
|
||||
|
||||
2. Query Parameter:
|
||||
2. Query Parameter:
|
||||
|
||||
curl ${TALK_ROOT_URL}/api/v1/plugin/asset-manager-example?access_token=${TOKEN}
|
||||
-XPOST \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${ASSET_JSON}"
|
||||
```sh
|
||||
curl ${TALK_ROOT_URL}/api/v1/plugin/asset-manager-example?access_token=${TOKEN}
|
||||
-XPOST \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${ASSET_JSON}"
|
||||
```
|
||||
|
||||
Where `${ASSET_JSON}` is the JSON for your Asset matching the
|
||||
[AssetSchema](https://github.com/coralproject/talk/blob/master/models/asset.js).
|
||||
@@ -239,14 +289,14 @@ Update your `router.js` to the following:
|
||||
// This file we'll create routes that will facilitate asset creation and
|
||||
// updates.
|
||||
|
||||
const authz = require('middleware/authorization');
|
||||
const authz = require("middleware/authorization");
|
||||
|
||||
module.exports = router => {
|
||||
// We'll respond to a POST request on the following route where the request
|
||||
// must have a valid ADMIN access token.
|
||||
router.post(
|
||||
'/api/v1/plugin/asset-manager-example',
|
||||
authz.needed('ADMIN'),
|
||||
"/api/v1/plugin/asset-manager-example",
|
||||
authz.needed("ADMIN"),
|
||||
async (req, res, next) => {
|
||||
// Get the graph context from the request.
|
||||
const { context } = req;
|
||||
@@ -254,7 +304,11 @@ module.exports = router => {
|
||||
// Grab from the graph context, the AssetModel that we can use to create
|
||||
// the new Asset. Lots of object destructuring here, but this lets us keep
|
||||
// the important business logic cleaner.
|
||||
const { connectors: { models: { Assets } } } = context;
|
||||
const {
|
||||
connectors: {
|
||||
models: { Assets }
|
||||
}
|
||||
} = context;
|
||||
|
||||
try {
|
||||
// Now we can create the asset that was passed to us in the body of the
|
||||
@@ -273,8 +327,8 @@ module.exports = router => {
|
||||
// We'll respond to a PUT request on the following route where the request
|
||||
// must also have a valid ADMIN access token.
|
||||
router.put(
|
||||
'/api/v1/plugin/asset-manager-example/:id',
|
||||
authz.needed('ADMIN'),
|
||||
"/api/v1/plugin/asset-manager-example/:id",
|
||||
authz.needed("ADMIN"),
|
||||
async (req, res, next) => {
|
||||
// Get the graph context from the request.
|
||||
const { context } = req;
|
||||
@@ -282,7 +336,11 @@ module.exports = router => {
|
||||
// Grab from the graph context, the AssetModel that we can use to update
|
||||
// the Asset. Lots of object destructuring here, but this lets us keep
|
||||
// the important business logic cleaner.
|
||||
const { connectors: { models: { Assets } } } = context;
|
||||
const {
|
||||
connectors: {
|
||||
models: { Assets }
|
||||
}
|
||||
} = context;
|
||||
|
||||
try {
|
||||
// Now we can lookup the asset we're updating and apply out updates to
|
||||
@@ -292,7 +350,7 @@ module.exports = router => {
|
||||
req.body,
|
||||
{
|
||||
// We want to validate the model being updated.
|
||||
runValidators: true,
|
||||
runValidators: true
|
||||
}
|
||||
);
|
||||
if (!asset) {
|
||||
@@ -343,23 +401,31 @@ When you install Talk, and visit the admin panel, we can see under
|
||||
|
||||
```html
|
||||
<div id="coral_talk_stream"></div>
|
||||
<script src="${TALK_ROOT_URL}static/embed.js" async onload="
|
||||
<script
|
||||
src="${TALK_ROOT_URL}static/embed.js"
|
||||
async
|
||||
onload="
|
||||
Coral.Talk.render(document.getElementById('coral_talk_stream'), {
|
||||
talk: '${TALK_ROOT_URL}'
|
||||
});
|
||||
"></script>
|
||||
"
|
||||
></script>
|
||||
```
|
||||
|
||||
We'll modify this to the following:
|
||||
|
||||
```html
|
||||
<div id="coral_talk_stream"></div>
|
||||
<script src="${TALK_ROOT_URL}static/embed.js" async onload="
|
||||
<script
|
||||
src="${TALK_ROOT_URL}static/embed.js"
|
||||
async
|
||||
onload="
|
||||
Coral.Talk.render(document.getElementById('coral_talk_stream'), {
|
||||
talk: '${TALK_ROOT_URL}',
|
||||
asset_id: '${ASSET_ID}'
|
||||
});
|
||||
"></script>
|
||||
"
|
||||
></script>
|
||||
```
|
||||
|
||||
Adding the `asset_id` parameter to the render function will accomplish a very
|
||||
@@ -369,8 +435,10 @@ the URL in the future, the embed will still reference the correct Asset. The
|
||||
`${ASSET_ID}` should be replaced by your CMS with the correct Asset id using
|
||||
your desired scripting/templating tools.
|
||||
|
||||
> **NOTE:** When used in conjunction with `asset_url`, you can explicitly force Talk to use a specified URL, rather than the canonical for link references. When used together, both `asset_id` and `asset_url` will be treated as unique identifiers for Talk assets.
|
||||
|
||||
At this point, you should have a fully built Talk plugin that can be paired with
|
||||
some work on your CMS to create a fully integrated asset management pipeline!
|
||||
|
||||
To view the fully completed source code, visit
|
||||
https://github.com/coralproject/talk-plugin-asset-manager-example.
|
||||
https://github.com/coralproject/talk-plugin-asset-manager-example.
|
||||
@@ -0,0 +1 @@
|
||||
../../../../plugins/talk-plugin-akismet/README.md
|
||||
@@ -0,0 +1 @@
|
||||
../../../../plugins/talk-plugin-auth/README.md
|
||||
@@ -0,0 +1 @@
|
||||
../../../../plugins/talk-plugin-author-menu/README.md
|
||||
@@ -0,0 +1 @@
|
||||
../../../../plugins/talk-plugin-comment-content/README.md
|
||||
@@ -0,0 +1 @@
|
||||
../../../../plugins/talk-plugin-deep-reply-count/README.md
|
||||
@@ -0,0 +1 @@
|
||||
../../../../plugins/talk-plugin-facebook-auth/README.md
|
||||
@@ -0,0 +1 @@
|
||||
../../../../plugins/talk-plugin-featured-comments/README.md
|
||||
@@ -0,0 +1 @@
|
||||
../../../../plugins/talk-plugin-flag-details/README.md
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user