Compare commits

..
102 Commits
Author SHA1 Message Date
Kim GardnerandWyatt Johnson ff72d79748 Update SSO docs and add GDPR docs (#2701)
* Update SSO docs and add GDPR docs

* Update docs/source/version-5-gdpr.md

Co-Authored-By: Wyatt Johnson <wyattjoh@gmail.com>

* Update docs/source/version-5-gdpr.md

Co-Authored-By: Wyatt Johnson <wyattjoh@gmail.com>

* Update docs/source/version-5-gdpr.md

Co-Authored-By: Wyatt Johnson <wyattjoh@gmail.com>
2019-11-12 14:47:05 -05:00
Max Baumann 0645735d2b Error responses from scraped pages should not return metadata (#2691)
* Don't return error pages as valid scrapes

* Fix linting

* whitespace
2019-11-08 18:57:20 +00:00
Kim Gardner f1a0febd6c Bump version to 4.11.2 (#2699) 2019-11-08 17:48:13 +00:00
Wyatt Johnson 16d0b39ebc fix: added index for query that loads flags for user (#2678) 2019-11-07 13:43:05 -05:00
Wyatt Johnson 66c7430ff1 fix: fixed deploy scripts (#2665) 2019-10-22 14:15:23 -04:00
Kim Gardner ac06f0d13b Bump version 4.11.1 (#2663) 2019-10-22 13:20:35 -04:00
Wyatt Johnson ca10062498 [CORL-646] Docs update (#2648)
* docs: added docs for trust proxy

* fix: adjusted incorrect HTML

- fixes #2656
2019-10-22 17:01:48 +00:00
Wyatt Johnson c9381c6367 fix: addressed link detection issue (#2660) 2019-10-22 10:34:32 -04:00
Andrew Losowsky 13853ef87e Added notes about hosting and improved sell (#2638) 2019-10-15 14:20:28 -04:00
Pepe Franco fb2f36d0ee Update getReactionConfig.js (#2618)
* Update getReactionConfig.js

Delete duplicated code

* fix: linting
2019-10-03 17:10:20 +00:00
Wyatt Johnson 1a2fa73941 [CORL-663] v4 release fixes (#2613)
* fix: removed latest pushing from docker deploy script

* fix: addressed circleci bug
2019-10-01 22:37:42 +00:00
Vinh d3931db285 [CORL-181] Add docs for comment count integration (#2598)
* docs: add docs for comment count integration

* docs: update count docs

* chore: improve integration docs
2019-10-01 19:27:00 +00:00
Wyatt Johnson 093202f9e2 fix: disable browserstack tests (#2610) 2019-10-01 13:49:22 -04:00
Kim Gardner 60be682a5f Update package.json (#2609) 2019-10-01 16:01:34 +00:00
Joe Simon 41c9ae32b1 v4 - Adding live updates for accepted comments (#2584)
* First pass at live updates for accepted comments

* fix: logic cleanup
2019-09-30 23:47:30 +00:00
Wyatt Johnson 4445479237 chore: updated minimum mongo versions (#2592) 2019-09-30 16:15:24 +00:00
Andrew Losowsky aab44587a6 Small changes (#2599)
Name change and small edit
2019-09-30 15:59:03 +00:00
immber 0af28edbd9 fixed broken links, added username restrictions, minor docs updates (#2580) 2019-09-20 19:31:20 +00:00
Wyatt Johnson 9db7571fe2 fix: fixed heroku install button (#2552) 2019-09-10 19:40:41 +00:00
Wyatt Johnson 683adceec4 [CORL-603] Documentation Updates (#2553)
* fix: replaced png with svg

* feat: updated docs

* feat: added netlify config

* fix: fixed netlify config
2019-09-10 19:20:24 +00:00
Wyatt Johnson aa6058e9d1 [CORL-584] Add Version 5 Documentation (#2523)
* feat: initial version

* fix: adjusted height

* fix: tweaked sidebar styles again

* fix: adjusted overflow property

* fix: reverted overflow, cleaned sidebar
2019-09-05 20:53:03 +00:00
Wyatt Johnson a4c4032b0d [CORL-546] Adjusted rejected username copy (#2483)
* fix: fixed copy, removed broken translations

* chore: version bump
2019-08-16 21:15:59 +00:00
Wyatt Johnson 2ea0d51613 chore: bumped version (#2459) 2019-08-09 17:43:22 -04:00
Wyatt Johnson f2564f4eda fix: addressed issues with create flow (#2458) 2019-08-09 21:38:38 +00:00
Kim Gardner 73040b1314 Update rejected username language (#2420) 2019-07-25 15:19:17 -04:00
Wyatt Johnson c3c106c7b0 Update README.md (#2413) 2019-07-19 11:59:31 -04:00
Andrew Losowsky 8faf69ce48 Updated copyright notice from Mozilla to Vox Media (#2412)
* Updated copyright notice from Mozilla to Vox Media

* fix: update package.json
2019-07-19 15:45:49 +00:00
Wyatt Johnson 23f17db0c9 [CORL-447] Stories Fix (#2408)
* fix: fixes close/open bug

* fix: fixed version display

* chore: bumped version
2019-07-17 15:52:58 +01:00
Kim Gardner 1385a0b961 Bump version 4.10.0 (#2406) 2019-07-15 19:53:33 +01:00
Wyatt Johnson 7fd01e5845 [CORL-444] Stories Tab Adjustments (#2404)
* feat: improved stories tab

* feat: swapped date sorting with text sorting
2019-07-12 20:18:21 +00:00
immber 340052cdf0 DOCS | Add asset_url note to CMS integration & Update FB config (#2393)
* added note RE passing asset_url, and updated fb configs

* fix: syntax tweaks

* feat: small tweaks to embedding
2019-07-10 09:41:52 -07:00
Leandro a085e4b6f9 Translation key typo. (#2389)
Fix translation key "login.request_passowrd"
to "login.request_password".
2019-07-03 17:28:34 +01:00
Kim Gardner 1575b15e36 Merge pull request #2374 from leeeandroo/german-translations
Added missing German translations.
2019-06-21 15:32:55 +01:00
Kim Gardner 7da9126a76 Merge branch 'master' into german-translations 2019-06-21 15:24:17 +01:00
Kim Gardner 9453d207f5 Merge pull request #2373 from leeeandroo/featured-tooltip-breakword
Break headline on featured comments plugin
2019-06-21 15:23:18 +01:00
Leandro Vasco da Rocha 869c760a8b Grammatical changes on German wording 2019-06-20 14:44:52 +02:00
Leandro Vasco da Rocha b283595c97 Break headline on featured comments plugin.
If a headline is too long, we need to break it to fit the tooltip
content box.
2019-06-20 13:24:05 +02:00
Leandro Vasco da Rocha d672af63f9 Added missing german translations. 2019-06-18 15:53:27 +02:00
Kim Gardner b4ad78fd65 Bump version 4.9.1 (#2361) 2019-06-14 15:28:26 +00:00
Kim Gardner 713de46c2a Merge pull request #2359 from coralproject/corl-427
[CORL-427] AddEmailAddressDialog should appear at the top
2019-06-14 14:53:46 +01:00
Chi Vinh Le fc1e51ed62 fix: Position Add Email Address Dialog to the top 2019-06-14 00:08:37 +02:00
Etienne Martin 4eef6f4218 Added missing translations (#2357) 2019-06-13 19:06:41 +00:00
Kim Gardner c8cfd7c0f8 Merge pull request #2345 from coralproject/bump-4-9-0
Bump version 4.9.0
2019-06-06 20:11:11 +01:00
Kim Gardner 0e4cbf5f44 Bump version 4.9.0 2019-06-06 19:59:58 +01:00
Kim Gardner 448b988281 Merge pull request #2327 from coralproject/next-amp-docs
AMP docs
2019-06-05 20:55:40 +01:00
Kim Gardner 7a688495f2 Merge branch 'master' into next-amp-docs 2019-05-29 13:29:57 +01:00
Kim Gardner b9cf7084eb Merge pull request #2326 from Sentynel/master
Don't crash on email address lookup failure
2019-05-29 13:29:34 +01:00
Kim Gardner b99161f0cb Merge branch 'master' into next-amp-docs 2019-05-29 12:59:22 +01:00
Kim Gardner 6faa4862de Merge branch 'master' into master 2019-05-29 12:58:57 +01:00
Aram Zucker-Scharff eff3f1dfd8 Small correction of a spelling error (#2325) 2019-05-28 16:36:17 +00:00
immber 73aa77d7da CORL-380 | DOCS | Updates pre-launch checklist, asset scraping (#2317)
* updated asset scraping docs

* added note about setting close date

* added close window setting details
2019-05-27 17:58:18 +02:00
Chi Vinh Le 5580ab385f fix: remove margin from amp body 2019-05-27 17:20:07 +02:00
Chi Vinh Le 40eb0e97ec docs: add amp docs 2019-05-27 17:19:05 +02:00
Sam Lade b7e1ce0205 Don't crash on email address lookup failure
If for whatever reason an email job gets queued for a user with no
email, an exception is thrown on the getEmailAddress() step. This
exception was never handled and therefore the app crashed. Handle it and
log instead..
2019-05-27 14:12:14 +01:00
Kiwi bfde9bf8c7 [CORL-373] Experimental AMP Support (#2324)
* feat: support amp

* fix: support a custom PORT for amp development
2019-05-23 20:42:58 +02:00
Gerardo Gálvez 54f9b28086 Show/hide a comment's edit history from Moderation and User Detail. (#2299)
* Show/Hide comment edit history in moderation queues and user detail.

* Add spanish translations

* Place timestamp and comment body on separate lines

* Remove padding between timestamp and comment
2019-05-15 19:57:21 +02:00
immber 03a5eb7f9b Merge pull request #2307 from coralproject/fbdocs
DOCS | Update fb app creation steps
2019-05-09 15:51:51 -07:00
immber 43e60e6544 Merge branch 'master' into fbdocs 2019-05-09 15:37:24 -07:00
Jessie Rushing ebd16666e6 Merge branch 'fbdocs' of https://github.com/coralproject/talk into fbdocs 2019-05-09 15:36:37 -07:00
Jessie Rushing f2129082a9 added note about location of live button 2019-05-09 15:35:41 -07:00
Kim Gardner 1098c068f2 Merge pull request #2310 from coralproject/bump-version-4-8-7
Bump version 4.8.7
2019-05-09 23:19:10 +01:00
Kim Gardner f0c95d0044 Update package.json 2019-05-09 23:12:14 +01:00
immber 74226aa6fc Merge branch 'master' into fbdocs 2019-05-09 15:08:59 -07:00
Jessie Rushing b6ed5b792b updated Fb dev steps 2019-05-09 15:07:40 -07:00
Kim Gardner d31969592c Remove quotes from Featured Comments (#2309) 2019-05-09 23:50:43 +02:00
Kiwi 794487b1a9 fix: bug where erasing the edit RTE content would respawn the original content (#2308) 2019-05-09 23:22:14 +02:00
Jessie Rushing 39e45c30ba added callback url 2019-05-08 16:05:13 -07:00
Jessie Rushing ed734ebb08 update fb app creation steps 2019-05-08 15:42:06 -07:00
Kim Gardner 9f059e0c4a Docs typo (#2303) 2019-05-07 21:59:25 +00:00
Kim Gardner 7d369f2d35 Merge pull request #2279 from type-face/admin-ui-custom-css
apply custom CSS to admin panel
2019-05-07 19:40:18 +01:00
type_face 5fb5240f89 Merge branch 'master' into admin-ui-custom-css 2019-05-02 12:28:59 -07:00
Wyatt Johnson 976931135c fix: added username config (#2295) 2019-05-02 16:07:42 +00:00
Wyatt Johnson 1e3d47eb87 Tags Permissions (#2291)
* fix: fixed a bug that exposed tags

* chore: bumped versions
2019-04-29 22:28:49 +02:00
Kim Gardner 2d804d783b Merge pull request #2283 from coralproject/docs
Removed Old Roadmap
2019-04-26 14:48:49 +01:00
type_face 3404578ecc Merge branch 'master' into admin-ui-custom-css 2019-04-24 15:54:12 -07:00
Rob Ring 48c5355a5b apply custom CSS to admin panel 2019-04-24 15:50:29 -07:00
Wyatt Johnson e3a300c643 fix: removed roadmap from sidebar 2019-04-24 13:00:58 -06:00
Andrew Losowsky a6653152e4 added info box (#2281)
* added info box

* fix: styles
2019-04-23 22:08:48 +00:00
Kim Gardner 441422542c Fix broken links and update info (#2277) 2019-04-22 23:41:34 +00:00
Kim Gardner 1d1822c086 Bump version 4.8.4 (#2266) 2019-04-17 16:54:58 +00:00
Kim Gardner 1c2ff7d2ae Update default plugins and docs (#2265)
* Update default plugins

* Update plugin docs with missing plugin

* Fixed broken link in docs

* Update Code of Conduct in docs

* Fix mixup that both plugins are moderation-actions

* fix: adapt server tests to use the rich text plugin

* fix: e2e

* fix: sync docs
2019-04-16 20:54:44 +02:00
Kim Gardner 9319bb4e1f Remove unnecessary Talk branding (#2271)
* Remove Talk copy from Login view

* Remove favicon from base template and only use it for admin view
2019-04-15 11:06:09 +02:00
Kim Gardner 6611f8b636 Fix email copy typo (#2267) 2019-04-11 17:50:10 +02:00
cpoonolly 738542ab99 Cherry seeing if he can fix stuff! (#2254) 2019-04-11 17:42:17 +02:00
Kim Gardner cd4ae47958 [Docs] Initial list of events you can track from Talk (#2262)
* Initial list of events you can track from Talk

* Remove duplicates

* Remove more duplicates
2019-04-08 19:01:42 +02:00
Wyatt Johnson 2ec66007d9 feat: added docs for featured comments tab (#2259) 2019-04-05 21:42:17 +00:00
immber ea34c936d0 DOCS || CORL-290 || Add Custom SSO Plugin Recipe (#2249)
* updated auth strategies

* added custom auth plugin recipe

* fixed format on TALK_SMTP_FROM_ADDRESS

* cleaned up formatting a bit

* added CDN to checklist

* Update docs/source/01-05-pre-launch-checklist.md

Co-Authored-By: immber <jessiekrushing@gmail.com>

* Update docs/source/integrating/authentication.md

Co-Authored-By: immber <jessiekrushing@gmail.com>

* added links to plugins
2019-04-01 22:23:22 +00:00
Mendel Konikov ef1fd9c159 Clarify that Reliable commenter is also not yet in use (#2253) 2019-04-01 21:14:04 +00:00
Andrew Losowsky c7ccf03132 Updated Vox Media Logo on Documentation (#2244)
* added Vox Media logo

* fix: added logo

* fix: cleanup
2019-03-27 22:34:58 -06:00
Andrew Losowsky 8015098305 Changed 'Mozilla' to 'Vox Media' in the sidebar (#2245)
* Changed 'Mozilla' to 'Vox Media' in the sidebar

* Delete moz-logo-bw-rgb.svg
2019-03-27 22:43:12 +00:00
Kim Gardner c85a2703f9 Merge pull request #2241 from coralproject/CORL-290
DOCS | CORL-290 | Documentation updates
2019-03-26 13:24:06 +00:00
Jessie Rushing 9aec6b026c updated event tracking page 2019-03-22 13:25:40 -07:00
immber b2ca9dba40 Merge pull request #2227 from coralproject/CORL-308
update tokenUserNotFound docs
2019-03-20 11:43:16 -07:00
immber 9186c73fff Merge branch 'master' into CORL-308 2019-03-20 11:07:20 -07:00
immber 86d55fa2de Merge pull request #2232 from GlynnPhillips/slack-doc-update
Update the slack config name in the docs
2019-03-20 11:02:56 -07:00
Wyatt Johnson 12c4cfbbf6 feat: improved server api docs 2019-03-19 16:24:32 -06:00
Jessie Rushing c875a8d9ed added note about subdomain whitelisting 2019-03-19 16:24:32 -06:00
Jessie Rushing 4d082165aa update tokenUserNotFound docs 2019-03-19 16:24:31 -06:00
Glynn Phillips 08c01981ac Update the slack config name in the docs
The documentation for the slack notification plugin referenced the wrong
environment variable name.
2019-03-19 14:16:07 +00:00
Wyatt Johnson cd01aae76c fix: fixed incorrect users lookup (#2229) 2019-03-18 12:06:29 -06:00
Andrew Losowsky 53367eeaac Updated text to include our pricing page (#2221)
A few updates plus corrections.
2019-03-11 22:58:50 +00:00
Wyatt Johnson 32962aa1e8 External User Fix (#2220)
* fix: fixed bug with user upserting

* chore: version bump
2019-03-11 19:35:22 +00:00
145 changed files with 2985 additions and 978 deletions
+25 -27
View File
@@ -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.
@@ -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
+5 -4
View File
@@ -1,6 +1,7 @@
# Mozilla Community Participation Guidelines
# Vox Media Code of Conduct
## Version 2.3 - Updated May 16, 2017
## Introduction
The heart of Mozilla is people. We put people first and do our best to recognize, appreciate and respect the diversity of our global contributors. The Mozilla Project welcomes contributions from everyone who shares our goals and wants to contribute in a healthy and constructive manner within our community. As such, we have adopted this code of conduct and require all those who participate to agree and adhere to these Community Participation Guidelines in order to help us create a safe and positive community experience for all.
https://www.mozilla.org/en-US/about/governance/policies/participation/
This code of conduct governs the environment of the Vox Product team. We created it not because we anticipate bad behavior, but because we believe that articulating our values and obligations to one another reinforces the already exceptional level of respect among the team and because having a code provides us with clear avenues to correct our culture should it ever stray from that course. We make this code public in the hopes of contributing to the ongoing conversation about inclusion in the tech, design, and media communities and encourage other teams to fork it and make it their own. To our team, we commit to enforce and evolve this code as our team grows.
Read the rest here: http://code-of-conduct.voxmedia.com/
+3 -23
View File
@@ -18,7 +18,7 @@ There are at least three ways to contribute to Talk:
Conversation surrounding contributions begins in [issues](https://github.com/coralproject/talk/issues).
### When should I Create an Issue?
### When should I create an issue?
File an issue as soon as you have an idea of something you'd like to contribute. We would love to hear what you're thinking and help refine the idea to make it into the Talk ecosystem.
@@ -30,9 +30,9 @@ Please file issues if:
### What should I include?
Coral has adopted an iterative, agile development philosophy. All contributions that make it into the Talk repository should start with a story or this form:
Coral has adopted an iterative, agile development philosophy. All contributions that make it into the Talk repository should start with a user story in this form:
> As a [type of person] I'd like to be able to [do something] so that I can [get some result].
> As a [type of Coral user] I'd like to [do something] so that I can [get some result/value].
This exercise does two things:
@@ -71,23 +71,3 @@ If you'd like to discuss a contribution, please [file an issue](https://github.c
Talk's translations are stored in `.yml` files [here](https://github.com/coralproject/talk/tree/master/locales).
Translations can be submitted via pull request. If you do not use github, you can use 'en.yml' as a template and [email](https://coralproject.net/contact) the translations to us. We can import it into the repository.
## I want to contribute but I'm not sure what to do!
If you want to contribute but don't have a clear idea of exactly what that may be, here are some resources that may help:
### Product Roadmap
Please visit our product roadmap here: https://www.pivotaltracker.com/n/projects/1863625. If you'd like to take on any of our scheduled tasks we'd be forever grateful!
### Discussion Forum
If you'd like to discuss what we're up to, please visit or [community](https://community.coralproject.net/) or [contact us](https://coralproject.net/contact).
### Integrations
Have a favorite analytics engine? Data science service? CMS? Auth platform? Deployment platform or pipeline? Pet project? Consider building a plugin to integrate them!
### Favorite Features?
Do you have a favorite feature of an existing platform that's not yet been done in Talk? Sounds like Talk needs that feature.
+1 -1
View File
@@ -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.
+7 -1
View File
@@ -1,8 +1,14 @@
# Talk &middot; [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk) &middot; [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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/a/go-deeper/about).
Built with <3 by The Coral Project, a part of [Vox Media](https://www.voxmedia.com/).
## Getting Started
+29 -15
View File
@@ -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 && (
+4 -3
View File
@@ -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;
+1 -1
View File
@@ -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 {
&nbsp;<span className={styles.editedMarker}>
({t('comment.edited')})
</span>
&nbsp;<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 -2
View File
@@ -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)}
+53 -31
View File
@@ -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,
@@ -140,8 +140,11 @@ const LOAD_MORE_QUERY = gql`
$limit: Int
$cursor: Cursor
$value: String
$state: UserStateInput
) {
users(query: { value: $value, limit: $limit, cursor: $cursor }) {
users(
query: { value: $value, limit: $limit, cursor: $cursor, state: $state }
) {
hasNextPage
endCursor
nodes {
@@ -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 {
&nbsp;<span className={styles.editedMarker}>
({t('comment.edited')})
</span>
&nbsp;<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')}
+41 -6
View File
@@ -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();
+1
View File
@@ -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) => {
+6
View File
@@ -159,6 +159,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 +198,7 @@ export async function createContext({
plugins,
eventEmitter,
rest,
rest2,
graphql,
notification,
localStorage,
+26
View File
@@ -43,6 +43,32 @@ const CONFIG = {
process.env.TALK_WHITELISTED_LANGUAGES &&
process.env.TALK_WHITELISTED_LANGUAGES.split(',').map(l => l.trim()),
// USERNAME_CAST_REGEXP defiles the regex expression that will be used to
// strip characters from a username during a username cast operation.
USERNAME_CAST_REGEXP: new RegExp(
process.env.USERNAME_CAST_REGEXP || '[^a-zA-Z_]',
'g'
),
// USERNAME_REPLACEMENT_CAST_REGEXP defiles the regex expression that will be
// used to replace characters with the replacement character during a username
// cast operation. First duplicates will be replaced, then
USERNAME_REPLACEMENT_CAST_REGEXP: new RegExp(
process.env.USERNAME_REPLACEMENT_CAST_REGEXP || ' +',
'g'
),
// USERNAME_REPLACEMENT_CHARACTER is the character used to replace other
// characters matching the USERNAME_REPLACEMENT_CAST_REGEXP.
USERNAME_REPLACEMENT_CHARACTER:
process.env.USERNAME_REPLACEMENT_CHARACTER || '_',
// USERNAME_VALIDATION_REGEX defines the allowed characters for a username in
// Talk.
USERNAME_VALIDATION_REGEX: new RegExp(
process.env.USERNAME_VALIDATION_REGEX || '^[A-Za-z0-9_]+$'
),
// When TRUE, it ensures that database indexes created in core will not add
// indexes.
CREATE_MONGO_INDEXES: process.env.DISABLE_CREATE_MONGO_INDEXES !== 'TRUE',
+133 -107
View File
@@ -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
@@ -76,114 +76,140 @@ sidebar:
- title: GitHub
url: https://github.com/coralproject/
- title: Docker
url: https://hub.docker.com/r/coralproject/
- title: Roadmap
url: https://www.pivotaltracker.com/n/projects/1863625
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: Developing
url: /v5/developing/
- title: Integration
children:
- title: CMS Integration
url: /v5/integrating/cms/
- title: Single Sign On
url: /v5/integrating/sso/
- title: GDPR
url: /v5/integrating/gdpr/
- title: Comment Count
url: /v5/integrating/counts/
- 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: 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: FAQ & Troubleshooting
children:
- title: FAQ
url: /faq/
- title: Troubleshooting Tips
url: /troubleshooting-tips/
- title: Contact
url: /contact/
- 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: FAQ & Troubleshooting
children:
- title: FAQ
url: /faq/
- title: Troubleshooting Tips
url: /troubleshooting-tips/
- title: Contact
url: /contact/
marked:
gfm: true
-4
View File
@@ -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/
+29 -25
View File
@@ -1,13 +1,22 @@
---
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. Read more about our product features and
goals [here](https://coralproject.net/talk). The
documentation available here is pertaining to the technical details for
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.
[Read more about our product features and
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/)
</div>
The documentation available here is pertaining to the technical details for
installing, configuring, and deploying Talk.
Talk is a [Node](https://nodejs.org/) application with
@@ -22,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
@@ -35,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:
@@ -43,10 +52,10 @@ 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.5
image: coralproject/talk:4
restart: always
ports:
- "3000:3000"
@@ -80,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
@@ -112,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.
@@ -148,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._
@@ -179,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
@@ -212,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.
@@ -226,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!
@@ -43,7 +43,7 @@ be used to setup Talk:
version: '2'
services:
talk:
image: coralproject/talk:4.5
image: coralproject/talk:4
restart: always
ports:
- "3000:3000"
@@ -121,7 +121,7 @@ base installation with additional custom plugins. Images can be created with the
most basic of `Dockerfile`'s:
```docker
FROM coralproject/talk:4.5-onbuild
FROM coralproject/talk:4-onbuild
```
And running the following to build the docker image:
@@ -153,7 +153,7 @@ your containerized infrastructure. The versioning of our Docker tags as well
lets you do something like:
```docker
FROM coralproject/talk:4.5-onbuild
FROM coralproject/talk:4-onbuild
```
Which would pin your image to `4.5.x release's.
Which would pin your image to `4.x.x release's.
+7 -1
View File
@@ -13,6 +13,9 @@ permalink: /pre-launch-checklist/
- [ ] Do you have a domain name for Talk?
- **Recommended**: You should host Talk on a subdomain on your main site (e.g. if your site is mysitefornews.org, you should serve Talk from a subdomain like talk.mysitefornews.org) to avoid issues with third-party cookie sharing.
- [ ] Do you have a Content Delivery Network (CDN) to improve performance and reduce load on your Talk servers?
- [ ] Do you have a MongoDB instance?
- A MongoDB [Docker instance](/talk/installation-from-docker/) hosted alongside Talk?
@@ -28,9 +31,11 @@ permalink: /pre-launch-checklist/
- [ ] Do you want to provide single sign-on (SSO) by integrating with an external auth system?
- See [Authenticating with Talk](/talk/integrating/authentication/)
- [ ] Do you want to integrate Talk with your CMS to automate embedding Talk Comment Stream into your site?
- See [CMS Integration](/talk/integrating/cms-integration/)
- [ ] Do you want to use Social sign-on?
- Facebook
- Install [talk-plugin-facebook-auth](/talk/plugin/talk-plugin-facebook-auth/)
@@ -72,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 Talks 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/)
+12 -5
View File
@@ -416,13 +416,19 @@ cannot. You would use this option in the latter situation.
## TALK_SMTP_FROM_ADDRESS
The email address to send emails from using the SMTP provider in the format:
The email address to send emails from using the SMTP provider. You can include the name and email address or only provide the email.
```plain
TALK_SMTP_FROM_ADDRESS="The Coral Project" <support@coralproject.net>
TALK_SMTP_FROM_ADDRESS="The Coral Project" support@coralproject.net
```
or
```plain
TALK_SMTP_FROM_ADDRESS=support@coralproject.net
```
Including the name and email address.
## TALK_SMTP_HOST
@@ -499,8 +505,9 @@ Could be read as:
pre-moderated once in order to post freely again. If they instead get rejected
again, then they must have two of their comments approved in order to get
added back to the queue.
- At the moment of writing, behavior is not attached to the flagging
reliability, but it is recorded.
- At the moment of writing, behavior is not attached to reliable commenters
as well as the flagging reliability and unreliability. Regardless,
all of these scores are recorded for future implementation.
## TALK_DISABLE_IGNORE_FLAGS_AGAINST_STAFF
+5
View File
@@ -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:
```
@@ -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
@@ -88,8 +88,9 @@ Comments with suspect words will display until a moderator takes action on them.
#### Permitted Domains
A list of domains where your Talk instance is allowed to be embedded. Typical
use is `localhost`, `staging.yourdomain.com`, `yourdomain.com`, etc.
A list of domains where your Talk instance is allowed to be embedded. List each domain and subdomain in use
separately, for example: `localhost`, `staging.yourdomain.com`, `yourdomain.com`, `www.yourdomain.com`, etc.
#### Embed Script
+6
View File
@@ -47,3 +47,9 @@ Source: [talk-recipes/tree/master/plugins/author-name](https://github.com/coralp
Enables the ability to hover over a commenters name and add plugin
functionality there. The Member Since plugin that is provided in this recipe is
an example of this.
## recipe-custom-auth
Source: [talk-recipes/tree/master/plugins/custom-auth](https://github.com/coralproject/talk-recipes/tree/master/plugins/custom-auth)
Example code for creating, and updating Users in Talk from JWT data provided by your custom SSO authentication service.
@@ -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/)
+8 -8
View File
@@ -33,24 +33,24 @@ Our team is small, so it's difficult for us to provide support packages. However
## Is there a hosted version I can purchase by monthly subscription?
Yes! We are happy to announce that as of July 2018, we provide a SaaS version of Talk, called the Coral Cloud. For a monthly subscription, you get your own hosted Talk instance to embed on your news and blog articles. [Reach out to us](mailto:support@coralproject.net) if you're interested in this option.
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 doesnt have any access to your data
* The Coral team doesnt have any access to your data
If you are using Coral Cloud Hosting for Talk:
If you are using our Hosted SaaS version of Talk:
* Your data is stored in a dedicated MongDB database that is provisioned for your in the Cloud
* 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 by team members
* 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?
Talk features a couple of plugins that provide advanced moderation:
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
@@ -64,4 +64,4 @@ Talk features a couple of plugins that provide advanced moderation:
* 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 version of Talk. Please [get in touch with us](mailto:support@coralproject.net) to discuss pricing for your custom requirements.
* 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.
+245 -168
View File
@@ -56,16 +56,32 @@ Thanks to [gql-merge](https://www.npmjs.com/package/gql-merge) the contents of
definitions. `enum`'s will be appended to, types will be appended, and new types
will be added.
This can be included as a plugin via:
```js
const fs = require("fs");
const path = require("path");
module.exports = {
typeDefs: fs.readFileSync(path.join(__dirname, "typeDefs.graphql"), "utf8")
};
```
Assuming the above GraphQL schema is located in the plugin folder at
`typeDefs.graphql`.
### context
```js
{
Slack: (context) => ({
notify: (message) => {
// return a promise after we're done sending notifications.
}
})
}
module.exports = {
context: {
Slack: context => ({
notify: message => {
// return a promise after we're done sending notifications.
},
});
},
};
```
Any property provided here will be added to the context parameter available
@@ -83,41 +99,50 @@ A special context hook, `Sort` will allow plugin authors to provide new
methods to sort data. An example is as follows:
```js
{
Sort: () => ({
Comments: { // <-- (1)
likes: { // <-- (2)
startCursor(ctx, nodes, {cursor}) { // <-- (3)
return cursor != null ? cursor : 0;
},
endCursor(ctx, nodes, {cursor}) { // <-- (4)
return nodes.length ? (cursor != null ? cursor : 0) + nodes.length : null;
},
sort(ctx, query, {cursor, sort}) { // <-- (5)
if (cursor) {
query = query.skip(cursor);
}
module.exports = {
context: {
Sort: () => ({
Comments: {
// <-- (1)
likes: {
// <-- (2)
startCursor(ctx, nodes, { cursor }) {
// <-- (3)
return cursor != null ? cursor : 0;
},
endCursor(ctx, nodes, { cursor }) {
// <-- (4)
return nodes.length
? (cursor != null ? cursor : 0) + nodes.length
: null;
},
sort(ctx, query, { cursor, sort }) {
// <-- (5)
if (cursor) {
query = query.skip(cursor);
}
return query.sort({
'action_counts.like': sort === 'DESC' ? -1 : 1,
created_at: sort === 'DESC' ? -1 : 1,
});
},
},
},
}),
}
return query.sort({
"action_counts.like": sort === "DESC" ? -1 : 1,
created_at: sort === "DESC" ? -1 : 1
});
}
}
}
})
}
};
```
This has a bunch of special features:
1. `Comments` is the name of the type being sorted, this is pluralized and
capitalized.
capitalized.
2. `likes` is the `sortBy` field in lowercase.
3. `startCursor` will retrieve the start cursor based on the current set of
nodes and the current cursor.
nodes and the current cursor.
4. `endCursor` will retrieve the end cursor based on the current set of nodes
and the current cursor.
and the current cursor.
5. `sort` will mutate the `query` to apply the sort operations.
All the `startCursor`, `endCursor`, and `sort` functions must be provided in
@@ -126,11 +151,13 @@ order for the sorting to apply properly.
### loaders
```js
(context) => ({
People: {
load: () => db.people.find({user: context.user})
}
})
module.exports = {
context: context => ({
People: {
load: () => db.people.find({ user: context.user })
}
})
};
```
Loaders should be provided as a function which returns a map which is used in
@@ -139,13 +166,15 @@ the resolvers function. These must return a promise or a value.
### mutators
```js
(context) => ({
People: {
create: (name) => {
return db.people.insert({user: context.user, name});
module.exports = {
context: context => ({
People: {
create: name => {
return db.people.insert({ user: context.user, name });
}
}
}
})
})
};
```
Mutators should be provided as a function which returns a map which is used in
@@ -154,27 +183,41 @@ the resolvers function. These must return a promise or a value.
### resolvers
```js
{
Person: {
name(obj, args, context) {
return obj.name;
module.exports = {
resolvers: {
Person: {
name(obj, args, context) {
return obj.name;
},
colour(obj, args, context) {
// Bill likes the colour red, everyone else likes blue.
return obj.name === "bill" ? "RED" : "BLUE";
}
},
colour(obj, args, context) {
// Bill likes the colour red, everyone else likes blue.
return obj.name === 'bill' ? 'RED' : 'BLUE';
}
},
RootQuery: {
people(obj, args, {loaders: {People}}) {
return People.load();
}
},
RootMutation: {
createPerson(obj, {name}, {mutators: {People}}) {
return People.create(name);
RootQuery: {
people(
obj,
args,
{
loaders: { People }
}
) {
return People.load();
}
},
RootMutation: {
createPerson(
obj,
{ name },
{
mutators: { People }
}
) {
return People.create(name);
}
}
}
}
};
```
Should return a resolver map as described in the
@@ -185,40 +228,44 @@ This will merge with the existing resolvers in core and from previous plugins.
### hooks
```js
{
RootMutation: {
createPerson: {
post: async (obj, args, {plugins: {Slack}}, info, person) {
if (!person) {
module.exports = {
hooks: {
RootMutation: {
createPerson: {
post: async (obj, args, {plugins: {Slack}}, info, person) {
if (!person) {
return person;
}
await Slack.notify(`A new person just was created with name ${person.name}`);
return person;
}
await Slack.notify(`A new person just was created with name ${person.name}`);
return person;
}
}
}
}
};
```
Hooks here are pretty special, for each resolver field, you can specify a
pre/post hook that will execute pre and post field resolution.
If your post function accepts four parameters, then it can modify the field
result. It is *required* that the function resolves a promise (or returns) with
result. It is _required_ that the function resolves a promise (or returns) with
the modified value or simply the original if you didn't modify it.
### setupFunctions
```js
setupFunctions: {
leader: (options, args) => ({
leader: {
filter: (person) => person.place === 1
},
}),
}
module.exports = {
setupFunctions: {
leader: (options, args) => ({
leader: {
filter: person => person.place === 1
}
})
}
};
```
Setup functions allow you to create filters that control which pubsub.publish() events
@@ -229,28 +276,32 @@ For more information, see the [Apollo Docs](https://github.com/apollographql/gra
### tokenUserNotFound
```js
tokenUserNotFound: async ({jwt, token}) => {
let profile = await someExternalService(token);
if (!profile) {
return null;
const Users = require("services/users");
module.exports = {
tokenUserNotFound: async ({ jwt }) => {
const user = await Users.upsertExternalUser(
null,
jwt.sub,
jwt.iss,
jwt.username
);
const email = jwt.email.toLowerCase();
//upsertExternalUser will also create a profile with provider:jwt.iss, id:jwt.sub
//add a "local" profile to persist email on the user
user.profiles.push({
provider: "local",
id: email
});
user.created_at = new Date(jwt.memberSince * 1000);
await user.save();
return user;
}
let user = await UserModel.findOneAndUpdate({
id: profile.id
}, {
id: profile.id,
username: profile.username,
lowercaseUsername: profile.username.toLowerCase(),
roles: [],
profiles: []
}, {
setDefaultsOnInsert: true,
new: true,
upsert: true
});
return user;
}
};
```
The `tokenUserNotFound` hook allows auth integrations to hook into the event
@@ -258,8 +309,15 @@ when a valid token is provided but a user can't be found in the database that
matches the provided id.
The function is async, and should return the user object that was created in the
database, or null if the user wasn't found. The `jwt` parameter of the object
is the unpacked token, while `token` is the original jwt token string.
database. The `jwt` parameter of the object is the unpacked token, which has already been validated.
More details about the `upsertExternalUser` method can be found at:
https://github.com/coralproject/talk/blob/32962aa1e84c09d87141440a1f04cbd1659b3336/services/users.js/#L592
In this example, a unix timestamp was included on the jwt with a date that is being persisted on the user. This is optional, but can be used to handle any extra claims you want to include on the jwt. You can use the metadata object on the user to store custom values.
Having trouble implementing your own `tokenUserNotFound` hook?
Submit a Support ticket ([support@coralproject.net](mailto:support@coralproject.net))
### tags
@@ -268,18 +326,20 @@ or enabled by code). Below is an example pulled from the core off topic plugin
on how to create a hook for the `OFF_TOPIC` name:
```js
[
{
name: 'OFF_TOPIC',
permissions: {
public: true,
self: true,
roles: []
},
models: ['COMMENTS'],
created_at: new Date()
}
]
module.exports = {
tags: [
{
name: "OFF_TOPIC",
permissions: {
public: true,
self: true,
roles: []
},
models: ["COMMENTS"],
created_at: new Date()
}
]
};
```
You can refer to `models/schema/tag.js` for the available schema to match when
@@ -288,11 +348,13 @@ creating models to enable/disable specific features.
### router
```js
(router) => {
router.get('/api/v1/people', (req, res) => {
res.json({people: [{name: 'Bob'}]});
});
}
module.exports = {
router: router => {
router.get("/api/v1/people", (req, res) => {
res.json({ people: [{ name: "Bob" }] });
});
}
};
```
The Router hook allows you to create a function that accepts the base express
@@ -302,56 +364,73 @@ action needed by external applications.
### passport
```js
const FacebookStrategy = require('passport-facebook').Strategy;
const UsersService = require('services/users');
const {ValidateUserLogin, HandleAuthPopupCallback} = require('services/passport');
const FacebookStrategy = require("passport-facebook").Strategy;
const UsersService = require("services/users");
const {
ValidateUserLogin,
HandleAuthPopupCallback
} = require("services/passport");
module.exports = {
passport(passport) {
passport.use(new FacebookStrategy({
clientID: process.env.TALK_FACEBOOK_APP_ID,
clientSecret: process.env.TALK_FACEBOOK_APP_SECRET,
callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`,
passReqToCallback: true,
profileFields: ['id', 'displayName', 'picture.type(large)']
}, async (req, accessToken, refreshToken, profile, done) => {
passport: passport => {
passport.use(
new FacebookStrategy(
{
clientID: process.env.TALK_FACEBOOK_APP_ID,
clientSecret: process.env.TALK_FACEBOOK_APP_SECRET,
callbackURL: `${
process.env.TALK_ROOT_URL
}/api/v1/auth/facebook/callback`,
passReqToCallback: true,
profileFields: ["id", "displayName", "picture.type(large)"]
},
async (req, accessToken, refreshToken, profile, done) => {
let user;
try {
const { id, provider, displayName } = profile;
user = await UsersService.upsertSocialUser(
req.context,
id,
provider,
displayName
);
} catch (err) {
return done(err);
}
let user;
try {
const { id, provider, displayName } = profile;
user = await UsersService.upsertSocialUser(
req.context,
id,
provider,
displayName
);
} catch (err) {
return done(err);
}
return ValidateUserLogin(profile, user, done);
}));
return ValidateUserLogin(profile, user, done);
}
)
);
},
router(router) {
router: router => {
// Note that we have to import the passport instance here, it is
// instantiated after all the strategies have been mounted.
const {passport} = require('services/passport');
const { passport } = require("services/passport");
/**
* Facebook auth endpoint, this will redirect the user immediately to facebook
* for authorization.
*/
router.get('/facebook', passport.authenticate('facebook', {display: 'popup', authType: 'rerequest', scope: ['public_profile']}));
router.get(
"/facebook",
passport.authenticate("facebook", {
display: "popup",
authType: "rerequest",
scope: ["public_profile"]
})
);
/**
* Facebook callback endpoint, this will send the user a html page designed to
* send back the user credentials upon successful login.
*/
router.get('/facebook/callback', (req, res, next) => {
router.get("/facebook/callback", (req, res, next) => {
// Perform the facebook login flow and pass the data back through the opener.
passport.authenticate('facebook', HandleAuthPopupCallback(req, res, next))(req, res, next);
passport.authenticate(
"facebook",
HandleAuthPopupCallback(req, res, next)
)(req, res, next);
});
}
};
@@ -360,10 +439,10 @@ module.exports = {
### translations
```js
const path = require('path');
const path = require("path");
module.exports = {
translations: path.join(__dirname, 'translations.yml'),
translations: path.join(__dirname, "translations.yml")
};
```
@@ -377,11 +456,11 @@ en:
Which overrides the copy for the `embedlink.copy` template. You can
also provide other languages as well by using the correct language
prefix.
prefix.
When creating a plugin using this `translations` hook to override copy
When creating a plugin using this `translations` hook to override copy
from another plugin, be sure to list it after the plugin it's overriding
in the `plugins.json` file.
in the `plugins.json` file.
### websockets
@@ -392,12 +471,12 @@ module.exports = {
// Do something with the connection params or connection, like
// logging it out, or incrementing a metric.
},
onDisconnect: (connection) => {
onDisconnect: connection => {
// Do something with the connection params or connection, like
// logging it out, or decrementing a metric.
},
},
}
}
}
};
```
This `websockets` hook can be used to attach methods to the
@@ -411,12 +490,13 @@ connections.
module.exports = {
schemaLevelResolveFunction: (root, args, ctx, info) => {
// The GraphQL Operation Name. Example: CoralEmbedStream_Embed
const name = info.operation.name !== null ? info.operation.name.value : null;
const name =
info.operation.name !== null ? info.operation.name.value : null;
// Maybe increment a metric based on the operation name...
// You must _always_ return the root.
return root;
},
}
};
```
@@ -431,9 +511,7 @@ Contents of `plugins.json`:
```json
{
"server": [
"people"
]
"server": ["people"]
}
```
@@ -527,5 +605,4 @@ module.exports = {
}
}
};
```
+47
View File
@@ -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
View File
@@ -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.
+22 -23
View File
@@ -3,25 +3,18 @@ title: Authenticating with Talk
permalink: /integrating/authentication/
---
Out of the box Talk supports account registration with username and password, as well as features like forgot password.
### Authentication Strategies
You can also integrate Talk with any external authentication service that will enable
seamless single sign-on (SSO) for users within your organization. There are a few
methods of doing so:
Talk comes with built in user management features like account registration with username/email and password, as well as features like resetting a forgotten password.
1. Passport Middleware
2. Custom Token Integration
There are four strategies to choose from when it comes to user authentication:
Both methods work, but there are product decisions that will affect the overall
choice.
#### Talk Email Authentication
Best when you want Talk 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 and are stored in Talks database. Optionally - you can require an email verification step at time of account creation. *Plugins required: [talk-plugin-local-auth](/talk/plugin/talk-plugin-local-auth), [talk-plugin-auth](/talk/plugin/talk-plugin-auth)*
## Passport Middleware
#### Social Sign-in (FaceBook or Google via Passport Middleware)
You would choose the **Passport Middleware** route when you are OK using an auth
that is triggered from inside Talk that is not connected to an external auth
state (you don't use the auth anywhere else now).
Plugins are available for the following 3rd party authentication providers:
Allows users to use existing social media account to sign in to Talk. Requires you to setup API keys with social sign on provider, and configure your API keys in Talk. Users register and login via a *“Sign in with ….”*, but are not required to set user details such as password or email address. *Plugins required: [talk-plugin-auth](/talk/plugin/talk-plugin-auth), plus at least one of the following:*
* [Facebook](/talk/plugin/talk-plugin-facebook-auth/)
* [Google](/talk/plugin/talk-plugin-google-auth/)
@@ -29,15 +22,20 @@ Plugins are available for the following 3rd party authentication providers:
_FAQ: Can I create a Twitter auth plugin?_
This is currently not possible because Talk uses passport.js which does not support Twitter's oAuth2 requirements.
## Custom Token Integration
#### Social Sign-in with Email Association
This strategy is actually a combination of the first two. Talks built in Email Authentication is augmented by also allowing registration with a social account. Users with social accounts are prompted to associate an email address with their social profile to enable notifications. *Plugins required: [talk-plugin-auth](/talk/plugin/talk-plugin-auth), [talk-plugin-local-auth](/talk/plugin/talk-plugin-local-auth), [talk-plugin-facebook-auth](/talk/plugin/talk-plugin-facebook-auth/), and or [talk-plugin-google-auth](/talk/plugin/talk-plugin-google-auth/)*
#### Custom SSO Token Integration
When you want users to authenticate via your existing identity system, and be automatically registered/logged into Talk. 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 Talk via jwt tokens passed from your auth service to Talk. *Required Plugins: requires custom plugin development, as well as disabling talk-plugin-auth, and talk-plugin-local-auth*
## Setting Up Custom SSO Token Integration
You can integrate Talk with any authentication service to enable single sign-on
for users. The steps to do that are:
1. Create a service that generates [JWT tokens](https://jwt.io/introduction/).
2. Push the token into the embed.
3. Implement the [`tokenUserNotFound`](#implement-tokenusernotfound) hook to
process the token.
2. Include the JWT token in your Talk embed script
3. Create a custom plugin to generate and authenticate Talk users from the jwt token
### Create JWT Token
@@ -67,7 +65,7 @@ The generated JWT must contain the following claims:
### Generate a key to sign the JWT
Optionally you can use https://github.com/coralproject/coralcert to generate a key with which to sign the JWTs and specify the secret as an environment variable.
### Push token into embed
### Include token in embed
We're assuming that your CMS is capable of authenticating a user account, or
at least having the user's details available to send off to the token creation
@@ -83,15 +81,17 @@ Coral.Talk.render(document.getElementById('coralStreamEmbed'), {
});
```
Which will pass down the token to Talk and will fire the next steps
Which will pass down the token to Talk and will fire the
`tokenUserNotFound` hook to complete the auth flow.
### Implement `tokenUserNotFound`
### Create a Custom Plugin to Generate Talk Users
This is the only piece of code you'll have to write that lives inside Talk.
The role of this code is to live as a plugin and provide Talk with a way of
taking the token that you gave it, and turning into a user.
Your custom plugin will need to include a `tokenUserNotFound` function that will handle creating a user in Talk from the claims provided on the JWT, refer to [`tokenUserNotFound`](/talk/api/server/#tokenusernotfound) for reference.
Using the example application we were working with in the JWT issuing step
above, we'll need to ensure that the configuration is consistent in-between both
Talk and the JWT issuer. Namely, the following environment variables from our
@@ -108,5 +108,4 @@ example issuer and Talk must match:
reference, the basic takeaway is that the secret used to sign the tokens issued
by the issuer must be able to be verified by Talk.
For an example of implementing the plugin, refer to [`tokenUserNotFound`](/talk/api/server/#tokenusernotfound)
reference.
For an example of implementing the plugin, check out our [SSO Custom Auth Recipe](https://github.com/coralproject/talk-recipes/tree/master/plugins/custom-auth)
+120 -52
View File
@@ -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 users 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 users session for SSO/authentication.
Instead of greedily triggering the embed to render on _EVERY PAGE LOAD_, we highly recommend implementing a _“lazy”_ rendering strategy to only render the comments section if a user wants to interact with it. This will greatly improve your initial page load performance, and will be critical to managing server resources if youre running Talk on a heavy-traffic production site.
Instead of greedily triggering the embed to render on _EVERY PAGE LOAD_, we highly recommend implementing a _“lazy”_ rendering strategy to only render the comments section if a user wants to interact with it. This will greatly improve your initial page load performance, and will be critical to managing server resources if youre running Talk on a heavy-traffic production site.
We recommend using one of these _“lazy”_ loading strategies:
#### Scroll to Comments Section
Wait for user to scroll to the comment section before triggering the embed to render.
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”
Talks provided embed script by default dynamically generates Assets in Talk based on each unique url that triggers it. The url must reference an existing Permitted Domain. If your articles/stories always have unique urls, then you will not need to modify the default behavior.
Talks provided embed script by default dynamically generates Assets in Talk based on each unique url that triggers it. The url must reference an existing Permitted Domain. If your articles/stories always have unique urls, then you will not need to modify the default behavior.
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.
@@ -3,9 +3,19 @@ title: Tracking Talk Events and Metrics
permalink: /integrating/event-tracking-metrics/
---
Talk supports event emitting via Redux, Apollo and GraphQL. This means that common actions taken within Talk, such as successfully posting a comment, posting a reaction, or changing a setting, are automatically emitted from Talk. To send these events to your analytics tool of choice, however, will require some integration on your part.
Talk supports event emitting. This means that common actions taken within Talk, such as successfully posting a comment, posting a reaction, or changing a setting, are automatically emitted from Talk. To send these events to your analytics tool of choice, youll need to configure your Talk embed to process them.
First, we want to uncomment the tracking code in `article.ejs` (https://github.com/coralproject/talk/blob/93bda87ad061a2dc5eb8dc5b65a579a20efb76f7/views/article.ejs#L34). This will enable events to be sent via the Talk embed that is on your article pages. This will start a stream of events to the browser console, so that you can see which events are available.
The following example demonstrates how the event emitter works on a sample article page generated by Talk. When youre ready to capture and handle events from your actual site, add the events property along with your event handling functions to your Talk embed script.
## Example Using Local Development Instance of Talk
If you have Talk running in a local development environment you can follow these steps to first view all of the events emitted by Talk, and then capture the `PostComment.success` event and send it to a third party analytics provider.
First, with Talk running, ensure that you can access the `/dev/assets` endpoint and create a random article (i.e.: `localhost:3000/dev/assets`).
Then in Talk, uncomment the events code in `/views/dev/article.njk`
(https://github.com/coralproject/talk/blob/master/views/dev/article.njk#L32). This will enable events to be sent via the Talk embed that is on the sample article page. This will start a stream of events to the browser console, so that you can see which events are available.
```
events: function(events) {
@@ -16,7 +26,10 @@ First, we want to uncomment the tracking code in `article.ejs` (https://github.c
},
```
Now, we want to add our code that sends the events to our analytics system. In this case, we're sending the `PostComment.success` event. 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.
Save your changes to `article.njk`, restart Talk, and try refreshing your article in a browser with development console open. You should see the console log the list of Talks events.
Next, we want to add our code that sends the events to our analytics system. In this example, we're sending the `PostComment.success` event. 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.
```
events: function(events) {
@@ -30,4 +43,24 @@ Now, we want to add our code that sends the events to our analytics system. In t
```
You can continue this process for any specific events you'd like to track. You can also remove the `console.log` to stop events being emitted to the browser and instead only send the events to your analytics tool.
PR for Reference: https://github.com/coralproject/talk/pull/785
## Metrics List
Note: This is not an exhaustive list, but should get you started.
```
mutation.CreateRespectAction.success
mutation.CreateLikeAction.success
mutation.PostComment.success
mutation.PostFlag.success
mutation.EditComment.success
action.CORAL_STREAM_SET_ACTIVE_TAB
action.SHOW_SIGNIN_DIALOG
action.FOCUS_SIGNIN_DIALOG
action.TALK_FRAMEWORK_HANDLE_SUCCESSFUL_LOGIN
action.HIDE_SIGNIN_DIALOG
action.TALK_VIEWING_OPTIONS_OPEN_MENU
action.TALK_VIEWING_OPTIONS_CLOSE_MENU
action.CORAL_STREAM_SET_SORT
action.SET_ACTIVE_REPLY_BOX
```
@@ -0,0 +1,18 @@
---
title: talk-plugin-notifications-category-moderation-actions
permalink: /plugin/talk-plugin-notifications-category-moderation-actions/
layout: plugin
plugin:
name: talk-plugin-notifications-moderation-actions
default: true
depends:
- name: talk-plugin-notifications
provides:
- Server
- Client
---
When a comment that is initially withheld from publication and is then approved or rejected, the user will receive a notification email.
Configuration:
`TALK_MODERATION_NOTIFICATION_TYPES`. This plugin requires values to be set. Available options: `APPROVED`, `REJECTED` as a single string (comma separated).
+1 -1
View File
@@ -111,7 +111,7 @@ If you deploy using Docker, you can extend from the `*-onbuild` image, an
example `Dockerfile` for your project could be:
```Dockerfile
FROM coralproject/talk:4.5-onbuild
FROM coralproject/talk:4-onbuild
```
Establish a private repository for your instance that includes the following:
+203
View File
@@ -0,0 +1,203 @@
---
title: GraphQL API Overview
permalink: /v5/api/overview/
---
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.
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 to access a GraphQL playground to use with documentation provided in the sidebar on what edges are available to you. 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**.
## 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.
+67
View File
@@ -0,0 +1,67 @@
---
title: Configuring Version 5
permalink: /v5/configuration/
---
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.
- `NODE_ENV` - Can be one of `production` or `development`. All production
deployments should use `production`. Defaults to `production` when ran with
`npm run start` and `development` when run with `npm run start:development`.
- `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`)
- `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_SECRET` - The shared secret to use to sign JSON Web Tokens (JWT) with
the selected signing algorithm. 🚨 **Don't forget to set this variable!** 🚨
(Default: `keyboard cat`)
- `SIGNING_ALGORITHM` - The signing algorithm to use for signing JWT's.
(Default `HS256`).
- `LOGGING_LEVEL` - The logging level that can be set to one of `fatal`,
`error`, `warn`, `info`, `debug`, or `trace`. (Default `info`)
- `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.
- `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`)
- `DISABLE_MONGODB_AUTOINDEXING` - When `true`, Coral will not perform indexing
operations when it starts up. This can be desired when you've already
installed Coral on the target MongoDB, but want to improve start performance.
**You should not use this parameter unless you know what you're doing! Upgrades
may introduce additional indexes that the application relies on.**
(Default `false`)
- `LOCALE` - Specify the default locale to use for all requests without a locale
specified. (Default `en-US`)
- `ENABLE_GRAPHIQL` - When `true`, it will enable the `/graphiql` even in
production, use with care. (Default `false`)
- `CONCURRENCY` - The number of worker nodes to spawn to handle web traffic,
this should be tied to the number of CPU's available. (Default
`os.cpus().length`)
- `DEV_PORT` - The port where the Webpack Development server is running on.
(Default `8080`)
- `METRICS_USERNAME` - The username for _Basic Authentication_ at the `/metrics` and `/cluster_metrics`
endpoint.
- `METRICS_PASSWORD` - The password for _Basic Authentication_ at the `/metrics` and `/cluster_metrics`
endpoint.
- `CLUSTER_METRICS_PORT` - If `CONCURRENCY` is more than `1`, the metrics are provided at this port under `/cluster_metrics`. (Default `3001`)
- `DISABLE_LIVE_UPDATES` - When `true`, disables subscriptions for the comment
stream for all stories across all tenants (Default `false`)
- `WEBSOCKET_KEEP_ALIVE_TIMEOUT` - A duration in a parsable format (e.g. `30 seconds`
, `1 minute`) that should be used to send keep alive messages through the
websocket to keep the socket alive (Default `30 seconds`)
- `TRUST_PROXY` - When provided, it configures the "trust proxy" settings for Express (See https://expressjs.com/en/guide/behind-proxies.html)
## `TRUST_PROXY`
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.
+38
View File
@@ -0,0 +1,38 @@
---
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 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.
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.
+103
View File
@@ -0,0 +1,103 @@
---
title: Developing
permalink: /v5/developing/
---
Running Coral for development is very similar to installing Coral via Source as
described above.
Coral requires NodeJS >=10, 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/.
+39
View File
@@ -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**
+120
View File
@@ -0,0 +1,120 @@
---
title: Installing Version 5
permalink: /
---
Online comments are broken. Our open-source commenting platform, Coral, reimagines
moderation, comment display, and conversation. Use Coral to add safer, smarter discussions to your site without giving away your data.
More than 70 publishers in 14 countries trust Coral to run 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 resources. [Contact us for more information.](https://coralproject.net/pricing/)
</div>
Built with ❤️ by Coral by [Vox Media](https://product.voxmedia.com/).
Try out a test version of Coral by running it via a Heroku App:
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/coralproject/talk)
## Requirements
- MongoDB >=4.2
- Redis >=3.2
- NodeJS >=10
- 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:5](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:5
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 >=10, 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 production mode.
npm run start
```
Then head on over to http://localhost:3000 to install Coral!
+29
View File
@@ -0,0 +1,29 @@
---
title: Integrating on your site
permalink: /v5/integrating/cms/
---
With Coral setup and running you can embed the comment stream 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.
+127
View File
@@ -0,0 +1,127 @@
---
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://{{ CORAL_DOMAIN_NAME }} /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 `Key` box
6. Click Save
> **NOTE:** Replace the value of ```{{ CORAL_DOMAIN_NAME }}``` 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. Without this parameter, the logout functionality inside the
embed stream will not work and you will need to call logout on the embed
itself.
- `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, the
logout functionality inside the embed stream will not work and 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.
- `user.id` **(required)** - the 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 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.
- `user.role` _(optional)_ - one of "COMMENTER", "STAFF", "MODERATOR", "ADMIN". Will create/update
Coral user with this role.
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 `Key` 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";
});
}
});
```
+1 -1
View File
@@ -1,6 +1,6 @@
<article{% if page.class %} class="{{ page.class }}"{% endif %}>
{% if page.title %}
<a class="btn btn-sm btn-light float-right suggest-edits plain-link" href="https://github.com/coralproject/talk/edit/master/docs/source/{{ page.source }}" title="Suggest edits to this page">Suggest Edits</a>
<a class="btn btn-sm btn-light float-right suggest-edits plain-link" href="https://github.com/coralproject/talk/edit/release/4/docs/source/{{ page.source }}" title="Suggest edits to this page">Suggest Edits</a>
<h1>{{ page.title }}</h1>
<hr/>
{% endif %}
+1 -1
View File
@@ -1,6 +1,6 @@
<hr/>
<footer class="footer">
<p class="text-center">Coral Project Talk Technical Documentation. Design inspired by <a href="https://docs.minio.io/" rel="noreferrer" target="_blank">Minio Docs</a>.</p>
<p class="text-center">Coral by Vox Media Technical Documentation. Design inspired by <a href="https://docs.minio.io/" rel="noreferrer" target="_blank">Minio Docs</a>.</p>
</footer>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
+32 -33
View File
@@ -1,36 +1,35 @@
<aside class="sidebar">
<a class="brand" href="{{ url_for("/") }}">
<h2>
{{ image_tag('images/logo.svg', {width: 50, height: 50}) }}
<span>Talk</span>
</h2>
</a>
<ul class="sidebar__list">
{% for item in config.sidebar.side %}
{% if item.url %}
<li class="sidebar__section{% if is_current(item.url) %} active toggled{% endif %}">
<a href="{{ url_for(item.url) }}" class="sidebar__header sidebar__header__link">{{ item.title }}</a>
</li>
{% else %}
<li class="sidebar__section{% for item in item.children %}{% if is_current(item.url) %} active toggled{% endif %}{% endfor %}">
<a href="#" class="sidebar__header">{{ item.title }}</a>
<ul class="sidebar__links">
{% for item in item.children %}
<li class="{% if is_current(item.url) %}active{% endif %}">
<a href="{{ url_for(item.url) }}">{{ item.title }}</a>
</li>
{% endfor %}
</ul>
</li>
{% endif %}
{% endfor %}
</ul>
<div class="notice">
<p>Don't see what you're looking for? We're upgrading the docs, check out our <a href="https://github.com/coralproject/talk/tree/ef49d9a3d2acc4d2fc03b00e0c872dfbc57f005a/docs/_docs" target="_blank" class="coral-link-invert">old docs</a>.</p>
<p><a href="https://coralproject.net/" class="coral-link-invert" target="_blank">The Coral Project</a> from Mozilla</p>
<a href="https://www.mozilla.org/" target="_blank" title="Mozilla"><figure class="mozilla" alt="Mozilla">{{ image_tag('images/moz-logo-bw-rgb.svg') }}</figure></a>
<div>
<a class="brand" href="{{ url_for("/") }}">
{{ image_tag('images/coralproject_color.svg') }}
</a>
<ul class="sidebar__list">
{% for item in config.sidebar.side %}
{% if item.url %}
<li class="sidebar__section{% if is_current(item.url) %} active toggled{% endif %}">
<a href="{{ url_for(item.url) }}" class="sidebar__header sidebar__header__link">{{ item.title }}</a>
</li>
{% else %}
<li class="sidebar__section{% for item in item.children %}{% for item in item.children %}{% if is_current(item.url) %} active toggled{% endif %}{% endfor %}{% endfor %}">
<a href="#" class="sidebar__header">{{ item.title }}</a>
<ul class="sidebar__headers">
{% for item in item.children %}
<li class="sidebar__subsection{% for item in item.children %}{% if is_current(item.url) %} active toggled{% endif %}{% endfor %}">
<a href="#" class="sidebar__header">{{ item.title }}</a>
<ul class="sidebar__links">
{% for item in item.children %}
<li class="{% if is_current(item.url) %}active{% endif %}">
<a href="{{ url_for(item.url) }}">{{ item.title }}</a>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</aside>
<div class="sidebar__backdrop"></div>
<div class="sidebar__backdrop"></div>
+54 -16
View File
@@ -1,21 +1,59 @@
$coral_orange: #f67150;
// Palate
$coral_orange: rgb(245, 100, 85);
$coral_dark_blue: rgb(19, 28, 84);
$coral_light_blue: rgb(83, 192, 191);
$coral_light_gray: rgb(238, 238, 238);
$coral_dark_gray: rgb(46, 56, 61);
$coral_success: rgb(35, 131, 53);
$coral_error: rgb(208, 48, 53);
// Variables
// // Sidebar
$sidebar_width: 320px;
$sidebar_background: $coral_orange;
$sidebar_color: white;
$sidebar_background: white;
$sidebar_color: black;
$sidebar_gutter: 35px;
// // Layout
$content_gutter: 40px;
$header_padding_bottom: $content_gutter;
$breakpoint: 960px;
$hljs_string_color: $coral_orange;
$code_background: #F0F0F0;
$code_border: 1px solid darken($code_background, 10%);
$copy_button_background: white;
$coral_button_background: $coral_orange;
$coral_button_border: darken($coral_button_background, 5%);
$demo_border: lighten($coral_button_border, 30%);
$coral_button_border_color: $coral_orange;
$coral_button_color: black;
$coral_button_hover_color: $coral_button_border_color;
$coral_button_invert_border_color: lighten($coral_button_border_color, 20%);
$coral_button_invert_color: white;
$coral_button_invert_hover_color: $coral_button_invert_border_color;
// // Code
$hljs_string_color: $coral_light_blue;
$code_background: $coral_light_gray;
$code_border: 0;
$copy_button_color: white;
$copy_button_background: $coral_light_blue;
// // Call out
$callout_color: white;
$callout_background: $coral_dark_blue;
$callout_link_color: darken($callout_color, 20%);
$callout_link_hover_color: darken($callout_link_color, 5%);
// // Buttons
$coral_button_background: $coral_light_blue;
$coral_button_color: $coral_orange;
$coral_button_hover_color: darken($coral_button_background, 5%);
$coral_button_invert_color: $coral_dark_gray;
$coral_button_invert_hover_color: lighten($coral_button_invert_color, 20%);
// // Search
$algolia_suggestion_highlight_color: $coral_light_blue;
$algolia_suggestion_highlight_background: fade-out(
$algolia_suggestion_highlight_color,
0.9
);
// // Navigation
$header_nav_link_color: $coral_dark_gray;
$header_nav_icon_color: $coral_dark_gray;
+81 -55
View File
@@ -3,7 +3,7 @@
@import "hljs";
body {
font-family: 'Open Sans';
font-family: "Open Sans";
font-style: normal;
font-variant: normal;
font-weight: 400;
@@ -58,31 +58,20 @@ h4 {
font-weight: 500;
}
.btn-coral {
color: white;
background: $coral_button_background;
border: 1px solid $coral_button_border;
&:hover {
color: white;
cursor: pointer;
background: darken($coral_button_background, 5%);
border-color: darken($coral_button_border, 5%);
}
}
.param {
font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
monospace;
}
article.configuration {
h2, .toc {
h2,
.toc {
@extend .param;
}
}
.suggest-edits {
padding: .20rem .5rem;
padding: 0.2rem 0.5rem;
}
/**
@@ -91,14 +80,14 @@ article.configuration {
.demo {
background: $coral_orange;
border: 1px solid $demo_border;
overflow: hidden;
margin: 0 auto;
padding: 0;
margin-bottom: 1rem;
max-width: 650px;
.btn, .alert {
.btn,
.alert {
border: none;
border-radius: 0;
margin: 0;
@@ -109,7 +98,6 @@ article.configuration {
}
.mount {
border-top: 1px solid $demo_border;
display: none;
background: white;
padding: 10px;
@@ -123,6 +111,7 @@ article.configuration {
background: $copy_button_background;
padding: 3px 10px;
border: $code_border;
color: $copy_button_color;
&:hover {
background: darken($copy_button_background, 10%);
@@ -142,7 +131,6 @@ pre {
color: #212529;
position: relative;
& > code {
display: block;
overflow-x: auto;
@@ -196,8 +184,8 @@ pre {
left: 0 !important;
.algolia-docsearch-suggestion--highlight {
color: $coral_orange;
background: fade-out($coral_orange, 0.9);
color: $algolia_suggestion_highlight_color;
background: $algolia_suggestion_highlight_background;
}
.ds-dropdown-menu {
@@ -207,7 +195,7 @@ pre {
// Hide the carrot.
&:before {
display: none
display: none;
}
// Fix for smaller screens.
@@ -215,7 +203,6 @@ pre {
min-width: 200px;
}
}
}
&.header--toggled {
@@ -232,7 +219,7 @@ pre {
display: inline-block;
text-align: center;
margin-left: 15px;
color: $coral_orange;
color: $header_nav_link_color;
white-space: nowrap;
& > svg {
@@ -240,12 +227,12 @@ pre {
width: 20px;
rect {
fill: $coral_orange;
fill: $header_nav_icon_color;
}
}
&:hover {
color: darken($coral_orange, 10%);
color: darken($header_nav_icon_color, 10%);
}
}
}
@@ -262,7 +249,6 @@ pre {
}
.coral-link {
border-bottom: 1px solid $coral_button_border_color;
color: $coral_button_color;
text-decoration: none;
@@ -274,7 +260,6 @@ pre {
.coral-link-invert {
@extend .coral-link;
border-color: $coral_button_invert_border_color;
color: $coral_button_invert_color;
&:hover {
@@ -310,6 +295,7 @@ a.brand {
padding: $sidebar_gutter;
color: #fff;
text-decoration: none;
min-height: 90px;
h2 {
margin: 0;
@@ -328,15 +314,9 @@ a.brand {
font-weight: 500;
}
.logo {
img {
vertical-align: middle;
height: 50px;
width: 50px;
@include transition(all, 250ms);
&:hover {
@include transform(rotate(8deg));
}
width: 100%;
}
}
@@ -344,9 +324,13 @@ a.brand {
padding: $content_gutter $content_gutter 60px;
}
.mozilla {
width: 50%;
.voxmedia {
// width: 50%;
margin: 0 0 $sidebar_gutter;
> img {
width: 100%;
}
}
.sidebar {
@@ -354,6 +338,9 @@ a.brand {
color: $sidebar_color;
height: 100%;
position: fixed;
display: flex;
justify-content: space-between;
flex-direction: column;
top: 0;
left: 0;
z-index: 2;
@@ -365,7 +352,7 @@ a.brand {
@media (max-width: $breakpoint) {
&.sidebar--toggled {
@include transform(translate3d(0, 0, 0));
box-shadow: 0 0 30px rgba(0,0,0,0.3);
box-shadow: 0 0 30px rgba(0, 0, 0, 0.3);
z-index: 3;
}
}
@@ -378,26 +365,49 @@ a.brand {
padding: 0px;
}
.notice {
padding: $sidebar_gutter $sidebar_gutter 0;
color: fade-out($sidebar_color, 0.5);
}
.sidebar__section {
list-style: none;
border-bottom: 1px solid lighten($sidebar_background, 3%);
&.active {
.sidebar__header {
background-color: darken($sidebar_background, 2%);
}
}
&.toggled {
& > .sidebar__header {
background: darken($sidebar_background, 10%);
// color: white;
}
.sidebar__subsection {
display: block;
}
.sidebar__links {
display: block;
}
}
&.active {
& > .sidebar__header {
background: darken($sidebar_background, 30%);
}
}
}
.sidebar__subsection {
display: none;
}
.sidebar__subsection {
list-style: none;
&.active {
& > .sidebar__header {
color: fade-out($sidebar_color, 0.3);
background-color: darken($sidebar_background, 2%);
}
}
}
.sidebar__headers {
padding: 0;
}
a.sidebar__header {
@@ -421,7 +431,6 @@ a.brand {
overflow-y: hidden;
& > li {
& > a {
display: block;
padding: 6px $sidebar_gutter;
@@ -434,11 +443,11 @@ a.brand {
}
&.active > a {
color: fade-out($sidebar_color, 0.3);
color: fade-out($sidebar_color, 0.2);
}
&.active {
background: darken($sidebar_background, 5%);
background: darken($sidebar_background, 1%);
}
}
}
@@ -461,3 +470,20 @@ a.brand {
background: rgb(0, 102, 176);
color: #fff;
}
.callout {
background-color: $callout_background;
color: $callout_color;
margin: 20px 0;
padding: 20px;
text-align: center;
a {
@extend .coral-link;
color: $callout_link_color;
&:hover {
color: $callout_link_hover_color;
}
}
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

-41
View File
@@ -1,41 +0,0 @@
<svg version="1.1" class="logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 255 255" style="enable-background:new 0 0 255 255;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<title>Artboard 1</title>
<g>
<path class="st0" d="M163.5,38.1c-2.8,12.8-2.1,21.9,2.1,26.9c5.6,6.7,18.4,5.6,25.7,0.2c5.8-4.3,7.7-12.3,8.9-19.6
c0.2-1.5,0.4-3.4,0.6-5.3c0.7-8.6,1.8-21.6,13.4-27c16.8-7.9,31.6,9.5,39.7,18.9c0.5,0.6,1.2,1.1,1.2,1.6v-8.9
c0.1-13.6-10.8-24.7-24.3-24.8c-0.1,0-0.2,0-0.3,0h-43.7C174.1,12,166.5,24.4,163.5,38.1z"/>
<path class="st0" d="M255,198.2v-38.5c-16,2.7-31.2-1.1-45.9-4.9c-7.3-2-14.7-3.6-22.1-4.8c-11.6-1.5-20.6,3.8-24.9,14.6
c-6,15.2,7,25.6,25.9,38.7c5.6,3.9,10.5,7.2,14.1,10.7c7.1-7.6,15-12.5,23.8-14.5C235.8,197.3,246,197.6,255,198.2z"/>
<path class="st0" d="M153.8,99.7c1.3,1,2.7,1.9,4.2,2.7c6,3.4,10.6,4.2,16.4,2.8c1.1-0.3,2.3-0.6,3.4-0.9c4.6-1.2,9.7-2.6,15.3-2.2
c7.8,0.4,14.4,4,20.6,7.6l0.6,0.3c4.7,2.8,9.2,5.4,13.7,6c6.3,0.9,11.2-4.9,17.5-13.1l0.5-0.7c0.6-0.8,1.2-1.8,2-2.8
c2.6-3.2,4.9-6.5,7-10.1V57.8c-1-2.2-3.4-4.5-5-6.7s-3.3-4.6-5.1-6.5c-0.7-0.8-1.4-1.7-2.2-2.6c-5.1-5.9-15.6-18.1-22.1-15.1
c-3.5,1.6-4.2,7.1-4.8,14.7c-0.2,2.2-0.4,4.4-0.7,6.4c-1.5,9.6-4.6,21.9-14.7,29.4c-6.7,4.9-14.7,7.5-23,7.6l0,0
c-9.6,0-17.8-3.8-23.2-10.3c-7.4-8.9-9.1-21.9-5.2-39.7c2.7-12.5,8.4-24,17.4-35h-33.5c-2.3,13-4.4,31.6-2.6,49.7
C132.4,73.1,140.4,89.9,153.8,99.7z"/>
<path class="st0" d="M81,130.4l3.4,0.4c1.9,0.2,4,0.6,6.2,1c8.1,1.4,19.2,3.4,22.5-1.6c2.7-4.1-0.8-12.9-5.7-18.2
c-3.5-3.8-7.9-6.9-12.5-10.2c-2.7-1.9-5.5-3.9-8.2-6.1c-6.4-5.2-13.3-12.8-9.4-25.4c1.3-3.8,3-7.4,5-10.8c1.9-3.4,3.5-6.3,4.2-9.5
c1.1-5.6-1.6-16.6-6.7-22c-2.7-2.8-4.7-2.8-6.1-2.6C62.8,27,58.7,38.5,54.1,54.6c-1.1,3.8-2.1,7.3-3.3,10.5l-0.1,0.4
c-3.2,8.8-8,22.1-20.7,25.6C19.4,94,9,89,0,82.8v32c3,6,8,11.9,14.7,17.8c5.5,4.6,11.4,8.8,17.6,12.3c3.9-6.3,9.9-11.1,16.9-13.6
C60,127.5,71.1,129,81,130.4z"/>
<path class="st0" d="M229.2,214.2c-6.6,1.5-12.5,5.4-18.1,12.1c4.9,9,8.5,18.7,10.6,28.8h8.6c13.5,0,24.6-10.9,24.6-24.4
c0-0.1,0-0.2,0-0.3v-17C246,212.7,237.5,212.3,229.2,214.2z"/>
<path class="st0" d="M26,76.6c5.2-1.4,8.3-10,10.5-16.2l0.1-0.4c1-2.8,2-6.1,3-9.6c4.5-15.5,10.6-36.8,31.8-39.9
c7-1,13.8,1.6,19.1,7.2c9,9.5,12.4,25.6,10.5,35.2c-1.1,5.4-3.6,9.9-5.8,13.8c-1.5,2.5-2.8,5.2-3.8,8c-0.9,3-0.8,5,4.6,9.3
c2.3,1.9,4.8,3.7,7.5,5.6c5,3.6,10.2,7.3,14.7,12.2c7.4,7.9,15.4,24.3,7.3,36.7c-8.8,13.3-26.2,10.2-37.7,8.1
c-2-0.4-3.9-0.7-5.5-0.9l-3.5-0.5c-8.6-1.2-17.5-2.4-24.9,0.1c-7.1,2.4-12.6,10.7-12.8,19.3c-0.1,3.8,0.8,10.5,8.3,13.5
c9.2,3.7,19.4,3.3,30.2,2.8c11.6-0.5,23.6-1,35.5,3.5c16,6.1,30.6,20.4,38,37.7c4.8,11.1,6.3,22.8,4.4,32.8h48.8
c-2-8-6.4-21.3-14.1-29.7c-2.5-2.8-7.5-6.1-12.8-9.8c-16.8-11.6-42.1-29.1-31.3-56.5c6.8-17.2,22.4-26.3,40.8-23.9
c8.3,1.1,16.1,3.1,23.8,5.1c14.5,3.7,28.2,7.2,42.2,4.1v-29.9c-5,7.5-13.8,16.5-25.8,16.5c-1.1,0-2.3-0.1-3.4-0.2
c-7.4-1-13.6-4.7-19.1-7.9l-0.6-0.3c-4.8-2.8-9.4-5.3-13.9-5.6c-3.2-0.2-6.8,0.8-10.6,1.8c-1.3,0.3-2.5,0.7-3.8,1
c-9.5,2.3-17.9,0.9-27.3-4.3c-1.9-1.1-3.8-2.3-5.6-3.6c-17-12.4-27-32.7-29.7-60.6c-1.8-18.6,0-37.2,2.3-51.2H25
C11.2,0,0.1,11.1,0,24.8v38.8C7,70,18.4,78.7,26,76.6z"/>
<path class="st0" d="M62.8,231.9c-20.2-1.1-32.2-2.7-43.4-23.9c-1.9-3.7-0.5-8.2,3.1-10.1c3.7-1.9,8.2-0.5,10.1,3.1
c7.5,14.2,12.6,14.9,31,15.9c14.4,0.8,26.2,14.1,35,38.1h43.7c2.1-8,1.2-17.6-2.8-26.9c-5.9-13.6-17.2-24.9-29.6-29.6
c-9-3.4-19-3-29.5-2.5C68.5,196.4,56.2,197,44,192c-11.4-4.6-18-15-17.6-27.9c0-1.8,0.2-3.6,0.5-5.3C17.1,153.6,8.1,147,0,139.4
v90.8C0.1,244,11.2,255.1,25,255c0,0,0,0,0,0h57.5C78.3,245,71.4,232.3,62.8,231.9z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 360 102.8" style="enable-background:new 0 0 360 102.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<rect y="-0.2" width="360" height="102.9"/>
<path class="st0" d="M343.8,73.7c-0.7,0.2-1.3,0.3-1.9,0.3c-2.1,0-3.1-0.9-3.1-3.5V51c0-10.3-8.2-15.3-17.9-15.3
c-7.4,0-11.4,0.9-19.3,4.1L300,50.2l10.3,1.1l1.5-5.1c2.1-1.1,4.2-1.3,6.9-1.3c7.3,0,7.4,5.5,7.4,10.1v1.5
c-2.3-0.3-4.9-0.4-7.4-0.4c-10.3,0-21,2.6-21,13.7c0,9.4,7.4,12.9,13.9,12.9c7.3,0,11.9-4.4,14.5-9c0.6,5.5,3.9,9,9.9,9
c2.8,0,5.7-0.8,8.1-2.1L343.8,73.7z M316.2,73.4c-3.9,0-5.3-2.3-5.3-5.2c0-4.9,4-6.2,8.6-6.2c2.1,0,4.4,0.3,6.5,0.6
C325.7,70.1,320.8,73.4,316.2,73.4z M298.9,12.9l-19.5,68.7h-12.7l19.5-68.7H298.9z M272.7,12.9l-19.5,68.7h-12.7L260,12.9H272.7z
M218.7,36.6h13.5v16.2h-13.5V36.6z M218.7,65.5h13.5v16.2h-13.5V65.5z M199,64.9l10.2,1l-2.8,15.8h-39.3l-1.3-6.8l24.8-28.5h-14.1
l-2,7l-9.3-1l1.6-15.8h39.5l1,6.8l-25,28.5h14.6L199,64.9z M134.5,35.7c-16.2,0-24.2,10.9-24.2,24.1c0,14.4,9.6,22.9,23.5,22.9
c14.4,0,24.8-9.1,24.8-23.5C158.6,46.6,150.7,35.7,134.5,35.7z M134.2,72.7c-7,0-10.6-6-10.6-13.8c0-8.5,4.1-13.4,10.7-13.4
c6.1,0,11,4.1,11,13.2C145.3,67.4,140.9,72.7,134.2,72.7z M98.7,71.8h6v9.8H85.9V56.3c0-7.8-2.6-10.8-7.7-10.8
c-6.2,0-8.7,4.4-8.7,10.7v15.7h6v9.8H56.6V56.3c0-7.8-2.6-10.8-7.7-10.8c-6.2,0-8.7,4.4-8.7,10.7v15.7h8.6v9.8H21.4v-9.8h6V46.4h-6
v-9.8h18.9v6.8c2.7-4.8,7.4-7.7,13.7-7.7c6.5,0,12.5,3.1,14.7,9.7c2.5-6,7.6-9.7,14.7-9.7c8.1,0,15.5,4.9,15.5,15.6V71.8z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

+3 -3
View File
@@ -64,13 +64,13 @@ const wrapCheck = (
/**
* checkPermissions checks that the current user has all the required
* permissions.
* permissions. It will return true if that's the case.
*
* @param {Object} ctx graph context
* @param {Array<String>} permissions permissions that the user must have
*/
const checkPermissions = (ctx, permissions) =>
!ctx.user || !ctx.user.can(...permissions);
ctx.user && ctx.user.can(...permissions);
/**
* wrapCheckPermissions will wrap a specific field with a permission check.
@@ -89,7 +89,7 @@ const wrapCheckPermissions = (
wrapCheck(
typeResolver,
field,
(obj, args, ctx) => !checkPermissions(ctx, permissions),
(obj, args, ctx) => checkPermissions(ctx, permissions),
skipFieldResolver
);
+8 -4
View File
@@ -8,7 +8,7 @@ const {
SUBSCRIBE_ALL_USERNAME_CHANGED,
SUBSCRIBE_ALL_USERNAME_FLAGGED,
SUBSCRIBE_ALL_USERNAME_REJECTED,
SUBSCRIBE_COMMENT_ACCEPTED,
SUBSCRIBE_ALL_COMMENT_ACCEPTED,
SUBSCRIBE_COMMENT_FLAGGED,
SUBSCRIBE_COMMENT_REJECTED,
SUBSCRIBE_COMMENT_RESET,
@@ -64,10 +64,14 @@ const setupFunctions = {
return !args.asset_id || comment.asset_id === args.asset_id;
},
commentAccepted: (options, args, comment, context) => {
if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_ACCEPTED)) {
return false;
if (args.asset_id) {
// Allow sending back the comment for the user that has filtered based
// on a specific asset.
return comment.asset_id === args.asset_id;
} else {
// Only privileged users can subscribe to all assets.
return context.user && context.user.can(SUBSCRIBE_ALL_COMMENT_ACCEPTED);
}
return !args.asset_id || comment.asset_id === args.asset_id;
},
commentRejected: (options, args, comment, context) => {
if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_REJECTED)) {
+6
View File
@@ -879,6 +879,9 @@ type Settings {
# customCssUrl is the URL of the custom CSS used to display on the frontend.
customCssUrl: String
# customAdminCssUrl is the URL of the custom CSS used to display on the admin panel.
customAdminCssUrl: String
# closedTimeout is the amount of seconds from the created_at timestamp that a
# given asset will be considered closed.
closedTimeout: Int
@@ -1353,6 +1356,9 @@ input UpdateSettingsInput {
# customCssUrl is the URL of the custom CSS used to display on the frontend.
customCssUrl: String
# customAdminCssUrl is the URL of the custom CSS used to display on the admin panel.
customAdminCssUrl: String
# closedTimeout is the amount of seconds from the created_at timestamp that a
# given asset will be considered closed.
closedTimeout: Int
+6 -1
View File
@@ -111,7 +111,12 @@ const processJob = transport => async ({ id, data }, done) => {
const { message } = data;
// Get the email address from the job data.
message.to = await getEmailAddress(data);
try {
message.to = await getEmailAddress(data);
} catch (err) {
logger.error({ err }, 'Failed to get user email address to send mail');
return done(err);
}
const log = logger.child({ jobID: id });
log.info('Starting to send mail');
+1 -3
View File
@@ -312,7 +312,6 @@ ar:
button: أرسل
error: 'يمكن أن تحتوي أسماء المستخدمين على أحرف, أرقام و _ فقط'
label: 'اسم مستخدم جديد'
msg: 'تم تعليق حسابك حاليا نظرا لأن اسم المستخدم قد اعتبر غير لائق. لاستعادة حسابك، يرجى إدخال اسم مستخدم جديد. يرجى الاتصال بنا إذا كان لديك أي أسئلة.'
my_comments: تعليقاتي
my_profile: ملفي
new_count: 'شاهد {0} أكثر {1}'
@@ -359,7 +358,7 @@ ar:
sign_in_button: 'تسجيل الدخول'
sign_in_message: 'تسجيل الدخول للتفاعل مع مجتمعك.'
password: كلمه المرور
request_passowrd: 'اطلب واحدة جديده.'
request_password: 'اطلب واحدة جديده.'
team_sign_in: 'تسجيل الدخول لفريق العمل'
marketing: 'هذا يشبه الإعلان / التسويق'
moderate_all_streams: 'Moderate comments on All Stories'
@@ -559,4 +558,3 @@ ar:
view_conversation: 'عرض المحادثة'
your_account_has_been_banned: 'تم حظر حسابك.'
your_account_has_been_suspended: 'تم تعليق حسابك مؤقتا.'
your_username_has_been_rejected: 'تم تعليق حسابك لعدم صلاحية اسم المستخدم الخاصة بك. لاستعادة حسابك رجاء أدخل اسم مستخدم جديدة.'
-2
View File
@@ -266,7 +266,6 @@ da:
button: Indsend
error: 'Brugernavne kan kun indeholde bogstaver og _'
label: 'Nyt brugernavn'
msg: 'Din konto er midlertidigt suspenderet, fordi dit brugernavn er blevet anset for upassende. For at gendanne din konto skal du indtaste et nyt brugernavn. Kontakt os venligst, hvis du har spørgsmål.'
my_comments: 'Mine kommentarer'
my_profile: 'Min profil'
new_count: 'Se {0} mere {1}'
@@ -473,4 +472,3 @@ da:
view_conversation: 'Vis samtale'
your_account_has_been_banned: 'Din konto er blevet banned.'
your_account_has_been_suspended: 'Din konto er midlertidigt suspenderet'
your_username_has_been_rejected: 'Din konto er blevet suspenderet fordi dit brugernavn er blevet anset for upassende. For at gendanne din konto, venligst indtast et nyt brugernavn.'
+77 -4
View File
@@ -3,6 +3,13 @@ de:
sort_comments: 'Kommentare sortieren'
view_options: Ansichtsoptionen
already_flagged_username: 'Sie haben diesen Nutzernamen schon markiert.'
alwayspremoddialog:
are_you_sure: 'Sind sie sicher, dass sie immer vormoderieren wollen für {0}?'
always_premod_user: 'Nutzer immer vormoderieren?'
cancel: Abbrechen
note: 'Hinweis: {0}'
note_always_premod_user: 'Kommentare dieses Nutzers werden niemals automatisch freigegeben sondern erscheinen immer in der Liste für Vormoderation.'
yes_always_premod_user: 'Nutzer vormoderieren'
bandialog:
are_you_sure: 'Sind Sie sich sicher, dass Sie {0} sperren möchten?'
ban_user: 'Nutzer sperren?'
@@ -28,6 +35,9 @@ de:
flagged: markiert
undo_reject: 'Rückgängig machen'
view_context: 'Kontext ansehen'
edit_history: 'Verlauf anpassen'
show_edit_history: 'Zeige Verlaufshistorie'
hide_edit_history: 'Verstecke Verlaufshistorie'
comment_box:
cancel: Abbrechen
characters_remaining: 'verbleibende Zeichen'
@@ -48,9 +58,10 @@ de:
comment_post_notif_premod: 'Vielen Dank für Ihren Kommentar. Unsere Moderatoren werden ihn in Kürze bearbeiten.'
comment_singular: Kommentar
common:
copied: 'Kopiert'
notsupported: 'Nicht unterstützt'
contains_link: 'Enthält einen Link'
copy: Kopieren
copied: 'Kopiert'
notsupported: 'Nicht unterstützt'
error: 'Ein Problem ist aufgetreten.'
reaction: 'Reaktion'
reactions: 'Reaktionen'
@@ -62,6 +73,8 @@ de:
active: Aktiv
admin: Administrator
ads_marketing: 'Dies scheint Werbung zu sein'
all: 'Alle'
always_premod: 'Vormoderiert'
are_you_sure: 'Sind Sie sich sicher, dass Sie {0} sperren möchten?'
ban_user: 'Nutzer sperren?'
banned: Gesperrt
@@ -69,6 +82,8 @@ de:
cancel: Abbrechen
commenter: Kommentator
dont_like_username: 'Unsympathischer Nutzername'
filter_users: 'Nutzer filtern'
filter_role: Rolle
flaggedaccounts: 'Gemeldete Nutzernamen'
flags: Markierungen
impersonating: 'Identität unklar'
@@ -85,6 +100,7 @@ de:
spam_ads: Spam/Werbung
staff: Mitarbeiter
status: Status
suspended: 'Vorübergehend gesperrt'
username_and_email: 'Nutzername und E-Mail'
yes_ban_user: 'Ja, Nutzer sperren'
configure:
@@ -111,6 +127,8 @@ de:
copy_and_paste: 'Kopieren Sie diesen Code in Ihr CMS, um die Kommentarfunktion an entsprechender Stelle anzuzeigen.'
custom_css_url: 'Benutzerdefinierte CSS-URL'
custom_css_url_desc: 'URL eines CSS-Stylesheets zum Überschreiben des Standard-Designs'
custom_admin_css_url: 'Benutzerdefinierte CSS URL für Admin Panel'
custom_admin_css_url_desc: 'URL eines CSS-Stylesheets zum Überschreiben des Standart-Designs für das Admin Panel.'
days: Tage
description: 'Ändern Sie die Einstellungen für den Kommentarbereich dieses Artikels.'
disable_commenting_desc: 'Verfassen Sie eine Nachricht, die angezeigt wird, solange das Kommentieren deaktiviert ist.'
@@ -147,6 +165,9 @@ de:
organization_info_copy_2: 'Wir empfehlen, einee generische E-Mail-Adresse (z.B. community@yournewsroom.com) für diesen Zweck einzurichten. Die kann über die Zeit gleich bleiben, und gibt nach außen keine Namen preis, die von Nutzern im Fall von Konflikten für persönliche Angriffe missbraucht werden könnten.'
organization_information: 'Über die Organisation'
organization_name: 'Name der Organisation'
suspect_or_forbidden_words_placeholder: 'Wort oder Phrase'
product_guide_link: 'Produkt Guide'
report_bug_or_feedback: 'Feedback'
require_email_verification: 'E-Mail-Bestätigung erforderlich'
require_email_verification_text: 'Neue Nutzer müssen ihre E-Mail-Adresse bestätigen.'
save: Speichern
@@ -165,6 +186,7 @@ de:
suspect_word_title: 'Liste verdächtiger Wörter'
tech_settings: 'Technische Einstellungen'
title: 'Kommentarbereich konfigurieren'
view_last_version: 'Letzte Version'
weeks: Wochen
wordlist: 'Gesperrte Wörter'
confirm_email:
@@ -222,6 +244,7 @@ de:
copied: 'Kopiert'
error:
ALREADY_EXISTS: 'Ressource existiert bereits'
AUTHENTICATION: 'Bei der Anmeldung ist ein Fehler aufgetreten.'
CANNOT_IGNORE_STAFF: 'Mitarbeiter können nicht ignoriert werden.'
COMMENT_PARENT_NOT_VISIBLE: 'Der Kommentar, auf den Sie antworten möchten, wurde entfernt oder existiert nicht.'
COMMENT_TOO_SHORT: 'Kommentare sollten mehr als ein Zeichen enthalten, bitte überprüfen Sie Ihren Kommentar und probieren Sie es erneut.'
@@ -250,6 +273,7 @@ de:
organization_contact_email: 'E-Mail-Adresse der Organisation ist ungültig.'
organization_name: 'Namen von Organisationen dürfen nur Buchstaben und Zahlen enthalten.'
password: 'Passwort muss mindestens 8 Zeichen enthalten'
PAGE_NOT_AVAILABLE_ROLE: 'Der Zugriff auf diese Seite ist eingeschränkt. Bitte wenden Sie sich an den Administrator.'
PASSWORD_INCORRECT: 'Ihr bestehendes Passwort wurde falsch eingegeben'
PASSWORD_LENGTH: 'Passwort ist zu kurz'
PASSWORD_REQUIRED: 'Passwort ist erforderlich'
@@ -265,6 +289,13 @@ de:
USERNAME_REQUIRED: 'Nutzername muss angegeben werden'
flag_comment: 'Kommentar melden'
flag_reason: 'Grund der Meldung (optional)'
flag_reasons:
username:
impersonating: 'Gibt sich für jemand anderen aus'
nolike: 'Ich mag diesen Nutzernamen nicht'
offensive: 'Dieser Nutzername ist unangemessen'
other: Sonstiges
spam: 'Dies scheint Werbung zu sein'
flag_username: 'Nutzername melden'
flagged_usernames:
notify_approved: '{0} hat Nutzername {1} freigegeben'
@@ -281,6 +312,7 @@ de:
comment_spam: Spam
links: Link
suspect_word: 'Verdächtiges Wort'
trust: Karma
user:
username_impersonating: 'Identität unklar'
username_nolike: Unerwünscht
@@ -300,11 +332,11 @@ de:
comments: Kommentare
configure_stream: Konfigurieren
content_not_available: 'Dieser Inhalt ist nicht verfügbar'
edit: Ändern
edit_name:
button: Senden
error: 'Nutzernamen dürfen nur Buchstaben, Zahlen und _ enthalten'
label: 'Neuer Nutzername'
msg: 'Ihr Konto ist vorübergehend gesperrt, da Ihr Nutzername als unangemessen eingestuft wurde. Um Ihr Konto wieder herzustellen, geben Sie bitte einen neuen Nutzernamen ein. Bei Fragen, kontaktieren Sie uns bitte.'
my_comments: 'Meine Kommentare'
my_profile: 'Mein Profil'
new_count: '{0} {1} mehr anzeigen'
@@ -343,16 +375,30 @@ de:
title: 'Zugelassene Domains'
like: 'Gefällt mir'
loading_results: 'Lädt Ergebnisse'
login:
email_address: 'E-Mail-Adresse'
forgot_password: 'Passwort vergessen?'
go_back: 'Zurück'
sign_in: 'Anmelden'
sign_in_button: 'Anmelden'
sign_in_message: 'Anmelden um mit der Community zu interagieren.'
password: Passwort
reset_password_send_button: 'Passwort erhalten'
request_password: 'Neues anfordern.'
team_sign_in: 'Team Anmeldung'
marketing: 'Dies scheint Werbung zu sein'
moderate_all_streams: 'Moderate comments on All Stories'
moderate_this_stream: 'Diesen Kommentarbereich moderieren'
modqueue:
account: Konto-Markierungen
actions: Aktionen
all: Alle
all_streams: 'Alle Kommentarbereiche'
always_premod_user_actions: 'Nutzer immer vormoderieren'
approve: Freigeben
approved: Freigegeben
ban_user: Sperren
ban_user_actions: 'Nutzer sperren'
billion: Mrd
close: Schließen
empty_queue: 'Keine weiteren Kommentare zu moderieren! Arbeite nicht zu viel, gönn dir eine Pause!'
@@ -387,6 +433,7 @@ de:
show_shortcuts: 'Tastaturkürzel anzeigen'
singleview: Zen-Modus
sort: Sortieren
suspend: 'Nutzer vorübergehend sperren'
system_withheld: 'System Withheld'
thismenu: 'Dieses Menü öffnen'
thousand: T
@@ -424,6 +471,13 @@ de:
username: Nutzername
write_message: 'Nachricht schreiben'
yes_suspend: 'Ja, vorübergehend sperren'
reject_username_dialog:
cancel: Abbrechen
description: 'Sagen Sie uns warun der Name nicht Ok ist?'
message: 'Grund für die Meldung (optional)'
reason: Begründung
reject_username: 'Nutzernamen ablehnen'
title: 'Nutzernamen ablehnen'
reply: Antworten
report: Melden
report_notif: 'Vielen Dank für Ihre Meldung. Unsere Moderatoren wurden informiert und werden sich in Kürze darum kümmern.'
@@ -452,11 +506,14 @@ de:
closed: Geschlossen
empty_result: 'Ihre Suche war ohne Ergebnisse. Versuchen Sie, Ihre Anfrage weiter zu fassen.'
filter_streams: 'Kommentarbereiche filtern'
most_recent_stories: 'Neueste Artikel'
newest: Neueste
no_results: 'Keine Ergebnisse'
oldest: Älteste
open: Offen
pubdate: Veröffentlichungsdatum
search: Suchen
search_results: Suchergebnisse
sort_by: 'Sortieren nach'
status: Status
stream_status: Status
@@ -484,24 +541,41 @@ de:
username_flags: 'Markierungen für diesen Nutzernamen'
user_detail:
all: Alle
always_premod: 'Nutzer immer vormoderieren'
always_premoded: 'Vormoderiert'
ban: 'Nutzer sperren'
banned: Gesperrt
email: E-Mail
id: ID
karma: Karma
karma_docs_link: 'https://docs.coralproject.net/talk/trust/#user-karma-score'
learn_more: 'Mehr erfahren'
member_since: 'Mitglied seit'
reject_rate: Ablehn-Rate
reject_username: 'Nutzernamen ablehnen'
rejected: Abgelehnte
remove_always_premod: 'Vormoderation entfernen'
remove_ban: 'Sperre aufheben'
remove_suspension: 'Vorübergehende Sperrung aufheben'
suspend: 'Nutzer vorübergehend sperren'
suspended: 'Vorübergehend gesperrt'
total_comments: 'Anzahl Kommentare'
unreliable: Unzuverlässig
user_history: Konto-Verlauf
user_karma_score: 'Nutzer Karma Wert'
username: Username
username_needs_approval: 'Nutzername muss akzetiert werden'
username_rejected: 'Nutzername abgelehnt'
user_history:
action: Aktion
always_premod_removed: 'Vormoderation entfernen'
ban_removed: 'Sperrung aufgehoben'
date: Datum
suspended: 'Vorübergehend gesperrt, {0}'
suspension_removed: 'Vorübergehende Sperrung aufgehoben'
system: System
taken_by: Durch
user_always_premoded: 'Nutzer vormoderiert'
user_banned: 'Nutzer gesperrt'
username_status: 'Nutzername {0}'
user_impersonating: 'Gibt sich für jemand anderen aus'
@@ -518,4 +592,3 @@ de:
view_conversation: 'Diskussion ansehen'
your_account_has_been_banned: 'Ihr Zugang wurde gesperrt.'
your_account_has_been_suspended: 'Ihr Zugang wurde vorübergehend gesperrt.'
your_username_has_been_rejected: 'Ihr Zugang wurde vorübergehend gesperrt, da Ihr Nutzername als unangemessen eingestuft wurde. Um Ihren Zugang wieder herzustellen, geben Sie bitte einen neuen Nutzernamen ein.'
+9 -4
View File
@@ -35,6 +35,9 @@ en:
flagged: flagged
undo_reject: Undo
view_context: 'View context'
edit_history: 'Edit history'
show_edit_history: 'Show edit history'
hide_edit_history: 'Hide edit history'
comment_box:
cancel: Cancel
characters_remaining: 'characters remaining'
@@ -124,6 +127,8 @@ en:
copy_and_paste: 'Copy and paste code below into your CMS to embed your comment box in your articles'
custom_css_url: 'Custom CSS URL'
custom_css_url_desc: 'URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external.'
custom_admin_css_url: 'Custom Admin Panel CSS URL'
custom_admin_css_url_desc: 'URL of a CSS stylesheet that will override default admin panel styles. Can be internal or external.'
days: Days
description: 'Change the comment settings on this story.'
disable_commenting_desc: 'Write a message that will be displayed while commenting is deactivated.'
@@ -219,7 +224,7 @@ en:
subject: 'Your account has been banned'
confirm:
confirm_email: 'Confirm Email'
has_been_requested: 'A email confirmation has been requested for the following account:'
has_been_requested: 'An email confirmation has been requested for the following account:'
if_you_did_not: 'If you did not request this, you can safely ignore this email.'
subject: 'Email Confirmation'
to_confirm: 'To confirm the account, please visit the following link:'
@@ -332,7 +337,7 @@ en:
button: Submit
error: 'Usernames can contain letters numbers and _ only'
label: 'New Username'
msg: 'Your account is currently suspended because your username has been deemed inappropriate. To restore your account please enter a new username. Please contact us if you have any questions.'
rejected: 'Your choice of username has been rejected because it is not in line with our username policies. Please submit another name for moderator approval.'
my_comments: 'My Comments'
my_profile: 'My profile'
new_count: 'View {0} more {1}'
@@ -380,7 +385,7 @@ en:
sign_in_message: 'Sign in to interact with your community.'
password: Password
reset_password_send_button: 'Retrieve Password'
request_passowrd: 'Request a new one.'
request_password: 'Request a new one.'
team_sign_in: 'Team sign in'
marketing: 'This looks like an ad/marketing'
moderate_all_streams: 'Moderate comments on All Stories'
@@ -588,4 +593,4 @@ en:
view_conversation: 'View Conversation'
your_account_has_been_banned: 'Your account has been banned.'
your_account_has_been_suspended: 'Your account has been temporarily suspended.'
your_username_has_been_rejected: 'Your account has been suspended because your username has been deemed inappropriate. To restore your account please enter a new username.'
your_username_has_been_rejected_not_in_line: 'Your choice of username has been rejected because it is not in line with our username policies. Please submit another name for moderator approval.'
+3 -2
View File
@@ -35,6 +35,9 @@ es:
flagged: Reportado
undo_reject: Deshacer
view_context: 'Ver contexto'
edit_history: 'Historial de ediciones'
show_edit_history: 'Mostrar historial de ediciones'
hide_edit_history: 'Ocultar historial de ediciones'
comment_box:
cancel: Cancelar
characters_remaining: 'caracteres restantes'
@@ -306,7 +309,6 @@ es:
button: Enviar
error: 'Nombres de usuarios pueden solamente incluir letras, números y _'
label: 'Nuevo Nombre'
msg: 'Tu cuenta está suspendida porque tu nombre de usuario ha sido considerado no apropiado para el espacio. Para recuperar la cuenta, por favor ingresar un nuevo nombre de usuario. Contáctanos si tienes alguna pregunta.'
my_comments: 'Mis Comentarios'
my_profile: 'Mi perfil'
new_count: 'Ver {0} más {1} '
@@ -539,4 +541,3 @@ es:
view_conversation: 'Ver Conversación'
your_account_has_been_banned: 'Su cuenta ha sido prohibida.'
your_account_has_been_suspended: 'Su cuenta ha sido suspendida.'
your_username_has_been_rejected: 'Su cuenta ha sido suspendida porque tu nombre de usuario ha sido considerado no apropiado para el espacio. Para recuperar la cuenta, por favor ingresar un nuevo nombre de usuario.'
-2
View File
@@ -269,7 +269,6 @@ fi_FI:
button: Lähetä
error: 'Käyttäjänimissä sallittuja merkkejä ovat ainoastaan kirjaimet, numerot, sekä alaviiva'
label: 'Uusi käyttäjänimi'
msg: 'Tilisi on suljettu väliaikaisesti, koska käyttäjänimi on todettu sopimattomaksi. Vaihda käyttäjänimi, jos haluat jatkaa tilin käyttöä. Ole meihin yhteydessä, jos sinulla on aiheesta kysyttävää.'
my_comments: Kommenttini
my_profile: Profiilini
new_count: 'Näytä {0} lisää {1}'
@@ -466,4 +465,3 @@ fi_FI:
view_conversation: 'Näytä keskustelu'
your_account_has_been_banned: 'Tilillesi on asetettu kirjoituskielto.'
your_account_has_been_suspended: 'Tilisi on väliaikasesti suljettu.'
your_username_has_been_rejected: 'Tilisi on suljettu, koska käyttäjänimesi on epäsopiva. Vaihda käyttäjänimeä jatkaaksesi tilin käyttöä.'
-2
View File
@@ -264,7 +264,6 @@ fr:
button: Soumettre
error: 'Les noms d''utilisateur ne peuvent contenir que des chiffres, des lettres et "_"'
label: 'Nouveau nom d''utilisateur'
msg: 'Votre compte est actuellement suspendu car votre nom d''utilisateur a été jugé inapproprié. Pour restaurer votre compte, entrez un nouveau nom d''utilisateur. Contactez-nous si vous avez des questions.'
my_comments: 'Mes commentaires'
my_profile: 'Mon profil'
new_count: 'Voir {0} plus {1}'
@@ -462,4 +461,3 @@ fr:
view_conversation: 'Afficher la conversation'
your_account_has_been_banned: 'Votre compte a été banni.'
your_account_has_been_suspended: 'Votre compte a été temporairement suspendu.'
your_username_has_been_rejected: 'Votre compte a été suspendu en raison de votre nom dutilisateur jugé inapproprié. Veuillez saisir un nouveau nom dutilisateur pour restaurer votre compte.'
-2
View File
@@ -1,7 +1,6 @@
he:
your_account_has_been_suspended: החשבון שלך הושעה באופן זמני.
your_account_has_been_banned: החשבון שלך הורחק.
your_username_has_been_rejected: החשבון שלך הושעה מפני ששם המשתמש שלך נחשב לא הולם. כדי לשחזר את החשבון שלך, הזן שם משתמש חדש.
embed_comments_tab: תגובות
bandialog:
are_you_sure: "האם אתה בטוח שאתה רוצה לאסור {0}?"
@@ -260,7 +259,6 @@ he:
button: שלח
error: "שמות משתמש יכולים להכיל מספרי אותיות ו- _ בלבד"
label: "שם משתמש חדש"
msg: "החשבון שלך מושעה כרגע מפני ששם המשתמש שלך נחשב לא הולם. כדי לשחזר את החשבון שלך, הזן שם משתמש חדש. אנא צור איתנו קשר אם יש לך שאלות."
changed_name:
msg: "שינוי שם המשתמש שלך נמצא בבדיקה על ידי צוות הניהול שלנו."
my_comments: "התגובות שלי"
+1 -3
View File
@@ -321,7 +321,6 @@ it:
button: Invia
error: 'L''username può contenere solo lettere numeri o _'
label: 'Nuovo username'
msg: 'Il tuo account è attualmente sospeso perchè il tuo username è considerato inappropriato. Per ripristinare il tuo account inserisci un nuovo username. Contattaci se hai qualche domanda.'
my_comments: 'I miei commenti'
my_profile: 'Il mio profilo'
new_count: 'Visualizza {0} e {1}'
@@ -369,7 +368,7 @@ it:
sign_in_message: 'Accedi per interagire con la comunità.'
password: Password
reset_password_send_button: 'Reimposta Password'
request_passowrd: 'Richiedi una nuova password.'
request_password: 'Richiedi una nuova password.'
team_sign_in: 'Accesso Team'
marketing: 'Sembra si tratti di pubblicità/marketing'
moderate_all_streams: 'Modera i commenti in Tutte le storie'
@@ -571,4 +570,3 @@ it:
view_conversation: 'Visualizza conversazione'
your_account_has_been_banned: 'Il tuo account è stato bloccato.'
your_account_has_been_suspended: 'Il tuo account è stato temporaneamente sospeso.'
your_username_has_been_rejected: 'Il tuo account è stato sospeso perchè il tuo username è ritenuto inappropriato. Per ripristinare il tuo account inserisci un nuovo username.'
+1 -3
View File
@@ -324,7 +324,6 @@ nl_NL:
button: Versturen
error: "Gebruikersnamen kunnen alleen cijfers, letters en _ bevatten."
label: "Nieuwe gebruikersnaam"
msg: "Je account is momenteel geschorst omdat je gebruikersnaam ongepast is bevonden. Om je account te herstellen, moet je je gebruikersnaam herstellen. Neem contact met ons op wanneer je vragen hebt."
my_comments: "Mijn reacties"
my_profile: "Mijn profiel"
new_count: "Bekijk {0} meer {1}"
@@ -372,7 +371,7 @@ nl_NL:
sign_in_message: "Log in om te communiceren met je community"
password: Wachtwoord
reset_password_send_button: "Wachtwoord ophalen"
request_passowrd: "Vraag een nieuwe aan."
request_password: "Vraag een nieuwe aan."
team_sign_in: "Team login"
marketing: "Dit lijkt op een advertentie/marketing"
moderate_all_streams: "Modereer reacties op alle artikelen"
@@ -574,4 +573,3 @@ nl_NL:
view_conversation: "Bekijk conversatie"
your_account_has_been_banned: "Je account is verbannen."
your_account_has_been_suspended: "Je account is tijdelijk geschorst."
your_username_has_been_rejected: "Je account is geschorst omdat we je gebruikersnaam ongepast vinden. Verander je gebruikersnaam om hem weer te activeren."
+1 -3
View File
@@ -285,7 +285,6 @@ pt_BR:
button: Enviar
error: 'Nomes de usuários podem conter números de letras e _ somente'
label: 'Novo usuário'
msg: 'Sua conta está suspensa porque seu nome de usuário foi considerado inapropriado. Para restaurar sua conta, insira um novo nome de usuário. Entre em contato conosco se você tiver alguma dúvida.'
my_comments: 'Meus comentários'
my_profile: 'Meu perfil'
new_count: 'Ver {0} {1}'
@@ -333,7 +332,7 @@ pt_BR:
sign_in_message: 'Entre para interagir com a comunidade.'
password: Senha
reset_password_send_button: 'Recuperar Senha'
request_passowrd: 'Recupere-a clicando aqui.'
request_password: 'Recupere-a clicando aqui.'
team_sign_in: 'Team sign in'
marketing: 'Isso parece um anúncio/marketing'
moderate_all_streams: 'Comentários moderados em todas as matérias'
@@ -522,4 +521,3 @@ pt_BR:
view_conversation: 'Ver conversa'
your_account_has_been_banned: 'Sua conta foi banida.'
your_account_has_been_suspended: 'Sua conta foi temporariamente suspensa.'
your_username_has_been_rejected: 'Sua conta foi rejeitada porque seu nome de usuário foi considerado inapropriado. Para restaurar sua conta, insira um novo nome de usuário.'
+1 -3
View File
@@ -323,7 +323,6 @@ sr:
button: 'Podnesi'
error: 'Došlo je do greške'
label: 'Novo korisničko ime'
msg: 'Izmjena vašeg korisničkog imena prolazi provjeru našeg tima moderatora'
my_comments: 'Moji komentari'
my_profile: 'Moj profil'
new_count: 'Vidi još {0} / {1}'
@@ -371,7 +370,7 @@ sr:
sign_in_message: 'Prijavi se kako bi komunikacirao sa zajednicom'
password: 'Šifra mora sadržati minimum 8 karaktera'
reset_password_send_button: 'povrati šifru'
request_passowrd: 'Zahtijevaj novu šifru'
request_password: 'Zahtijevaj novu šifru'
team_sign_in: 'Prijava tima'
marketing: 'Ovo izgleda kao reklama/marketing'
moderate_all_streams: 'Moderiraj komentare na svim pričama'
@@ -573,4 +572,3 @@ sr:
view_conversation: 'Vidi razgovor'
your_account_has_been_banned: 'Vaš nalog je banovan'
your_account_has_been_suspended: 'Vaš nalog je privremeno suspendovan'
your_username_has_been_rejected: 'Vaš nalog je suspendovan jer je korisničko ime označeno kao neprikladno. Da povratite nalog, unesite novo korisničko ime'
-2
View File
@@ -232,7 +232,6 @@ zh_CN:
button: 提交
error: 用户名只能包含字母、数字跟下划线
label: 新用户名
msg: 由于您的用户名不当,您的帐号目前被暂停使用。如要恢复您的帐户,请输入一个新的用户名。如有任何疑问,请与我们联系。
my_comments: 我的评论
my_profile: 我的资料
new_count: '查看 {0} 更多 {1}'
@@ -411,4 +410,3 @@ zh_CN:
view_conversation: 查看对话
your_account_has_been_banned: 您的帐户已被禁用。
your_account_has_been_suspended: 您的帐户已被暂时停用。
your_username_has_been_rejected: 由于您使用了不恰当的用户名,您的帐户已被暂停使用。若需恢复帐户,请输入一个新的用户名
-2
View File
@@ -228,7 +228,6 @@ zh_TW:
button: 提交
error: 用戶名只能包含字母、數字和下劃線。
label: 新用戶名
msg: 由於您的用戶名不當,您的帳號目前已被暫停使用。如要恢復您的帳戶,請輸入一個新的用戶名。如有任何疑問,請與我們聯繫。
my_comments: 我的評論
my_profile: 我的概況
new_count: '查看{0}更多{1}'
@@ -407,4 +406,3 @@ zh_TW:
view_conversation: 查看對話
your_account_has_been_banned: 您的賬戶已被禁用。
your_account_has_been_suspended: 您的賬戶已被暫停使用。
your_username_has_been_rejected: 由於您使用了不恰當的用戶名,您的帳戶已被暫停使用。若需恢復帳戶,請輸入一個新的用戶名。
+11 -2
View File
@@ -12,6 +12,8 @@ const {
STATIC_ORIGIN,
} = require('../url');
const { PORT } = require('../config');
const { RECAPTCHA_PUBLIC, WEBSOCKET_LIVE_URI } = require('../config');
// Grab TALK_CLIENT_* environment variables.
@@ -42,6 +44,7 @@ const TEMPLATE_LOCALS = {
MOUNT_PATH,
STATIC_URL,
TALK_CLIENT_ENV,
PORT,
data: TALK_CLIENT_ENV,
};
@@ -96,12 +99,18 @@ const createResolveFactory = (() => {
module.exports = async (req, res, next) => {
try {
// Attach the custom css url and organization name.
const { customCssUrl, organizationName } = await SettingsService.select(
// Attach the custom css urls and organization name.
const {
customCssUrl,
customAdminCssUrl,
organizationName,
} = await SettingsService.select(
'customCssUrl',
'customAdminCssUrl',
'organizationName'
);
res.locals.customCssUrl = customCssUrl;
res.locals.customAdminCssUrl = customAdminCssUrl;
res.locals.organizationName = organizationName;
} catch (err) {
console.warn(err);
+3
View File
@@ -41,4 +41,7 @@ const Action = new Schema(
}
);
// Indexes for listing users actions.
Action.index({ user_id: 1, item_type: 1 }, { background: true });
module.exports = Action;
+4
View File
@@ -28,6 +28,10 @@ const Setting = new Schema(
type: String,
default: '',
},
customAdminCssUrl: {
type: String,
default: '',
},
infoBoxContent: {
type: String,
default: '',
+3
View File
@@ -0,0 +1,3 @@
[build]
command = "yarn generate-introspection && cd docs && yarn build"
publish = "docs/public"
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "talk",
"version": "4.8.1",
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
"version": "4.11.2",
"description": "A better commenting experience from Vox Media.",
"main": "app.js",
"private": true,
"scripts": {
+1 -1
View File
@@ -1,5 +1,5 @@
module.exports = {
SUBSCRIBE_COMMENT_ACCEPTED: 'SUBSCRIBE_COMMENT_ACCEPTED',
SUBSCRIBE_ALL_COMMENT_ACCEPTED: 'SUBSCRIBE_ALL_COMMENT_ACCEPTED',
SUBSCRIBE_COMMENT_REJECTED: 'SUBSCRIBE_COMMENT_REJECTED',
SUBSCRIBE_COMMENT_FLAGGED: 'SUBSCRIBE_COMMENT_FLAGGED',
SUBSCRIBE_COMMENT_RESET: 'SUBSCRIBE_COMMENT_RESET',
+1 -1
View File
@@ -4,7 +4,7 @@ const types = require('../constants');
module.exports = (user, perm) => {
switch (perm) {
case types.SUBSCRIBE_COMMENT_FLAGGED:
case types.SUBSCRIBE_COMMENT_ACCEPTED:
case types.SUBSCRIBE_ALL_COMMENT_ACCEPTED:
case types.SUBSCRIBE_COMMENT_REJECTED:
case types.SUBSCRIBE_COMMENT_RESET:
case types.SUBSCRIBE_ALL_COMMENT_EDITED:
@@ -21,17 +21,6 @@ function getReactionConfig(reaction) {
background: true,
}
);
Comment.collection.ensureIndex(
{
asset_id: 1,
[`action_counts.${sc(reaction)}`]: -1,
created_at: -1,
},
{
background: true,
}
);
}
const reactionPlural = pluralize(reaction);
+22 -6
View File
@@ -1,26 +1,42 @@
{
"server": [
"talk-plugin-featured-comments",
"talk-plugin-local-auth",
"talk-plugin-featured-comments",
"talk-plugin-respect",
"talk-plugin-rich-text",
"talk-plugin-profile-data",
"talk-plugin-respect"
"talk-plugin-notifications",
"talk-plugin-notifications-category-featured",
"talk-plugin-notifications-category-moderation-actions",
"talk-plugin-notifications-category-reply",
"talk-plugin-notifications-category-staff",
"talk-plugin-notifications-digest-hourly",
"talk-plugin-notifications-digest-daily"
],
"client": [
"talk-plugin-auth",
"talk-plugin-local-auth",
"talk-plugin-author-menu",
"talk-plugin-featured-comments",
"talk-plugin-flag-details",
"talk-plugin-ignore-user",
"talk-plugin-local-auth",
"talk-plugin-member-since",
"talk-plugin-moderation-actions",
"talk-plugin-permalink",
"talk-plugin-profile-data",
"talk-plugin-respect",
"talk-plugin-rich-text",
"talk-plugin-viewing-options",
"talk-plugin-sort-most-replied",
"talk-plugin-sort-most-respected",
"talk-plugin-sort-newest",
"talk-plugin-sort-oldest",
"talk-plugin-viewing-options"
"talk-plugin-profile-data",
"talk-plugin-notifications",
"talk-plugin-notifications-category-featured",
"talk-plugin-notifications-category-moderation-actions",
"talk-plugin-notifications-category-reply",
"talk-plugin-notifications-category-staff",
"talk-plugin-notifications-digest-hourly",
"talk-plugin-notifications-digest-daily",
"talk-plugin-ignore-user"
]
}
@@ -4,7 +4,6 @@ permalink: /plugin/talk-plugin-comment-content/
layout: plugin
plugin:
name: talk-plugin-comment-content
default: true
provides:
- Client
---
@@ -12,4 +11,4 @@ plugin:
Pluginizes the text of a comment to support custom treatment of this text. This
plugin currently parses the given text to see if it contains a link, and makes
them clickable using
[react-linkify](https://www.npmjs.com/package/react-linkify).
[react-linkify](https://www.npmjs.com/package/react-linkify).
+31 -12
View File
@@ -11,25 +11,44 @@ plugin:
- Client
---
Enables sign-in via Facebook via the server side passport middleware.
Enables sign-in via Facebook via the server side passport middleware. Requires creating and registering a login app with Facebook.
Configuration:
**Configuration:**
- `TALK_FACEBOOK_APP_ID` (**required**) - The Facebook App ID for your Facebook
Login enabled app. You can learn more about getting a Facebook App ID at the
[Facebook Developers Portal](https://developers.facebook.com) or by visiting
the [Creating an App ID](https://developers.facebook.com/docs/apps/register)
guide. This is only required while the `talk-plugin-facebook-auth` plugin is
Login enabled app. This is only required while the `talk-plugin-facebook-auth` plugin is
enabled.
- `TALK_FACEBOOK_APP_SECRET` (**required**) - The Facebook App Secret for your
Facebook Login enabled app. You can learn more about getting a Facebook App
Secret at the [Facebook Developers Portal](https://developers.facebook.com)
or by visiting the
[Creating an App ID](https://developers.facebook.com/docs/apps/register)
guide. This is only required while the `talk-plugin-facebook-auth` plugin is
Facebook Login enabled app. This is only required while the `talk-plugin-facebook-auth` plugin is
enabled.
You can learn more about getting a Facebook App ID at the
[Facebook Developers Portal](https://developers.facebook.com) or by visiting
their [Creating an App ID](https://developers.facebook.com/docs/apps/register)
guide.
_NOTE: FabceBook auth requires your site to use `https` (SSL) not `http`. If your site is not `https` you can not use this plugin!_
**Setting up your Facebook app:**
* Go to [Facebook Developers Portal](https://developers.facebook.com) and click on Getting Started or My Apps
* Create a new app > set the App Name and Email to create an app id
* Confirm that you are not a robot, then configure the app as follows:
* In Settings > Basic:
* add app domains (your Talk domain)
* add a link to your privacy policy
* add a link to your terms of service
* In Settings > Advanced:
* disable "Require App Secret"
* Add a "Product" (Under "Products" click + to add a Product):
* Setup "Facebook Login"
* choose `www`
* enter your Talk domain url
* click _Next_ several times to get through the add code steps (You do not need to modify any code, the plugin takes care of this part for you.)
* Under Product Settings:
* set Valid OAuth Redirect URIs to your callback url (Use your Talk domain with this endpoint: `/api/v1/auth/facebook/callback`)
* Locate your App Id and App Secret, set these as config vars on your instance of Talk
* Toggle the "Live" button on the top bar to make app live
_NOTE: Facebook auth requires your site to use `https` (SSL) not `http`. If your site is not `https` you can not use this plugin!_
## GDPR Compliance
@@ -14,6 +14,17 @@ Enables the ability for Moderators to feature and un-feature comments via the
Stream and the Admin. Featured comments show in a first-place tab on the Stream
if there are any featured comments on that story.
When paired with the [talk-plugin-moderator-actions](/talk/plugin/talk-plugin-moderator-actions)
When paired with the [talk-plugin-moderation-actions](/talk/plugin/talk-plugin-moderation-actions)
plugin, moderators will have the option of featuring comments from the comment
stream.
stream.
## Default Comments Tab
You can also change the default tab when rendering the comment stream to the
featured tab so when a user reaches a comment stream, they will see the featured
comments first by setting the [TALK_DEFAULT_STREAM_TAB](/talk/advanced-configuration/#talk-default-stream-tab)
variable to `talk-plugin-featured-comments`.
This is a **Build Variable** and must be consumed during build. If using the
[Docker-onbuild](/talk/installation-from-docker/#onbuild)
image you can specify it with `--build-arg TALK_DEFAULT_STREAM_TAB=talk-plugin-featured-comments`.
@@ -42,19 +42,10 @@
text-align: left;
letter-spacing: 0.1px;
margin: 0;
quotes: '\201c' '\201d';
margin-bottom: 10px;
word-wrap: break-word;
}
.quote:before {
content: open-quote;
}
.quote:after {
content: close-quote;
}
.footer {
display: flex;
}
@@ -38,13 +38,15 @@
color: #484747;
display: inline-block;
margin: 0;
padding: 0;
padding: 0 16px 0 0;
font-size: 1.02em;
margin-left: 6px;
letter-spacing: 0.2px;
vertical-align: middle;
margin-bottom: 2px;
line-height: 22px;
box-sizing: border-box;
white-space: pre-wrap;
}
.icon {
@@ -36,17 +36,32 @@
}
.button {
color: #787D80;
background-color: #3498db;
border-radius: 2px;
background-color: transparent;
height: 30px;
font-size: 0.9em;
width: 100%;
display: inline-block;
text-align: center;
font-size: 1em;
background-color: #3498DB;
border: 0;
color: white;
display: inline-block;
font-size: 1em;
height: 30px;
text-align: center;
width: 100%;
&:hover {
cursor: pointer;
}
}
.cancel {
background-color: #e0e0e0;
border-radius: 2px;
border: 0;
color: #4f5c67;
display: inline-block;
font-size: 1em;
height: 30px;
margin-top: 5px;
text-align: center;
width: 100%;
&:hover {
cursor: pointer;
@@ -14,10 +14,10 @@ import {
} from 'coral-framework/lib/validation';
import { Form, Field } from 'react-final-form';
const AddEmailContent = ({ onSubmit }) => (
const AddEmailContent = ({ onSubmit, onCancel }) => (
<div>
<h4 className={styles.title}>
{t('talk-plugin-local-auth.add_email.content.title')}
{t('talk-plugin-local-auth.add_email.title')}
</h4>
<p className={styles.description}>
{t('talk-plugin-local-auth.add_email.content.description')}
@@ -113,6 +113,14 @@ const AddEmailContent = ({ onSubmit }) => (
<button className={styles.button} disabled={submitting}>
{t('talk-plugin-local-auth.add_email.add_email_address')}
</button>
<button
onClick={onCancel}
className={styles.cancel}
disabled={submitting}
type="button"
>
{t('talk-plugin-local-auth.add_email.cancel')}
</button>
</div>
</form>
)}
@@ -122,6 +130,7 @@ const AddEmailContent = ({ onSubmit }) => (
AddEmailContent.propTypes = {
onSubmit: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
};
export default AddEmailContent;
@@ -0,0 +1,6 @@
.dialog {
border: none;
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
width: 400px;
top: 10px;
}

Some files were not shown because too many files have changed in this diff Show More