Compare commits

..
517 Commits
Author SHA1 Message Date
Wyatt Johnson 637605a002 fix: added trust proxy config (#2751) 2019-12-12 22:50:00 +00:00
Nick Funk 5580e14bf3 Create Slack integration documentation (#2714)
Explains how to create a Slack App and hook it into
Coral as well as how to re-find your webhook URLs
if you have lost them.

CORL-645
2019-12-03 16:06:52 -05:00
Wyatt Johnson 109d9e93f5 docs: added doc updates for Node 12 (#2730) 2019-11-22 23:40:11 +00:00
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
Kim Gardner 049357a09c Bump version 4.8.1 (#2218) 2019-03-07 16:00:18 +00:00
Wyatt Johnson 5e7fe7cf5a fix: upgrade passport-google-oauth2 (#2217)
- Fixes #2215
2019-03-07 01:12:08 +00:00
Kim Gardner 6243fcba91 Merge pull request #2212 from vittoboa/italian-translation
Italian translation
2019-03-04 11:53:41 -05:00
vittoboa 805825e5d2 Fix typo 2019-03-02 22:42:13 +01:00
vittoboa a40e721a68 Fix typo 2019-03-02 22:38:16 +01:00
vittoboa 1dd8d0a706 Add italian translation for plugins 2019-03-02 22:33:52 +01:00
Leandro da1a8bb539 Remove duplicate placeholder proptype. (#2211) 2019-03-01 19:34:24 +01:00
Kim Gardner 18add4cdea Merge pull request #2210 from sanoma/feature/talk_470_dutch_translations
Feature/talk 470 dutch translations
2019-02-28 12:28:17 -05:00
Kim Gardner de5de26c82 Merge branch 'master' into feature/talk_470_dutch_translations 2019-02-28 11:09:19 -05:00
Besma 934e4b4754 Replace email with e-mail for Dutch translation consistency 2019-02-27 10:11:39 +01:00
Kim Gardner 049658508c Merge pull request #2206 from coralproject/bump-version-4-8-0
v4.8.0
2019-02-26 17:03:38 -05:00
Kim Gardner f0ead66f68 v4.8.0 2019-02-26 14:30:35 -05:00
David Burke 14f1343930 Additional Replies Increment (#2203)
* added ADDTL_REPLIES_ON_LOAD_MORE variable

* feat: added docs and onbuild ref
2019-02-26 16:18:55 +00:00
Besma b21845b963 Update translations 2019-02-26 09:39:54 +01:00
Kim Gardner 0ff20bed5e Merge pull request #2201 from mazzi/fix/issue-2141
Suspended vs. Banned  translations and typos in Spanish fixed
2019-02-25 17:33:27 -05:00
Besma 0bb2d4cbca Add Dutch translation 2019-02-25 16:18:33 +01:00
Lautaro Mazzitelli 927a34a579 ci: quotation not needed 2019-02-22 17:37:38 -03:00
Lautaro Mazzitelli 75f2f05534 fix: Suspended vs. Banned. Fixed in spanish translations. 2019-02-22 17:21:56 -03:00
Lautaro Mazzitelli 2998db46ad fix: three different typos were fixed on spanish translation file. 2019-02-22 16:21:02 -03:00
Gerardo Gálvez 8163b52301 Flag users as "Always premoderate" (#2145)
* Flag users as "Always premoderate"

Status can be set using the action dropdown in the People tab and in User Details.
Users flagged as "Always premoderate" will have their comments sent to the premod queue.
Users can be filtered with the Always Premoderate status.
Include Always Premoderate status changes in User History.
Add spanish translations.

* Reorder CSS as per cvle's suggestion

* Address second comment
2019-02-13 20:08:22 +01:00
Kim Gardner 19b425dc1a Merge pull request #2182 from mkonikov/snackbar-improvements
Snackbar improvements
2019-02-13 13:57:35 -05:00
Kim Gardner be81ced2f5 Merge branch 'master' into snackbar-improvements 2019-02-13 11:42:57 -05:00
Kim Gardner f6e5f24722 Merge pull request #2189 from javierbarriosafonso/master
Spanish (es) translations for talk notifications plugins
2019-02-13 11:42:48 -05:00
Kim Gardner 2c1dc9c118 Merge branch 'master' into master 2019-02-13 11:34:21 -05:00
Steven 657bf388cf fix: rename devToolsExtension (#2192)
Note that starting from v2.7, window.devToolsExtension was renamed to window.__REDUX_DEVTOOLS_EXTENSION__ / window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__.
2019-02-12 17:12:29 +01:00
Javier Barrios b3412a46cf Spanish (es) translations for talk notifications plugins 2019-02-11 14:08:31 -03:00
Mendel Konikov c6ee54c2f5 Rename classes to match id 2019-02-08 10:46:32 -05:00
Mendel Konikov 174a80c377 Reorder style as previous 2019-02-08 10:34:40 -05:00
Mendel Konikov 17a05059d9 Add working close button and style 2019-02-08 10:31:57 -05:00
Mendel Konikov 8c53c25a89 Merge branch 'master' of github.com:mkonikov/talk into snackbar-improvements 2019-02-08 09:15:07 -05:00
Mendel Konikov 1928a7b02e Create Moderation actions notification plugin (#2175)
* Create Moderation actions plugin

* Clarify env var in readme

* Reword log message

* Update with suggestion from Wyatt

* Whitelist notification types

* Update messaging

* Rename unpublished to pending
2019-02-07 15:20:44 +01:00
immber 5173af2a27 talk-plugin-toxic-comments - ADD MODEL VARIABLE TO TOXIC COMMENTS (#2178)
* updated readme

* added config var TALK_PERSPECTIVE_MODEL

* fixed duplication on readme

* fixed link on readme

* fix: applied some changes
2019-02-07 00:22:10 +00:00
Kim Gardner 9077f31872 Merge pull request #2179 from mkonikov/patch-1
Break word on long description
2019-02-06 18:14:51 -05:00
Mendel Konikov 51e8035533 Break word on long description
Also fix font size
2019-02-06 11:40:51 -05:00
Wyatt Johnson 94786c2711 fix: repairs poor performance for assets (#2174)
- fixes #2158
2019-02-05 22:06:05 +01:00
Nacho Toledo 456abaf862 Update appropriate <section> class in Profile section (#2170)
* Update appropriate <section> class in user profile section

Download my comment history has `talk-plugin-ignore-user-section` class. Should be `talk-plugin-profile-data--download-my-comment-history` (couldn't figure out the proper naming convention used).

* Fix linting error

* Fix lint second try
2019-02-05 16:49:25 +01:00
Kim Gardner fe9f92d3fc Merge pull request #2165 from mkonikov/hebrew
Add Hebrew Language Support
2019-02-04 17:37:46 -05:00
Kim Gardner a49fbf2d05 Yml fix 2019-02-04 17:16:15 -05:00
Kim Gardner 51bb158446 Merge branch 'master' into hebrew 2019-02-04 15:52:14 -05:00
Kim Gardner d601bd1466 Merge pull request #2177 from coralproject/remove-pt-link
Update Readme
2019-02-04 15:51:53 -05:00
immber 8eb8294987 Merge branch 'master' into remove-pt-link 2019-02-04 12:08:11 -08:00
Kim Gardner 99332323a8 Merge branch 'master' into hebrew 2019-02-04 14:52:29 -05:00
Kim Gardner 1a23cc1fa7 Merge pull request #2159 from mkonikov/fix-ios-copy
Fix Permalink copy on iOS
2019-02-04 19:48:38 +00:00
Kim Gardner 798f55ed79 Merge branch 'master' into hebrew 2019-02-04 19:47:24 +00:00
Kim Gardner 6897c0e18d Merge branch 'master' into fix-ios-copy 2019-02-04 19:34:31 +00:00
Kim Gardner 9d7b9a91cd Merge pull request #2153 from ljubar/master
sr_ME translations (Montenegrin version of Serbian language, Latin letters)
2019-02-04 19:32:56 +00:00
Kim Gardner 51a62b8c26 Merge branch 'master' into master 2019-02-04 19:28:23 +00:00
Kim Gardner f11a51ebfc Update Readme 2019-02-04 12:56:50 -05:00
Kim Gardner dbdf38af73 Merge branch 'master' into hebrew 2019-02-04 17:30:32 +00:00
Mendel Konikov abc1664ef7 WIP: Snackbar close 2019-01-31 09:59:58 -05:00
immber c16514502c Update recommended release version to link (#2168)
* Update recommended release version to 4.7

* update release link on readme!
2019-01-29 20:41:26 +00:00
Mendel Konikov 9d3e3ce45d Add additional Hebrew translations 2019-01-27 21:59:38 -05:00
Mendel Konikov f6bfef3fb5 Sort Translations ABC, Add missing Hebrew 2019-01-27 21:31:51 -05:00
Mendel Konikov 9b0173e6bf Add few more translations 2019-01-26 21:41:02 -05:00
Mendel Konikov 43219eed4b Replace quote marks 2019-01-24 22:32:50 -05:00
Mendel Konikov 9300359ed1 Merge branch 'master' of github.com:coralproject/talk into hebrew 2019-01-24 22:31:15 -05:00
Mendel Konikov 80c3367259 Merge branch 'master' into fix-ios-copy 2019-01-24 13:58:22 -05:00
Andrew Losowsky ef731f719d added Vox Media link (#2161) 2019-01-24 18:11:04 +00:00
Kim Gardner 9c09509a08 Merge pull request #2163 from mkonikov/fix-typo
Fix typo for Asset URL translation
2019-01-23 20:10:58 +00:00
Mendel Konikov 313310a097 Fix typo 2019-01-23 00:55:23 -05:00
Mendel Konikov fa42c028df Fix quotes 2019-01-23 00:45:31 -05:00
Mendel Konikov 905e43e745 Remove deleted plugin 2019-01-23 00:41:29 -05:00
Mendel Konikov 6cc0c60ade Add Final hebrew translations 2019-01-23 00:38:53 -05:00
Mendel Konikov ca44588095 Add Hebrew profile translations - server 2019-01-22 23:38:10 -05:00
Mendel Konikov 9a49044030 Add client profile translations 2019-01-22 23:30:39 -05:00
Mendel Konikov 02963dc5ac Fix translation 2019-01-22 23:18:18 -05:00
Mendel Konikov 5ad90e00cb Add Hebrew to profile plugin 2019-01-22 23:16:09 -05:00
Mendel Konikov 1dfc4b83c7 Remove unnecessary diffs 2019-01-22 23:11:25 -05:00
Mendel Konikov d522a72d42 Add additional hebrew 2019-01-22 23:09:30 -05:00
Mendel Konikov d1a22f9dc8 Remove merge comments 2019-01-22 22:10:53 -05:00
Mendel Konikov 5ac3910687 Fix lint 2019-01-22 22:08:29 -05:00
Mendel Konikov d007c2295e Fix Permalink copy on iOS 2019-01-21 17:15:16 -05:00
Kim Gardner 7ebcc4c793 Merge pull request #2157 from coralproject/docs-update
added native comments pdf
2019-01-18 19:41:19 +00:00
immber 582d45bb21 moved pdf to wp and updated copy 2019-01-17 09:56:55 -08:00
immber 6ef5c3a42f added link to sidebar 2019-01-16 14:29:35 -08:00
immber 00883a3739 added link to sidebar 2019-01-16 14:25:52 -08:00
immber d7fb0056be added native comments pdf 2019-01-16 14:16:04 -08:00
Kim Gardner ad55d715f6 Merge branch 'master' into master 2019-01-16 21:24:52 +00:00
Kim Gardner 0a855cd3d4 Merge pull request #2154 from coralproject/losowsky-patch-1
Fixed blog url
2019-01-16 21:24:18 +00:00
Andrew Losowsky c02d2b9b4e Fixed blog url 2019-01-16 10:46:49 -05:00
Kim Gardner 68255bec70 Merge branch 'master' into master 2019-01-16 14:44:51 +00:00
immber 28edd98ff1 Merge pull request #2151 from coralproject/docs-update
added TALK_CONCURRENCY to adv config
2019-01-14 13:53:12 -08:00
immber d1b5409e44 Merge branch 'master' into docs-update 2019-01-14 13:46:43 -08:00
Wyatt Johnsonandimmber df5e863cc4 updated default phrasing to be consistent
Co-Authored-By: immber <jessiekrushing@gmail.com>
2019-01-14 13:45:28 -08:00
immber acc83e27ce added TALK_CONCURRENCY to adv config 2019-01-14 10:37:36 -08:00
Kim Gardner ca13fde87d Merge pull request #2148 from coralproject/docs-update
Docs update
2019-01-11 15:46:35 +00:00
immber f69a9c826d removed link to community from faq 2019-01-10 17:04:22 -08:00
immber 76ef0f7d52 pulled community link from readme 2019-01-10 10:55:20 -08:00
immber 8f54c1d3c3 fixed md typo 2019-01-09 12:38:33 -08:00
immber 83379b58a7 em that can not be opted out of 2019-01-08 16:46:49 -08:00
immber b0b5807c8b added scaling talk 2019-01-08 16:39:06 -08:00
immber c98d227411 updated notifications page 2019-01-08 15:12:44 -08:00
immber 83819df922 added talk-plugin-comment-count to plugins directory 2019-01-08 13:27:08 -08:00
immber db5bf9a676 added twitter faq 2019-01-08 10:04:39 -08:00
immber ba31dbbdf9 added logout wait time 2019-01-08 09:53:46 -08:00
immber 8f633fbe78 added comment count to checklist 2019-01-04 17:20:49 -08:00
immber 886222260d fb requires ssl 2019-01-04 16:55:56 -08:00
immber 2c3589cdac added cert to auth section 2019-01-04 16:49:16 -08:00
Ljuba Rankovic ed953b5e7e Change translation 2018-12-24 15:41:05 +01:00
Ljuba Rankovic 79d1f059c2 fixes 2018-12-22 16:34:16 +01:00
Kim Gardner dd0601a801 Merge pull request #2135 from coralproject/concurrency
Concurrency
2018-12-19 20:53:30 +00:00
Wyatt Johnson 9f7d485871 Update package.json 2018-12-19 20:39:25 +00:00
Wyatt Johnson ec816636e6 feat: added concurrency features 2018-12-19 12:40:08 -07:00
Kim Gardner 08f579225b Bump version 4.6.11 (#2130) 2018-12-17 16:34:43 +00:00
Kim Gardner 46987b6303 Merge pull request #2122 from oafx/oafx/qbb-localization
Add localization for QuestionBoxBuilder
2018-12-17 15:09:31 +00:00
Kim Gardner c2d07ec79f Merge branch 'master' into oafx/qbb-localization 2018-12-17 14:43:32 +00:00
Kim Gardner a98f5ef980 Merge pull request #2128 from oafx/oafx/fix/moderate-commentcontainer-wordbreak
fix minor ui/css bug in moderation/comment component caused by long links
2018-12-17 14:42:54 +00:00
Christian Ruttorf d6515e23f8 fix ui/css bug in moderation/comment component caused by long words/links 2018-12-16 08:48:21 +01:00
Ljuba Rankovic 282db6847a locale sr added 2018-12-15 09:40:53 +01:00
immber a3db05845d Merge pull request #2126 from coralproject/docs-update
Add Documentation Redirect - asset management to cms integration
2018-12-14 13:06:59 -08:00
Wyatt Johnson c8fc419cec Update _redirects 2018-12-14 20:46:57 +00:00
immber 677c8a0828 Merge branch 'master' into docs-update 2018-12-14 12:06:22 -08:00
immber 08146fd979 added redirect 2018-12-14 12:04:28 -08:00
Kim Gardner ab92fe3fe8 Merge branch 'master' into oafx/qbb-localization 2018-12-14 18:18:33 +00:00
immber 95435f0793 Docs update (#2120)
* merging in typo fixes from docs (rtd) branch

* revert generateIntrospectionResult.json

* revert git ignore

* added asset mgmt to prelaunch checklist

* updated cusotm plugin docker onbuild instructions

* add google to auth options

* add google to auth options

* added yarn watch to from source

* added dev links to quickstart

* typo on following

* fixed typos

* fixing links

* added lazy render and updated lazy load

* added version note

* renamed asset management to cms-integration

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

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

* fix: fixed up some static headers

- bumped version

* fix: moved intersection observer out of chunk

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

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

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

* fix cli setup bug

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

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

* fix: added new env var to onbuild args

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

* revert generateIntrospectionResult.json

* revert git ignore

* added asset mgmt to prelaunch checklist

* updated cusotm plugin docker onbuild instructions

* add google to auth options

* add google to auth options

* added yarn watch to from source

* added dev links to quickstart

* typo on following

* fixed typos

* fixing links
2018-12-03 19:47:46 +00:00
immber 99777d6b49 fixing links 2018-12-03 11:07:40 -08:00
immber 6f6da1fa25 merging changes from master 2018-12-03 09:34:25 -08:00
immber 2fac304163 fixed typos 2018-12-03 09:33:03 -08:00
immber 5ca7c092db Merge branch 'master' into docs-update 2018-11-30 18:05:35 -08:00
immber cfae6b5374 typo on following 2018-11-30 18:04:20 -08:00
immber 3668a2cffa added dev links to quickstart 2018-11-30 17:56:20 -08:00
immber 92681f52aa added yarn watch to from source 2018-11-30 17:39:02 -08:00
immber 847ded5bb4 add google to auth options 2018-11-30 17:19:27 -08:00
immber 76c959f790 add google to auth options 2018-11-30 17:11:42 -08:00
immber 4659392ad3 updated cusotm plugin docker onbuild instructions 2018-11-30 15:10:00 -08:00
immber e9cec73966 added asset mgmt to prelaunch checklist 2018-11-30 14:44:35 -08:00
immber bac0ee9c3b revert git ignore 2018-11-28 14:00:07 -08:00
immber 5ae293af89 revert generateIntrospectionResult.json 2018-11-28 13:58:56 -08:00
immber cce464b82a merging in typo fixes from docs (rtd) branch 2018-11-28 13:56:31 -08:00
Kim Gardner 5d32c4ec03 Merge pull request #2097 from richardvenneman/nl-talk-plugins
Add/fix Dutch (nl_NL) translations for Talk plugins
2018-11-28 18:01:22 +00:00
Kim Gardner 50d4e2bb9b Merge branch 'master' into nl-talk-plugins 2018-11-27 21:08:22 +00:00
Richard Venneman debdd3eae4 Fix nl typo 2018-11-27 16:56:56 -04:00
Richard Venneman 286d9baddf Update Dutch (nl_NL) translations for talk-plugin-love 2018-11-27 16:56:49 -04:00
Richard Venneman 9c06a63afd Add Dutch (nl_NL) translations for talk notifications plugins 2018-11-27 16:55:32 -04:00
Richard Venneman f38e72c4df Add Dutch (nl_NL) translations for talk-plugin-google-auth 2018-11-27 16:32:36 -04:00
Richard Venneman e2fac4a2dc Fix and update talk-plugin-auth Dutch (nl_NL) translations 2018-11-27 16:30:42 -04:00
Richard Venneman ba0513fd6c Fix talk-plugin-member-since Dutch (nl_NL) translation
Member != User
2018-11-27 16:22:00 -04:00
Richard Venneman 8e29f0094c Add Dutch (nl_NL) translations for talk-plugin-facebook-auth 2018-11-27 16:20:39 -04:00
Kim Gardner f60c7e3ca6 Version 4.6.7 (#2096) 2018-11-27 20:20:00 +00:00
Richard Venneman b3c034d89d Add Dutch (nl_NL) translation for talk-plugin-local-auth 2018-11-27 16:16:34 -04:00
Kim Gardner be2657eb30 Merge pull request #2095 from richardvenneman/update-nl-translations
Update Dutch (nl_NL) translations
2018-11-27 19:39:16 +00:00
Richard Venneman fd32d2aa24 Merge branch 'master' into update-nl-translations 2018-11-27 15:29:05 -04:00
Richard Venneman 809b39ae2a Update Dutch (nl_NL) translations 2018-11-27 15:25:14 -04:00
Kim Gardner 6197981480 Merge pull request #2092 from coralproject/sp-trans-4
Fixed Sp translations
2018-11-27 18:39:51 +00:00
Kim Gardner eb58dd52f8 Fixed Sp translations 2018-11-26 15:25:44 +00:00
Kim Gardner f5c011f44b Merge pull request #2077 from coralproject/ie11-fixes
Fix IE11 issues in Admin
2018-11-16 12:59:04 -05:00
Chi Vinh Le 396ccbd6c2 fix: workaround flexbox bug in IE11 2018-11-15 23:51:28 +01:00
Chi Vinh Le 4fd61b3731 fix: polyfill missing forEach for NodeLists in IE11 2018-11-15 23:51:14 +01:00
Kim Gardner cbd795dc95 Merge pull request #2073 from coralproject/sp-trans-3
Add Spanish translations for blank comment history
2018-11-09 16:18:02 -05:00
Kim Gardner 65a6b484a9 Add Spanish translations for blank comment history 2018-11-09 10:00:53 -05:00
Kim Gardner 45c193b9bf Merge pull request #2069 from coralproject/wyattjoh-patch-1
Toxic Comments Fix
2018-11-08 11:53:41 -05:00
Wyatt Johnson ed037f8461 Merge branch 'master' into wyattjoh-patch-1 2018-11-08 16:00:46 +00:00
Kim Gardner 7bb0c73ee4 Merge pull request #2070 from coralproject/add-email-iframe-size
Fix add email dialog cutoff
2018-11-08 10:33:43 -05:00
Chi Vinh Le f13654b297 fix: lint 2018-11-08 16:04:16 +00:00
Chi Vinh Le 4a165c0972 fix: ensure iframe is big enough for add email dialog 2018-11-08 15:58:26 +00:00
Wyatt Johnson bbf034a595 Update ToxicDetail.js 2018-11-07 22:27:48 +00:00
Kim Gardner 9f6ddd3801 Version 4.6.6 (#2066) 2018-11-07 18:31:06 +00:00
Kim Gardner 34184d07eb Merge pull request #2065 from coralproject/button-trans-es
Fix button translation
2018-11-06 20:32:26 +00:00
Kim Gardner 057d083341 Merge branch 'master' into button-trans-es 2018-11-06 20:07:35 +00:00
Kim Gardner c5f107a168 Merge pull request #2067 from coralproject/perspective-feedback
Perspective Feedback
2018-11-06 20:05:32 +00:00
Wyatt Johnson 17a801d6d2 fix: removed url parsing 2018-11-06 13:02:20 -07:00
Kim Gardner 888a61f895 Merge branch 'master' into perspective-feedback 2018-11-06 19:56:31 +00:00
Wyatt Johnson 9edf1d7c41 feat: added perspective feedback 2018-11-06 12:39:16 -07:00
Kim Gardner b802fdd5eb Merge pull request #2061 from coralproject/display-avatar-left
Display avatar on the left
2018-11-06 19:31:16 +00:00
Kim Gardner 1a7b3fe903 Fix button translation 2018-11-06 13:50:31 -05:00
Kiwi cadbbd0b5c Merge branch 'master' into display-avatar-left 2018-11-02 00:09:03 +01:00
Chi Vinh Le b6101b880d fix: show avatar on the left 2018-11-02 00:01:46 +01:00
immber afb9fdcdc1 Merge pull request #2060 from coralproject/losowsky-patch-1
Updated text
2018-11-01 12:22:26 -07:00
Andrew Losowsky 53abb65da1 Updated text
- updated User Drawer information
- updated Shortcuts information
- changed the order
2018-11-01 15:08:08 -04:00
immber 7b5192d40c Merge pull request #2045 from coralproject/moderation-docs
updated mod queues documentation
2018-11-01 09:23:40 -07:00
Wyatt Johnson 8fdea8734a Merge branch 'master' into moderation-docs 2018-11-01 16:03:14 +00:00
Kim Gardner 588fbb0eb9 Merge pull request #2036 from tanhengyeow/cli-tools
Document CLI tools as many people don't know about this #1941
2018-10-30 18:29:08 +00:00
Kim Gardner a28024dfed Revert CSS from #1990 2018-10-30 18:18:35 +00:00
Kim Gardner 343eb3e8b6 Merge branch 'master' into cli-tools 2018-10-30 18:17:27 +00:00
Kim Gardner 80319f9aca Merge pull request #2047 from coralproject/sp-trans-1
Spanish translation tweak #1
2018-10-30 18:16:59 +00:00
Kim Gardner 33e1e5e729 Merge branch 'master' into sp-trans-1 2018-10-30 18:08:26 +00:00
Kim Gardner ea2b2bc2e3 Merge pull request #2032 from vittoboa/italian-translation
Add italian translation
2018-10-30 18:08:15 +00:00
Kim Gardner 6ca2f21bc2 Merge branch 'master' into italian-translation 2018-10-30 18:01:25 +00:00
Kim Gardner 0d9b795740 Merge pull request #2051 from coralproject/edit-link-trans
i18n support for Edit link
2018-10-30 18:00:54 +00:00
Kim Gardner 73482d68ac Merge branch 'master' into italian-translation 2018-10-30 17:55:43 +00:00
Wyatt Johnson e837dfc3c1 Merge branch 'master' into edit-link-trans 2018-10-30 17:54:37 +00:00
Kim Gardner 2cd9a71d62 i18n support for Edit link 2018-10-30 17:40:45 +00:00
Wyatt Johnson ad73fcd94b fix: fixed typo (#2049) 2018-10-30 17:31:32 +00:00
Wyatt Johnson 66145d6fd5 Merge branch 'master' into sp-trans-1 2018-10-30 17:06:44 +00:00
Wyatt Johnson 7d246bba76 Metadata Scraper Debugger (#2048)
* feat: added asset scrape debugger

* fix: added docs for scraper

* fix: added asset scraping to sidebar
2018-10-30 17:02:33 +00:00
Kim Gardner 0ce0d57a96 Spanish translation tweak #1 2018-10-30 15:45:26 +00:00
Kim Gardner 792bc713f2 Merge branch 'master' into italian-translation 2018-10-30 15:35:53 +00:00
Kim Gardner 399f5ef7ba Merge pull request #2046 from kant/patch-1
Typo on #77
2018-10-30 15:34:17 +00:00
Darío Hereñú 14c0945687 Typo on #77
* plus some other typos found (and fixed)
2018-10-30 11:01:23 -03:00
immber 52f2f48385 updated mod queues documentation 2018-10-29 16:05:26 -07:00
Andrew Losowsky e52605e26b removed Reliable/Unreliable flaggers (#2043)
We don't display this any more
2018-10-29 16:29:13 +00:00
Andrew Losowsky ae4d6e2424 Updated Tag text (#2042) 2018-10-29 16:12:17 +00:00
Kiwi 968d20e744 Merge pull request #2018 from coralproject/admin-improvements
Consistency Improvements
2018-10-26 20:28:19 +02:00
Kiwi 49d2416172 Merge branch 'master' into admin-improvements 2018-10-26 20:08:24 +02:00
Parikshit Hooda aac7cae6d6 typo fix : whih to which (#2039)
typo fix : whih to which
2018-10-26 17:22:19 +00:00
Kim Gardner f4fd843efe Merge pull request #1963 from twosmalltrees/1920
Update text and description in moderation configure view
2018-10-26 10:58:36 +01:00
Heng Yeow c4de69218c Update _config.yml 2018-10-26 13:55:30 +08:00
Heng Yeow 94ae029a4a Create 02-03-cli-tools.md 2018-10-26 13:54:37 +08:00
Kim Gardner 1dc1f91b72 Merge branch 'master' into 1920 2018-10-25 21:00:47 +01:00
Kiwi c98216cf3c Merge branch 'master' into admin-improvements 2018-10-25 21:58:16 +02:00
Kim Gardner 2c9f8586d3 Merge pull request #2035 from coralproject/fix-pointer-events
Fix pointer events in configure card
2018-10-25 20:55:03 +01:00
Chi Vinh Le 4ce5d97e3b Always have pointer events in configure cards 2018-10-25 21:42:18 +02:00
Wyatt Johnson 3ea2632187 Merge branch 'master' into admin-improvements 2018-10-25 17:57:24 +00:00
vittoboa c9b709ad43 Add italian translation 2018-10-25 09:34:33 +02:00
Kim Gardner dcd7145510 Update Spanish translations (#2019)
* Update translations

* More sp trans

* Add translation for Contains Link
2018-10-24 11:58:42 -06:00
Kim Gardner d3896ed97d Merge pull request #2016 from hovoodd/stream-status
Fixed stream status updating
2018-10-24 17:39:22 +01:00
Kim Gardner 47a3cba5f5 Merge branch 'master' into stream-status 2018-10-24 13:57:53 +01:00
Kim Gardner 0f1cabe126 Merge pull request #2017 from iSasFTW/master
Visible validation errors in the username change box
2018-10-24 13:56:52 +01:00
Saska Kolehmainen 5f62083c2f Merge branch 'master' into master 2018-10-23 20:53:52 +03:00
Wyatt Johnson 3cabe50a7b fix: strip out 0xAD from the comment body before testing for links 2018-10-23 11:06:31 -06:00
Kim Gardner c0efc33044 Merge branch 'master' into stream-status 2018-10-23 17:47:48 +01:00
Kim Gardner 9344c1bc80 Merge pull request #2015 from hovoodd/change-username
Added additional check for `usernameCanBeUpdated` condition in profile
2018-10-23 17:47:37 +01:00
Kim Gardner 1bfe0fbac1 Merge branch 'master' into change-username 2018-10-23 17:41:10 +01:00
Kim Gardner b3d704be76 Merge branch 'master' into 1920 2018-10-23 17:23:11 +01:00
Kim Gardner 3c100160fb Merge pull request #2014 from hovoodd/collapsible-list
Made Banned/Suspended Word Lists collapsible in Admin > Configure
2018-10-23 16:19:23 +01:00
Wyatt Johnson 49c7d2eeda fix: move priority up for token fetching 2018-10-22 12:25:20 -06:00
Wyatt Johnson cff9b29a36 fix: adjust perspective defaults when failure occurs 2018-10-22 12:22:01 -06:00
Wyatt Johnson 5a1f93975c feat: added support for access_token hash passthrough 2018-10-22 12:13:15 -06:00
Wyatt Johnson cd6da751a6 fix: added patch to reset form 2018-10-22 12:12:35 -06:00
Saska Kolehmainen 41610ccf57 Added error message to profile tab in embedded comments for when disallowed characters are added while changing the username. 2018-10-21 17:45:54 +03:00
hovoodd 607a59ad63 Fix stream status updating 2018-10-20 22:59:13 +03:00
hovoodd c0fea4821b Add additional check for usernameCanBeUpdated condition in profile 2018-10-20 22:05:32 +03:00
hovoodd 8432df15a1 Add collapsible prop to banned and suspended lists in admin 2018-10-20 19:07:56 +03:00
hovoodd 61486caa9e Make ConfigureCard collapsible 2018-10-20 19:01:01 +03:00
Harrison Reid 7f45f7e2e2 Add Code of Conduct Link 2018-10-20 08:43:13 +11:00
Kim Gardner 9e511817a8 Merge pull request #1990 from snorremd/master
Display avatar left of comments by default
2018-10-19 19:51:21 +01:00
Kim Gardner 4015e19040 Merge branch 'master' into master 2018-10-19 19:47:36 +01:00
Kim Gardner c1fcc82b62 Merge pull request #2006 from twosmalltrees/1939
Keep Question Box visible after stream is closed #1939
2018-10-19 19:47:10 +01:00
Kim Gardner 893c8d32b2 Merge branch 'master' into 1920 2018-10-19 19:07:59 +01:00
Kim Gardner 2dfadebd42 Merge branch 'master' into 1939 2018-10-19 18:45:46 +01:00
Kim Gardner a2d5af1372 Merge pull request #2007 from coralproject/base64-secret
Base64 Secret Encoding
2018-10-19 18:45:12 +01:00
Kim Gardner 7a5416a807 Merge branch 'master' into base64-secret 2018-10-19 18:39:36 +01:00
Kim Gardner 8c2c632f52 Merge pull request #2011 from coralproject/email-prefix
Email Prefix Fix
2018-10-19 18:39:02 +01:00
Wyatt Johnson fabec2c89b review: fix invalid substring 2018-10-19 16:28:13 +00:00
Wyatt Johnson 7f18603fd5 fix: fixes #2009 2018-10-19 10:05:05 -06:00
Kim Gardner 3a69e0cf96 Merge branch 'master' into base64-secret 2018-10-18 21:53:43 +01:00
Wyatt Johnson 7ad3d247b5 feat: added base64 secret encoding 2018-10-18 14:33:45 -06:00
Harrison Reid 130dc578b3 Refactor Stream component, move QuestionBox render logic into seperate fn 2018-10-18 21:25:36 +11:00
Harrison Reid fd7114ab2a Keep question box visible when comment stream is closed 2018-10-18 21:06:00 +11:00
immber 5e4bdf1f0c Perspective (#2001)
* perspective documentation update

* fixed typo
2018-10-16 20:25:11 +00:00
immber 71e1b69afe perspective documentation update (#1999) 2018-10-16 18:35:10 +00:00
Kim Gardner 6fa18f6c7c Merge branch 'master' into 1920 2018-10-15 16:52:37 +01:00
Kim Gardner bda9b55562 Merge branch 'master' into master 2018-10-15 16:50:42 +01:00
Kim Gardner d843320c4a Merge pull request #1976 from brycekahle/fix-admin-menu-visibility
do not show admin drawer unless user has access
2018-10-15 16:50:30 +01:00
Kim Gardner 8a88228d9c Merge branch 'master' into 1920 2018-10-12 18:57:35 +01:00
Kim Gardner 0a9664d464 Merge branch 'master' into fix-admin-menu-visibility 2018-10-12 18:53:09 +01:00
Kim Gardner b3749b9332 Merge pull request #1961 from hovoodd/master
Added filters to Admin > Community > People
2018-10-12 18:48:55 +01:00
Kim Gardner aecb7de019 Add Spanish translations 2018-10-12 18:24:55 +01:00
Snorre Magnus Davøen ade3cf6252 Display avatar left of comments by default
Previously avatars would be displayed on top of comments. Most comment
and forum systems will display avatars to the left of comments/posts.
The https://github.com/snorremd/talk-plugin-gravatar plugin currently
renders avatars in the avatar slot which will render the avatar on top
of the comments. This change modified the comment container to be
displayed as a flex container with row direction. It also adds 10px top
margin to the avatar slot which corresponds with the comment header.

Signed-off-by: Snorre Magnus Davøen <snorremd@gmail.com>
2018-10-12 19:13:04 +02:00
Wyatt Johnson 1837ae4a0f Merge branch 'master' into fix-admin-menu-visibility 2018-10-12 16:44:22 +00:00
Kim Gardner 334074a7ca Merge branch 'master' into master 2018-10-12 16:55:59 +01:00
Fabian Marz f93c4adc97 Fixed typo in GDPR docs. (#1986) 2018-10-11 14:57:16 +00:00
Wyatt Johnson bb54009567 Websocket Regression (#1971)
* fix: addresses websocket connection issues

- reverted upgrade to subscriptions-transport-ws
- removed unnecessary websocket resets
- improved websocket reconnection logic

* Update package.json
2018-10-09 17:47:07 +00:00
vittoboa c126d217ad Fix spanish translation (#1977) 2018-10-09 15:35:22 +00:00
Bryce Kahle 8b2dc3a46d do not show admin drawer unless user has access
Fixes https://github.com/coralproject/talk/issues/1791
2018-10-08 13:02:37 -04:00
Wyatt Johnson d0eca26d5b Body Count (#1969)
* fix: adjusted beheviour of body count

* fix: updated translation @okbel
2018-10-05 18:03:02 +00:00
Nat Welch b742923897 Toxic Comments Plugin logging (#1970)
* Add debug as a dep to toxic-comments

* Add debug logging to toxic-comments plugin

* fix: cleaned up a bit
2018-10-05 17:40:32 +00:00
Wyatt Johnson 1781b926d9 fix: tab container css (#1959) 2018-10-04 20:27:38 +00:00
Harrison Reid 9222ee57a5 Update text and description in moderation configure view
Rename include_comment_stream to code_of_conduct_summary, update text.
Rename include_comment_stream_desc to code_of_conduct_summary_desc, update text.
2018-10-04 18:46:18 +10:00
hovoodd 67cca7d85d Add filters to list of people 2018-10-03 22:15:05 +03:00
Wyatt Johnson 7b97a8fca2 Passport Fix (#1955)
* fix: Fixed bug in passport access

* fix: resolved issues with postMessage and static urls
2018-10-02 16:21:45 +00:00
Kim Gardner 6bff2de371 Merge pull request #1954 from coralproject/static-uri
Static URI + Social Callback bug
2018-10-01 19:08:48 -04:00
Wyatt Johnson 05283b08f7 chore: version bump 2018-10-01 16:58:58 -06:00
Wyatt Johnson 838687a3ae fix: added support for static uri in CSP header for social callback 2018-10-01 16:58:14 -06:00
immber 7b0213299a Updated quickstart (#1898)
* udpated quickstart

* fixed typo and removed quote from quickstart

* fixed broken link in readme

* fixed broken anchor tags

* fix: fixed anchor links
2018-09-20 21:24:44 -06:00
Kim Gardner f4dbc7a24d Merge pull request #1893 from coralproject/fixes
Shorten Spanish phrases for UI purposes
2018-09-19 16:37:56 -04:00
Kim Gardner 161e8b4117 Shorten Spanish phrases for UI purposes 2018-09-19 16:30:17 -04:00
Kim Gardner 6f3535984c Add Spanish translations for toxic-comments (#1891) 2018-09-19 10:22:34 -06:00
Kim Gardner e3557c1f7a Fix en and es translations (#1890) 2018-09-19 10:07:33 -06:00
Kim Gardner 692b6af09d Fix bug with translations for Google auth (#1886)
* Fix bug with translations for Google auth

* Typo
2018-09-18 17:46:55 -06:00
Kim Gardner 25932e10d6 Update translations for ignore-user plugin (#1887) 2018-09-18 17:33:40 -06:00
Kim Gardner ec13a001a2 Notifications plugins es translations (#1888)
* Es translation

* More translations for es

* Category and digest translations

* Typo

* Typo

* Fixes
2018-09-18 17:22:39 -06:00
Kim Gardner 702a616e6c More profile-data translations (#1889) 2018-09-18 17:17:38 -06:00
Kiwi a3dd208fbd Add static className to featured tooltip (#1885) 2018-09-18 15:45:29 -06:00
Kim Gardner 55c3406990 Add more missing es translations (#1883)
* Add more missing es translations

* Typo
2018-09-17 17:09:04 -06:00
Kim Gardner 197fbc33e0 Bump version 4.6.3 (#1871)
* Bump version 4.6.3

* Update index.js
2018-09-17 15:12:30 -06:00
Kim Gardner 008a4fd7d5 Es typo (#1880) 2018-09-17 14:53:36 -06:00
Kim Gardner 8c5665095f Adds Spanish translations for profile data (#1872) 2018-09-17 19:33:27 +00:00
Kim Gardner 647b2ea0c8 Add missing Spanish translations for local-auth (#1879)
* Add missing Spanish translations for local-auth

* fix: yaml fix
2018-09-17 18:27:30 +00:00
Kim Gardner e61f88b98d Shorten es.yml translation to "Configurar" (#1878) 2018-09-17 17:26:53 +00:00
Kim Gardner b024421bbb Merge pull request #1875 from coralproject/http-error
404 Errors
2018-09-14 16:25:02 -04:00
Wyatt Johnson ce95467145 fix: don't log an error for http 404, let the logger do it 2018-09-14 11:48:48 -06:00
Kim Gardner 1564b380b1 Merge pull request #1870 from coralproject/tag-fix
Tag Fix
2018-09-13 23:38:40 -04:00
Kim Gardner 593b8959c5 Merge branch 'master' into tag-fix 2018-09-13 22:36:57 -04:00
Kim Gardner 3fda3b4b7c Merge pull request #1868 from coralproject/upgrade-ws
Upgraded websocket package
2018-09-13 22:36:36 -04:00
Wyatt Johnson 7e680038d6 fix: added tests 2018-09-13 11:21:40 -06:00
Wyatt Johnson 71b8bb5d60 fix: fixes #1867 2018-09-13 11:10:45 -06:00
Wyatt Johnson cfbafdeb5c feat: upgraded websocket package 2018-09-13 07:46:53 -06:00
Wyatt Johnson c19d4a433a Improved Comment URL Handling (#1860)
* feat: replaces PR 1819

- fixes #1851

* fix: set query using new syntax

* review: fixes for missed framework changes

* fix: linting
2018-09-11 22:44:23 +00:00
Kiwi 13cc80bd35 Merge pull request #1841 from hjanuschka/toggle_comment_date
moderation: toggle comment date from timeago to full date
2018-09-11 16:09:15 +02:00
Helmut Januschka 1eea2daeae adress feedback 2018-09-10 21:34:07 +02:00
Kim Gardner 5b29e7d351 Merge branch 'master' into toggle_comment_date 2018-09-10 10:53:34 -04:00
Kiwi 3af39d99e4 Merge pull request #1840 from hjanuschka/word_break
fix long urls breaking moderation box
2018-09-07 22:01:40 +02:00
Kiwi 1abf988366 Merge branch 'master' into word_break 2018-09-07 21:51:37 +02:00
Helmut Januschka 205f943ab5 Update Comment.css 2018-09-07 19:13:36 +02:00
Helmut Januschka 879683da4f Merge branch 'toggle_comment_date' of github.com:hjanuschka/talk into toggle_comment_date 2018-09-07 13:38:52 +02:00
Helmut Januschka 974e884432 adress feedback 2018-09-07 13:36:55 +02:00
Helmut Januschka 846319f1a9 Merge branch 'word_break' of github.com:hjanuschka/talk into word_break 2018-09-07 13:12:49 +02:00
Helmut Januschka e5237ae805 adopt feedback 2018-09-07 13:12:01 +02:00
Wyatt Johnson b9d88251e6 External User Fix (#1846)
* fix: return user if user was not initially found

* fix: added tests, added wasUpserted boolean
2018-09-06 15:22:58 +00:00
Wyatt Johnson 0d720626d4 Merge branch 'master' into toggle_comment_date 2018-09-04 17:59:55 +00:00
Wyatt Johnson 50b2c6aa88 Merge branch 'master' into word_break 2018-09-04 15:04:50 +00:00
Thomaz de Oliveira dos Reis 382019d907 Change proxy-agent to support http/https through http/https/socks/pac proxy on scraper (#1839) 2018-09-04 15:01:10 +00:00
Helmut Januschka 97103739c2 toggle comment date 2018-09-03 09:04:33 +02:00
Helmut Januschka 9cf13691ad fix long urls breaking moderation box 2018-09-03 08:20:56 +02:00
Kim Gardner e6107173fb Merge pull request #1831 from coralproject/small-fix
Add missing translation
2018-08-29 12:02:34 -04:00
Kim Gardner 1d9ec7a779 Formatting 2018-08-29 11:46:41 -04:00
Kim Gardner 0c345fe913 Add missing translation 2018-08-29 11:44:56 -04:00
Kim Gardner b6f06f1e89 Merge pull request #1805 from danilogila/translations_ptbr
Adding more translations in some missing pages
2018-08-29 11:39:47 -04:00
Kim Gardner aea3a7449e Merge branch 'master' into translations_ptbr 2018-08-29 10:23:08 -04:00
Kim Gardner 4db73ae73a Merge pull request #1804 from hjanuschka/patch-2
support translation in permalink share button
2018-08-29 10:21:34 -04:00
Kim Gardner 2af7acd3db Merge branch 'master' into patch-2 2018-08-28 16:30:25 -04:00
Kim Gardner da9847b88f Merge pull request #1822 from hjanuschka/patch-3
add german translation for sort-upvoted
2018-08-28 13:53:46 -04:00
Kim Gardner 5b9f9f1b0d Merge branch 'master' into translations_ptbr 2018-08-28 13:53:19 -04:00
Helmut Januschka 5427f295f6 add german translation for sort-upvoted 2018-08-28 15:48:50 +02:00
Wyatt Johnson 620273769f Update package.json (#1818) 2018-08-24 12:22:23 -06:00
Wyatt Johnson dff3d79569 Subscription Optimizations (#1817)
* fix: optimized user extraction during subscription operations

* fix: fixed query

* feat: added user refeshing batching
2018-08-24 16:14:18 +00:00
Suriyaa ✌️️ 0904453e41 Remove Node Security Platform Badge (#1813) 2018-08-22 16:04:58 +00:00
Helmut Januschka 90eafb557c fix language detection in IE11 and Edge (#1810) 2018-08-21 17:52:02 +00:00
Danilo.Santana bcb294c625 adding more translations in some missing pages 2018-08-16 11:27:31 -03:00
Helmut Januschka b070a7cc52 cs fixes 2018-08-16 14:25:48 +02:00
Helmut Januschka e48d89a607 Update en.yml 2018-08-16 14:03:57 +02:00
Helmut Januschka cb57e5c1e1 add DE translation 2018-08-16 14:03:23 +02:00
Helmut Januschka bf363621d3 support translation in button 2018-08-16 14:02:07 +02:00
Kim Gardner 4dbd1e0bb3 Add version tip to docs (#1790) 2018-08-08 17:03:23 +00:00
Kim Gardner cbda970437 Merge pull request #1789 from coralproject/release-4-6-1
Bump version 4.6.1
2018-08-08 17:42:19 +01:00
Wyatt Johnson eecd814e53 Merge branch 'master' into release-4-6-1 2018-08-08 16:31:42 +00:00
Kim Gardner fb4b6330da 4.6.1 Release 2018-08-08 17:30:01 +01:00
Kim Gardner eb24b5c027 Merge pull request #1785 from coralproject/comment-edit-moderation
Comment Editing Passthrough
2018-08-08 16:44:17 +01:00
Kim Gardner 3346f5de77 Merge branch 'master' into comment-edit-moderation 2018-08-08 14:39:45 +01:00
Kim Gardner 1a69cea9f4 Merge pull request #1786 from coralproject/lowercasedSearch
Case sensitive displayName Issue
2018-08-08 14:38:43 +01:00
Wyatt Johnson d763f0b8f6 fix: added lowercased/case searches for displayName 2018-08-07 16:31:51 -06:00
Wyatt Johnson 6bb98a00dc fix: added support for comment edit mod passthrough 2018-08-07 13:56:23 -06:00
Leandro 2edb8e8d0a use config variable scheduledDeletionDelayHours to schedule deletion time on hoc (#1775) 2018-08-01 21:48:11 +00:00
Mendel Konikov 63400a455f Merge https://github.com/coralproject/talk 2018-05-10 20:53:41 -04:00
Mendel Konikov b7d8fb950b Merge master 2018-05-01 20:25:05 -04:00
Mendel Konikov 3915ffc4e0 Merge master 2018-04-10 20:10:21 -04:00
Mendel Konikov ac9899406a Merge 2018-03-07 12:02:01 -05:00
Mendel Konikov 13e1b288b3 Add more translations 2018-03-07 12:00:38 -05:00
Mendel Konikov 8c8f5d8fee Fix apostrophes 2018-03-07 12:00:38 -05:00
Mendel Konikov 35cf694b60 Add More hebrew translations 2018-03-07 12:00:38 -05:00
Mendel Konikov fa4dbfe9d0 Trigger build 2018-03-07 12:00:38 -05:00
Mendel Konikov 0ea08ccc9c Remove extra space 2018-03-07 12:00:38 -05:00
Mendel Konikov fd5bbe0234 Viewing options Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov b43f657c8d Toxic comments Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov 9456c79bad Most respected Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov 0cd18a458d More Hebrew translations 2018-03-07 12:00:38 -05:00
Mendel Konikov fdcd1404ef Most loved Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov 741d5000dd Most liked Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov 8f413852d9 Settings Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov 420fc6daa7 Off-top Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov ad69086888 Moderation actions Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov f18c51dc68 Member since Hebrew Translation 2018-03-07 12:00:38 -05:00
Mendel Konikov 8e1822af21 Google Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov 5d3e8f3fb6 Flag Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov 71288f543e Featured comments Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov a1890a0bfe Facebook Auth translation 2018-03-07 12:00:38 -05:00
Mendel Konikov 1f17d71a76 Ignore user hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov fc8b9c3547 Like hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov 8ebb90c160 Subscriber hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov b0c230a1df Akismet Hebrew translations 2018-03-07 12:00:38 -05:00
Mendel Konikov be15734c77 Respect hebrew translations 2018-03-07 12:00:38 -05:00
Mendel Konikov 4478ced205 Auth Hebrew translations 2018-03-07 12:00:38 -05:00
Mendel Konikov 769437c8fb Add more translations 2018-03-07 12:00:38 -05:00
Mendel Konikov 5fde3235ad Add Hebrew template to all translation files 2018-03-07 12:00:38 -05:00
Mendel Konikov 2a8975711a Begin adding Hebrew translation 2018-03-07 12:00:38 -05:00
Mendel Konikov da56cd5b18 Add more translations 2018-03-06 13:02:03 -05:00
Mendel Konikov 90424b4766 Fix apostrophes 2018-02-26 16:39:28 -05:00
Mendel Konikov cf23bdba12 Add More hebrew translations 2018-02-26 16:37:40 -05:00
Mendel Konikov 0f983109e1 Trigger build 2018-02-26 16:14:16 -05:00
Mendel Konikov 8f7edde93f Remove extra space 2018-02-24 21:38:07 -05:00
Mendel Konikov 1946d023cd Viewing options Hebrew translation 2018-02-24 21:27:31 -05:00
Mendel Konikov 1e5f5848b2 Toxic comments Hebrew translation 2018-02-24 21:25:58 -05:00
Mendel Konikov 3cbf5fef07 Most respected Hebrew translation 2018-02-24 21:21:02 -05:00
Mendel Konikov 4f276ed944 More Hebrew translations 2018-02-24 21:19:43 -05:00
Mendel Konikov e69afc4e76 Most loved Hebrew translation 2018-02-24 21:17:02 -05:00
Mendel Konikov c4b46c28fe Most liked Hebrew translation 2018-02-24 21:15:51 -05:00
Mendel Konikov dac823f9c4 Settings Hebrew translation 2018-02-24 21:14:00 -05:00
Mendel Konikov 65deb1cfbe Off-top Hebrew translation 2018-02-24 21:12:04 -05:00
Mendel Konikov 828f200eb6 Moderation actions Hebrew translation 2018-02-24 21:10:23 -05:00
Mendel Konikov fe5c95c27a Member since Hebrew Translation 2018-02-24 21:06:24 -05:00
Mendel Konikov c7fad00b66 Google Hebrew translation 2018-02-24 21:05:14 -05:00
Mendel Konikov 5165d06d64 Flag Hebrew translation 2018-02-24 21:03:10 -05:00
Mendel Konikov e15275666e Featured comments Hebrew translation 2018-02-24 21:01:25 -05:00
Mendel Konikov 1dc8df4189 Facebook Auth translation 2018-02-24 20:43:18 -05:00
Mendel Konikov ffe83452cd Ignore user hebrew translation 2018-02-24 20:41:22 -05:00
Mendel Konikov 3c3603ee08 Like hebrew translation 2018-02-24 20:34:22 -05:00
Mendel Konikov f6e94b0cc2 Subscriber hebrew translation 2018-02-24 20:30:34 -05:00
Mendel Konikov c3f92e8c8d Akismet Hebrew translations 2018-02-24 20:27:52 -05:00
Mendel Konikov 84a121a1d9 Respect hebrew translations 2018-02-24 20:22:12 -05:00
Mendel Konikov f55f1341e1 Auth Hebrew translations 2018-02-24 20:19:20 -05:00
Mendel Konikov cccac6a8bc Add more translations 2018-02-23 12:17:13 -05:00
Mendel Konikov e775a9f092 Add Hebrew template to all translation files 2018-02-23 12:00:01 -05:00
Mendel Konikov 76237f42da Begin adding Hebrew translation 2018-02-23 11:29:28 -05:00
Mendel Konikov 6e942a1121 Fix typos in comments 2018-02-23 11:11:41 -05:00
315 changed files with 13048 additions and 2959 deletions
+28 -30
View File
@@ -1,4 +1,3 @@
# job_environment will setup the environment for any job being executed.
job_environment: &job_environment
NODE_ENV: test
@@ -41,9 +40,9 @@ integration_job: &integration_job
- attach_workspace:
at: ~/coralproject/talk
- <<: *create_indexes
- run:
name: Setup the database with defaults
command: ./bin/cli setup --defaults
# - run:
# name: Setup the database with defaults
# command: ./bin/cli setup --defaults
- run:
name: Run the integration tests
command: bash .circleci/e2e.sh
@@ -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
+1
View File
@@ -45,6 +45,7 @@ plugins/*
!plugins/talk-plugin-moderation-actions
!plugins/talk-plugin-notifications
!plugins/talk-plugin-notifications-category-featured
!plugins/talk-plugin-notifications-category-moderation-actions
!plugins/talk-plugin-notifications-category-reply
!plugins/talk-plugin-notifications-category-staff
!plugins/talk-plugin-notifications-digest-daily
+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.
+2
View File
@@ -4,6 +4,7 @@ FROM coralproject/talk:latest
ONBUILD ARG TALK_ADDTL_COMMENTS_ON_LOAD_MORE=10
ONBUILD ARG TALK_ASSET_COMMENTS_LOAD_DEPTH=10
ONBUILD ARG TALK_REPLY_COMMENTS_LOAD_DEPTH=3
ONBUILD ARG TALK_ADDTL_REPLIES_ON_LOAD_MORE=999999
ONBUILD ARG TALK_THREADING_LEVEL=3
ONBUILD ARG TALK_DEFAULT_STREAM_TAB=all
ONBUILD ARG TALK_DISABLE_EMBED_POLYFILL=FALSE
@@ -11,6 +12,7 @@ ONBUILD ARG TALK_DEFAULT_LANG=en
ONBUILD ARG TALK_WHITELISTED_LANGUAGES
ONBUILD ARG TALK_PLUGINS_JSON
ONBUILD ARG TALK_WEBPACK_SOURCE_MAP
ONBUILD ARG TALK_DEFAULT_LAZY_RENDER
# Bundle app source
ONBUILD COPY . /usr/src/app
+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.
+12 -8
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; [![NSP Status](https://nodesecurity.io/orgs/coralproject/projects/7bd7d26c-47ed-4a5f-8c4a-b919bf1c2946/badge)](https://nodesecurity.io/orgs/coralproject/projects/7bd7d26c-47ed-4a5f-8c4a-b919bf1c2946) &middot; [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md#pull-requests)
# 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.
Built with <3 by The Coral Project, a part of [Vox Media](https://www.voxmedia.com/).
## Getting Started
@@ -14,21 +20,19 @@ Learn more about Talk, including a deep dive into features for commenters and mo
## Pre-Launch Guide
Youve installed Talk on your server, and youre preparing to launch it on your site. The real community work starts now, before you go live. You have a unique opportunity pre-launch to set your community up for success. Read our [Talk Community Guide](https://blog.coralproject.net/youve-installed-talk-now-what/).
Youve installed Talk on your server, and youre preparing to launch it on your site. The real community work starts now, before you go live. You have a unique opportunity pre-launch to set your community up for success. Read our [Talk Community Guide](https://coralproject.net/blog/youve-installed-talk-now-what/).
## Advanced Usage
For advanced configuration and usage of Talk, check out our [Configuration](https://docs.coralproject.net/talk/advanced-configuration/) and [Integration](https://docs.coralproject.net/talk/integrating/authentication/) how-tos. This covers topics in whih you will need dev support to fully customize and integrate Talk, such as SSO/authentication, creating and managing assets and articles, styling Talk with custom CSS, and setting up Notifications and SMTP support.
For advanced configuration and usage of Talk, check out our [Configuration](https://docs.coralproject.net/talk/advanced-configuration/) and [Integration](https://docs.coralproject.net/talk/integrating/authentication/) how-tos. This covers topics in which you will need dev support to fully customize and integrate Talk, such as SSO/authentication, creating and managing assets and articles, styling Talk with custom CSS, and setting up Notifications and SMTP support.
## Versions & Upgrading
The current recommended release version is v4.5.0. ^4.5 (and all future even-numbered versions) are considered stable LTS versions. We recommend ^4.5 for use in production environments.
Check our Releases page for the latest recommended release version. [Releases](https://github.com/coralproject/talk/releases) All future even-numbered versions are considered stable LTS versions. We recommend the latest verified release for use in production environments.
## More Resources
- [Talk Product Roadmap](https://www.pivotaltracker.com/n/projects/1863625)
- [Our Blog](https://blog.coralproject.net/)
- [Community Forums](https://community.coralproject.net/)
- [Our Blog](https://coralproject.net/blog)
- [Community Guides for Journalism](https://guides.coralproject.net/)
- [More About Us](https://coralproject.net/)
+26 -2
View File
@@ -11,7 +11,12 @@ const { HELMET_CONFIGURATION } = require('./config');
const { MOUNT_PATH } = require('./url');
const routes = require('./routes');
const debug = require('debug')('talk:app');
const { ENABLE_TRACING, APOLLO_ENGINE_KEY, PORT } = require('./config');
const {
ENABLE_TRACING,
APOLLO_ENGINE_KEY,
PORT,
TRUST_PROXY,
} = require('./config');
const app = express();
@@ -58,7 +63,26 @@ if (ENABLE_TRACING && APOLLO_ENGINE_KEY) {
// Trust the first proxy in front of us, this will enable us to trust the fact
// that SSL was terminated correctly.
app.set('trust proxy', 1);
app.set(
'trust proxy',
(function() {
if (!TRUST_PROXY) {
return null;
}
const lowercase = TRUST_PROXY.toLowerCase();
if (lowercase === 'true' || lowercase === 'false') {
return lowercase === 'true';
}
const parsed = Number(TRUST_PROXY);
if (!isNaN(parsed)) {
return parsed;
}
return TRUST_PROXY;
})()
);
// Enable a suite of security good practices through helmet. We disable
// frameguard to allow crossdomain injection of the embed.
+29
View File
@@ -219,6 +219,28 @@ async function rewrite(search, replace, options) {
}
}
/**
* debugAssetURL will scrape the given URL and print it's scraped values.
*/
async function debugAssetURL(url) {
try {
const meta = await scraper.scrape(url);
const table = new Table({ head: ['Property', 'Value'] });
for (const [property, value] of Object.entries(meta)) {
table.push([property, value]);
}
console.log(table.toString());
util.shutdown(0);
} catch (err) {
console.error(err);
util.shutdown(1);
}
}
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
@@ -234,6 +256,13 @@ program
.description('list all the assets in the database')
.action(listAssets);
program
.command('debug [url]')
.description(
'prints the scraped metadata that would be added to the given asset'
)
.action(debugAssetURL);
program
.command('refresh [age]')
.description('queues the assets that exceed the age requested')
+17 -5
View File
@@ -1,8 +1,10 @@
#!/usr/bin/env node
const throng = require('throng');
const { CONCURRENCY } = require('../config');
const { logger } = require('../services/logging');
const util = require('./util');
const program = require('commander');
const serve = require('../serve');
//==============================================================================
// Setting up the program command line arguments.
@@ -22,8 +24,18 @@ program
)
.parse(process.argv);
// Start serving.
serve(program).catch(err => {
console.error(err);
util.shutdown(1);
throng({
workers: CONCURRENCY,
start: i => {
logger.info({ workerID: i }, 'started worker');
// Load in the serve.
const serve = require('../serve');
// Start serving.
serve(program).catch(err => {
console.error(err);
util.shutdown(1);
});
},
});
+37 -31
View File
@@ -45,11 +45,10 @@ const performSetup = async () => {
} catch (err) {
// If the error is `not init`, then we're good, otherwise, it's something
// else.
if (err instanceof ErrSettingsNotInit) {
if (!err instanceof ErrSettingsNotInit) {
throw err;
return;
}
throw err;
}
if (program.defaults) {
@@ -138,7 +137,7 @@ const performSetup = async () => {
console.log("\nWe'll ask you some questions about your first admin user.\n");
let user = await inquirer.prompt([
let { username, email } = await inquirer.prompt([
{
type: 'input',
name: 'username',
@@ -161,39 +160,46 @@ const performSetup = async () => {
return 'Email is required';
},
},
{
name: 'password',
message: 'Password',
type: 'password',
filter: password => {
return UsersService.isValidPassword(password).catch(err => {
throw err.message;
});
},
},
{
name: 'confirmPassword',
message: 'Confirm Password',
type: 'password',
filter: (confirmPassword, { password }) => {
if (password !== confirmPassword) {
return Promise.reject(new Error('Passwords do not match'));
}
return UsersService.isValidPassword(confirmPassword).catch(err => {
throw err.message;
});
},
},
]);
let password = '';
while (!password) {
answers = await inquirer.prompt([
{
name: 'password',
message: 'Password',
type: 'password',
filter: password => {
try {
UsersService.isValidPassword(password);
} catch (err) {
throw err.message;
}
return password;
},
},
{
name: 'confirmPassword',
message: 'Confirm Password',
type: 'password',
},
]);
if (answers.password !== answers.confirmPassword) {
console.error('Passwords do not match');
} else {
password = answers.password;
}
}
const ctx = Context.forSystem();
let { user: newUser } = await SetupService.setup(ctx, {
settings: settings.toObject(),
user: {
email: user.email,
username: user.username,
password: user.password,
email,
username,
password,
},
});
+2 -1
View File
@@ -105,7 +105,8 @@ function printUserAsTable(user) {
Suspension: user.suspended
? `Until ${user.status.suspension.until}`
: false,
}
},
{ 'Always premod comments': user.alwaysPremod }
);
console.log(table.toString());
@@ -0,0 +1,14 @@
import {
SHOW_ALWAYS_PREMOD_USER_DIALOG,
HIDE_ALWAYS_PREMOD_USER_DIALOG,
} from '../constants/alwaysPremodUserDialog.js';
export const showAlwaysPremodUserDialog = ({ userId, username }) => ({
type: SHOW_ALWAYS_PREMOD_USER_DIALOG,
userId,
username,
});
export const hideAlwaysPremodUserDialog = () => ({
type: HIDE_ALWAYS_PREMOD_USER_DIALOG,
});
+12 -1
View File
@@ -1,4 +1,4 @@
import queryString from 'query-string';
import queryString from 'querystringify';
import {
FETCH_USERS_REQUEST,
@@ -9,6 +9,8 @@ import {
SET_SEARCH_VALUE,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG,
SHOW_ALWAYS_PREMOD_USER_DIALOG,
HIDE_ALWAYS_PREMOD_USER_DIALOG,
SHOW_REJECT_USERNAME_DIALOG,
HIDE_REJECT_USERNAME_DIALOG,
SET_INDICATOR_TRACK,
@@ -61,6 +63,15 @@ export const setSearchValue = value => ({
export const showBanUserDialog = user => ({ type: SHOW_BANUSER_DIALOG, user });
export const hideBanUserDialog = () => ({ type: HIDE_BANUSER_DIALOG });
// Always premod User Dialog
export const showAlwaysPremodUserDialog = user => ({
type: SHOW_ALWAYS_PREMOD_USER_DIALOG,
user,
});
export const hideAlwaysPremodUserDialog = () => ({
type: HIDE_ALWAYS_PREMOD_USER_DIALOG,
});
// Reject Username Dialog
export const showRejectUsernameDialog = user => ({
type: SHOW_REJECT_USERNAME_DIALOG,
+30 -16
View File
@@ -1,16 +1,18 @@
import queryString from 'query-string';
import queryString from 'querystringify';
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 && (
@@ -0,0 +1,29 @@
.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: 50%;
transform: translateY(-50%);
padding: 20px;
border-radius: 4px;
}
.header {
color: black;
font-size: 1.5em;
font-weight: 500;
margin: 0 0 8px 0;
}
.subheader {
color: black;
font-size: 1.3em;
font-weight: 500;
margin: 0 0 8px 0;
}
.buttons {
margin-top: 8px;
margin-bottom: 6px;
text-align: right;
}
@@ -0,0 +1,68 @@
import React from 'react';
import cn from 'classnames';
import PropTypes from 'prop-types';
import { Dialog } from 'coral-ui';
import styles from './AlwaysPremodUserDialog.css';
import Button from 'coral-ui/components/Button';
import t from 'coral-framework/services/i18n';
class AlwaysPremodUserDialog extends React.Component {
handlePerform = () => {
this.props.onPerform();
};
render() {
const { open, onCancel, username, info } = this.props;
return (
<Dialog
className={cn(styles.dialog, 'talk-always-premod-user-dialog')}
id="alwaysPremodUserDialog"
open={open}
onCancel={onCancel}
title={t('alwayspremoddialog.always_premod_user')}
>
<span className={styles.close} onClick={onCancel}>
×
</span>
<section>
<h2 className={styles.header}>
{t('alwayspremoddialog.always_premod_user')}
</h2>
<h3 className={styles.subheader}>
{t('alwayspremoddialog.are_you_sure', username)}
</h3>
<p className={styles.description}>{info}</p>
<div className={styles.buttons}>
<Button
className={cn('talk-always-premod-user-dialog-button-cancel')}
cStyle="white"
onClick={onCancel}
raised
>
{t('alwayspremoddialog.cancel')}
</Button>
<Button
className={cn('talk-always-premod-user-dialog-button-confirm')}
cStyle="black"
onClick={this.handlePerform}
raised
>
{t('alwayspremoddialog.yes_always_premod_user')}
</Button>
</div>
</section>
</Dialog>
);
}
}
AlwaysPremodUserDialog.propTypes = {
open: PropTypes.bool,
onPerform: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
username: PropTypes.string,
info: PropTypes.string,
};
export default AlwaysPremodUserDialog;
+5 -6
View File
@@ -7,9 +7,9 @@ import t from 'coral-framework/services/i18n';
import { can } from 'coral-framework/services/perms';
import cn from 'classnames';
const CoralDrawer = ({ handleLogout, currentUser }) => (
<Drawer className={cn('talk-admin-drawer-nav', styles.drawer)}>
{currentUser && can(currentUser, 'ACCESS_ADMIN') ? (
const CoralDrawer = ({ handleLogout, currentUser }) =>
currentUser && can(currentUser, 'ACCESS_ADMIN') ? (
<Drawer className={cn('talk-admin-drawer-nav', styles.drawer)}>
<div>
<Navigation className={styles.nav}>
{can(currentUser, 'MODERATE_COMMENTS') && (
@@ -48,9 +48,8 @@ const CoralDrawer = ({ handleLogout, currentUser }) => (
<span>{`v${process.env.VERSION}`}</span>
</Navigation>
</div>
) : null}
</Drawer>
);
</Drawer>
) : null;
CoralDrawer.propTypes = {
handleLogout: PropTypes.func.isRequired,
@@ -1,12 +1,10 @@
import React from 'react';
import styles from './Forbidden.css';
import t from 'coral-framework/services/i18n';
const Forbidden = () => (
<div className={styles.container}>
<p className={styles.copy}>
This page is for team use only. Please contact an administrator if you
want to join this team.
</p>
<p className={styles.copy}>{t('error.PAGE_NOT_AVAILABLE_ROLE')}</p>
</div>
);
@@ -30,7 +30,7 @@ class ForgotPassword extends React.Component {
href="#"
onClick={this.handleSignInLink}
>
Sign in
{t('login.sign_in')}
</a>
<Success />
</div>
@@ -48,7 +48,7 @@ class ForgotPassword extends React.Component {
onChange={this.handleEmailChange}
/>
<Button type="submit" cStyle="black" full>
{t('login.reset_password')}
{t('login.reset_password_send_button')}
</Button>
<p className={styles.cta}>
{t('login.go_back')}{' '}
+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>
@@ -137,6 +137,12 @@
display: inline-block;
}
.actionsMenuAlwaysPremod {
background-color: #F0B50B;
border-color: #F0B50B;
color: white;
}
.actionsMenuSuspended {
background-color: #F29336;
border-color: #F29336;
@@ -12,6 +12,7 @@ import {
isUsernameRejected,
isUsernameChanged,
isBanned,
isAlwaysPremod,
getKarma,
} from 'coral-framework/utils/user';
@@ -45,6 +46,7 @@ function getUserStatusArray(user) {
const statusMap = {
suspended: isSuspended,
banned: isBanned,
alwaysPremod: isAlwaysPremod,
usernameRejected: isUsernameRejected,
usernameChanged: isUsernameChanged,
};
@@ -68,6 +70,12 @@ class UserDetail extends React.Component {
username: this.props.root.user.username,
});
showAlwaysPremodUserDialog = () =>
this.props.showAlwaysPremodUserDialog({
userId: this.props.root.user.id,
username: this.props.root.user.username,
});
showRejectUsernameDialog = () =>
this.props.showRejectUsernameDialog({
userId: this.props.root.user.id,
@@ -107,6 +115,8 @@ class UserDetail extends React.Component {
return t('user_detail.suspended');
case 'banned':
return t('user_detail.banned');
case 'alwaysPremod':
return t('user_detail.always_premoded');
case 'usernameRejected':
return (
<span>
@@ -153,6 +163,7 @@ class UserDetail extends React.Component {
toggleSelectAll,
unbanUser,
unsuspendUser,
removeAlwaysPremodUser,
modal,
acceptComment,
rejectComment,
@@ -169,6 +180,7 @@ class UserDetail extends React.Component {
const banned = isBanned(user);
const suspended = isSuspended(user);
const alwaysPremod = isAlwaysPremod(user);
const usernameRejected = isUsernameRejected(user);
const usernameChanged = isUsernameChanged(user);
@@ -200,6 +212,7 @@ class UserDetail extends React.Component {
{
[styles.actionsMenuSuspended]: suspended,
[styles.actionsMenuBanned]: banned,
[styles.actionsMenuAlwaysPremod]: alwaysPremod,
},
'talk-admin-user-detail-actions-button'
)}
@@ -231,6 +244,21 @@ class UserDetail extends React.Component {
</ActionsMenuItem>
)}
{alwaysPremod ? (
<ActionsMenuItem
onClick={() => removeAlwaysPremodUser({ id: user.id })}
>
{t('user_detail.remove_always_premod')}
</ActionsMenuItem>
) : (
<ActionsMenuItem
disabled={me.id === user.id}
onClick={this.showAlwaysPremodUserDialog}
>
{t('user_detail.always_premod')}
</ActionsMenuItem>
)}
{usernameChanged && (
<ActionsMenuItem onClick={this.goToReportedUsernames}>
{t('user_detail.username_needs_approval')}
@@ -476,8 +504,10 @@ UserDetail.propTypes = {
showRejectUsernameDialog: PropTypes.func,
showSuspendUserDialog: PropTypes.func,
showBanUserDialog: PropTypes.func,
showAlwaysPremodUserDialog: PropTypes.func,
unbanUser: PropTypes.func.isRequired,
unsuspendUser: PropTypes.func.isRequired,
removeAlwaysPremodUser: PropTypes.func.isRequired,
modal: PropTypes.bool,
rejectUsername: PropTypes.func.isRequired,
};
@@ -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;
}
@@ -14,9 +14,13 @@ import ApproveButton from './ApproveButton';
import RejectButton from 'coral-admin/src/components/RejectButton';
import CommentDeletedTombstone from './CommentDeletedTombstone';
import t, { timeago } from 'coral-framework/services/i18n';
import { buildCommentURL } from 'coral-framework/utils/url';
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
@@ -27,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,
@@ -73,14 +100,20 @@ class UserDetailComment extends React.Component {
checked={selected}
onChange={e => toggleSelect(e.target.value, e.target.checked)}
/>
<span className={styles.created}>
{timeago(comment.created_at)}
</span>
<TimeAgo className={styles.created} datetime={comment.created_at} />
{comment.editing && comment.editing.edited ? (
<span>
&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}
@@ -112,7 +145,7 @@ class UserDetailComment extends React.Component {
/>
<a
className={styles.external}
href={`${comment.asset.url}?commentId=${comment.id}`}
href={buildCommentURL(comment.asset.url, comment.id)}
target="_blank"
rel="noopener noreferrer"
>
@@ -123,7 +156,7 @@ class UserDetailComment extends React.Component {
<IfHasLink text={comment.body}>
<span className={styles.hasLinks}>
{/* TODO: translate string */}
<Icon name="error_outline" /> Contains Link
<Icon name="error_outline" /> {t('common.contains_link')}
</span>
</IfHasLink>
<div className={styles.actions}>
@@ -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>
);
@@ -48,6 +48,10 @@ const buildActionResponse = (typename, created_at, until, status) => {
return status
? t('user_history.user_banned')
: t('user_history.ban_removed');
case 'AlwaysPremodStatusHistory':
return status
? t('user_history.user_always_premoded')
: t('user_history.always_premod_removed');
case 'SuspensionStatusHistory':
return until
? t('user_history.suspended', readableDuration(created_at, until))
@@ -0,0 +1,2 @@
export const SHOW_ALWAYS_PREMOD_USER_DIALOG = 'SHOW_ALWAYS_PREMOD_USER_DIALOG';
export const HIDE_ALWAYS_PREMOD_USER_DIALOG = 'HIDE_ALWAYS_PREMOD_USER_DIALOG';
@@ -14,6 +14,9 @@ export const FETCH_FLAGGED_COMMENTERS_FAILURE = `${prefix}_FETCH_FLAGGED_COMMENT
export const SHOW_BANUSER_DIALOG = `${prefix}_SHOW_BANUSER_DIALOG`;
export const HIDE_BANUSER_DIALOG = `${prefix}_HIDE_BANUSER_DIALOG`;
export const SHOW_ALWAYS_PREMOD_USER_DIALOG = `${prefix}_SHOW_ALWAYS_PREMOD_USER_DIALOG`;
export const HIDE_ALWAYS_PREMOD_USER_DIALOG = `${prefix}_HIDE_ALWAYS_PREMOD_USER_DIALOG`;
export const SHOW_REJECT_USERNAME_DIALOG = `${prefix}_SHOW_REJECT_USERNAME_DIALOG`;
export const HIDE_REJECT_USERNAME_DIALOG = `${prefix}_HIDE_REJECT_USERNAME_DIALOG`;
+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`;
@@ -0,0 +1,66 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import AlwaysPremodUserDialog from '../components/AlwaysPremodUserDialog';
import { hideAlwaysPremodUserDialog } from '../actions/alwaysPremodUserDialog';
import { withAlwaysPremodUser } from 'coral-framework/graphql/mutations';
import { compose } from 'react-apollo';
import t from 'coral-framework/services/i18n';
class AlwaysPremodUserDialogContainer extends Component {
alwaysPremodUser = async () => {
const { userId, alwaysPremodUser, hideAlwaysPremodUserDialog } = this.props;
await alwaysPremodUser({ id: userId });
hideAlwaysPremodUserDialog();
};
getInfo() {
let note = t('alwayspremoddialog.note_always_premod_user');
return t('alwayspremoddialog.note', note);
}
render() {
return (
<AlwaysPremodUserDialog
open={this.props.open}
onPerform={this.alwaysPremodUser}
onCancel={this.props.hideAlwaysPremodUserDialog}
username={this.props.username}
info={this.getInfo()}
/>
);
}
}
AlwaysPremodUserDialogContainer.propTypes = {
alwaysPremodUser: PropTypes.func.isRequired,
hideAlwaysPremodUserDialog: PropTypes.func,
open: PropTypes.bool,
username: PropTypes.string,
};
const mapStateToProps = ({
alwaysPremodUserDialog: { open, userId, username },
}) => ({
open,
userId,
username,
});
const mapDispatchToProps = dispatch => ({
...bindActionCreators(
{
hideAlwaysPremodUserDialog,
},
dispatch
),
});
export default compose(
connect(
mapStateToProps,
mapDispatchToProps
),
withAlwaysPremodUser
)(AlwaysPremodUserDialogContainer);
@@ -5,6 +5,7 @@ import Layout from '../components/Layout';
import Login from '../containers/Login';
import { FullLoading } from '../components/FullLoading';
import BanUserDialog from './BanUserDialog';
import AlwaysPremodUserDialog from './AlwaysPremodUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import RejectUsernameDialog from './RejectUsernameDialog';
import { toggleModal as toggleShortcutModal } from '../actions/moderation';
@@ -40,6 +41,7 @@ class LayoutContainer extends React.Component {
toggleShortcutModal={toggleShortcutModal}
currentUser={this.props.currentUser}
>
<AlwaysPremodUserDialog />
<BanUserDialog />
<SuspendUserDialog />
<RejectUsernameDialog />
@@ -22,12 +22,14 @@ import {
withSetCommentStatus,
withUnbanUser,
withUnsuspendUser,
withRemoveAlwaysPremodUser,
withRejectUsername,
withPostFlag,
} from 'coral-framework/graphql/mutations';
import UserDetailComment from './UserDetailComment';
import update from 'immutability-helper';
import { showBanUserDialog } from 'actions/banUserDialog';
import { showAlwaysPremodUserDialog } from 'actions/alwaysPremodUserDialog';
import { showSuspendUserDialog } from 'actions/suspendUserDialog';
import { showRejectUsernameDialog } from 'actions/rejectUsernameDialog';
@@ -153,6 +155,7 @@ UserDetailContainer.propTypes = {
selectedCommentIds: PropTypes.array,
unbanUser: PropTypes.func.isRequired,
unsuspendUser: PropTypes.func.isRequired,
removeAlwaysPremodUser: PropTypes.func.isRequired,
rejectUsername: PropTypes.func.isRequired,
userId: PropTypes.string,
};
@@ -218,6 +221,17 @@ export const withUserDetailQuery = withQuery(
created_at
}
}
alwaysPremod {
status
history {
status
assigned_by {
id
username
}
created_at
}
},
username {
status
history {
@@ -280,6 +294,7 @@ const mapDispatchToProps = dispatch => ({
...bindActionCreators(
{
showBanUserDialog,
showAlwaysPremodUserDialog,
showSuspendUserDialog,
showRejectUsernameDialog,
changeTab,
@@ -302,6 +317,7 @@ export default compose(
withSetCommentStatus,
withUnbanUser,
withUnsuspendUser,
withRemoveAlwaysPremodUser,
withRejectUsername,
withPostFlag,
withRouter
@@ -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)}
+78
View File
@@ -10,6 +10,9 @@ const userStatusFragment = gql`
banned {
status
}
alwaysPremod {
status
}
suspension {
until
}
@@ -125,6 +128,64 @@ export default {
});
},
}),
AlwaysPremodUser: ({
variables: {
input: { id },
},
}) => ({
update: proxy => {
const fragmentId = `User_${id}`;
const data = proxy.readFragment({
fragment: userStatusFragment,
id: fragmentId,
});
const updated = update(data, {
state: {
status: {
alwaysPremod: {
status: { $set: true },
},
},
},
});
proxy.writeFragment({
fragment: userStatusFragment,
id: fragmentId,
data: updated,
});
},
}),
RemoveAlwaysPremodUser: ({
variables: {
input: { id },
},
}) => ({
update: proxy => {
const fragmentId = `User_${id}`;
const data = proxy.readFragment({
fragment: userStatusFragment,
id: fragmentId,
});
const updated = update(data, {
state: {
status: {
alwaysPremod: {
status: { $set: false },
},
},
},
});
proxy.writeFragment({
fragment: userStatusFragment,
id: fragmentId,
data: updated,
});
},
}),
BanUser: ({
variables: {
input: { id },
@@ -154,6 +215,23 @@ export default {
});
},
}),
SetUserAlwaysPremodStatus: ({ variables: { status, id } }) => ({
updateQueries: {
TalkAdmin_Community: prev => {
if (!status) {
return prev;
}
const updated = update(prev, {
users: {
nodes: {
$apply: nodes => nodes.filter(node => node.id !== id),
},
},
});
return updated;
},
},
}),
UnbanUser: ({
variables: {
input: { id },
+5
View File
@@ -14,6 +14,11 @@ import { hideShortcutsNote } from './actions/moderation';
smoothscroll.polyfill();
if (!NodeList.prototype.forEach) {
// Polyfill IE11 missing forEach in NodeList.
NodeList.prototype.forEach = Array.prototype.forEach;
}
function init({ store, localStorage }) {
const shouldHide = localStorage.getItem('coral:shortcutsNote') === 'hide';
if (shouldHide) {
@@ -0,0 +1,29 @@
import {
SHOW_ALWAYS_PREMOD_USER_DIALOG,
HIDE_ALWAYS_PREMOD_USER_DIALOG,
} from '../constants/alwaysPremodUserDialog';
const initialState = {
open: false,
userId: null,
username: '',
};
export default function alwaysPremodUserDialog(state = initialState, action) {
switch (action.type) {
case SHOW_ALWAYS_PREMOD_USER_DIALOG:
return {
...state,
open: true,
userId: action.userId,
username: action.username,
};
case HIDE_ALWAYS_PREMOD_USER_DIALOG:
return {
...state,
open: false,
};
default:
return state;
}
}
+2
View File
@@ -5,6 +5,7 @@ import moderation from './moderation';
import install from './install';
import banUserDialog from './banUserDialog';
import suspendUserDialog from './suspendUserDialog';
import alwaysPremodUserDialog from './alwaysPremodUserDialog';
import rejectUsernameDialog from './rejectUsernameDialog';
import userDetail from './userDetail';
import ui from './ui';
@@ -14,6 +15,7 @@ export default {
banUserDialog,
configure,
suspendUserDialog,
alwaysPremodUserDialog,
userDetail,
stories,
community,
+53 -30
View File
@@ -3,56 +3,79 @@ import update from 'immutability-helper';
const initialState = {
assets: {
byId: {},
ids: [],
assets: [],
edges: [],
pageInfo: {},
},
searchValue: '',
criteria: {
asc: 'false',
filter: 'all',
},
loading: true,
loadingMore: false,
};
export default function assets(state = initialState, action) {
switch (action.type) {
case actions.FETCH_ASSETS_SUCCESS: {
const assets = action.assets.reduce((prev, curr) => {
prev[curr.id] = curr;
return prev;
}, {});
case actions.FETCH_ASSETS_REQUEST: {
return update(state, {
loading: { $set: true },
});
}
case actions.FETCH_ASSETS_FAILURE: {
return update(state, {
loading: { $set: false },
});
}
case actions.FETCH_ASSETS_SUCCESS: {
return update(state, {
loading: { $set: false },
assets: {
totalPages: { $set: action.totalPages },
page: { $set: action.page },
byId: { $set: assets },
count: { $set: action.count },
ids: { $set: Object.keys(assets) },
edges: { $set: action.edges },
pageInfo: { $set: action.pageInfo },
},
});
}
case actions.UPDATE_ASSET_STATE_REQUEST:
case actions.LOAD_MORE_ASSETS_REQUEST: {
return update(state, {
loadingMore: { $set: true },
});
}
case actions.LOAD_MORE_ASSETS_FAILURE: {
return update(state, {
loadingMore: { $set: false },
});
}
case actions.LOAD_MORE_ASSETS_SUCCESS: {
return update(state, {
loadingMore: { $set: false },
assets: {
edges: { $push: action.edges },
pageInfo: {
endCursor: { $set: action.pageInfo.endCursor },
hasNextPage: { $set: action.pageInfo.hasNextPage },
},
},
});
}
case actions.UPDATE_ASSET_STATE_SUCCESS:
const index = state.assets.edges.findIndex(
({ node: { id } }) => id === action.id
);
if (index < 0) {
return state;
}
return update(state, {
assets: {
byId: {
[action.id]: {
closedAt: { $set: action.closedAt },
edges: {
[index]: {
node: {
closedAt: { $set: action.closedAt },
},
},
},
},
});
case actions.UPDATE_ASSETS:
return update(state, {
assets: {
assets: { $set: action.assets },
},
});
case actions.SET_PAGE:
return {
...state,
page: action.page,
};
case actions.SET_SEARCH_VALUE:
return {
...state,
+14
View File
@@ -6,6 +6,10 @@ import {
SHOW_SUSPEND_USER_DIALOG,
HIDE_SUSPEND_USER_DIALOG,
} from '../constants/suspendUserDialog';
import {
SHOW_ALWAYS_PREMOD_USER_DIALOG,
HIDE_ALWAYS_PREMOD_USER_DIALOG,
} from '../constants/alwaysPremodUserDialog';
const initialState = {
modal: false,
@@ -23,6 +27,11 @@ export default function config(state = initialState, action) {
...state,
modal: true,
};
case SHOW_ALWAYS_PREMOD_USER_DIALOG:
return {
...state,
modal: true,
};
case HIDE_BAN_USER_DIALOG:
return {
...state,
@@ -33,6 +42,11 @@ export default function config(state = initialState, action) {
...state,
modal: false,
};
case HIDE_ALWAYS_PREMOD_USER_DIALOG:
return {
...state,
modal: false,
};
default:
return state;
}
@@ -78,9 +78,10 @@
}
.flaggedByReason {
font-size: 1tpx;
font-size: 14px;
margin: 0px;
line-height: 1.4;
word-break: break-word;
}
@@ -34,6 +34,21 @@
}
}
.filterHeader {
margin-top: 30px;
font-size: 16px;
font-weight: 900;
}
.filterDetail {
margin-top: 15px;
font-size: 16px;
}
.radioGroup {
margin: 5px 0;
}
.mainContent {
width: calc(100% - 300px);
padding: 34px 14px;
@@ -115,6 +130,12 @@ th.header:nth-child(2), th.header:nth-child(3) {
font-size: 0.98em;
}
.actionsMenuAlwaysPremod {
background-color: #F0B50B;
border-color: #F0B50B;
color: white;
}
.actionsMenuSuspended {
background-color: #F29336;
border-color: #F29336;
@@ -8,7 +8,12 @@ import LoadMore from '../../../components/LoadMore';
import PropTypes from 'prop-types';
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
import { isSuspended, isBanned } from 'coral-framework/utils/user';
import {
isSuspended,
isBanned,
isAlwaysPremod,
} from 'coral-framework/utils/user';
import { RadioGroup, Radio } from 'react-mdl';
import moment from 'moment';
import {
ADMIN,
@@ -42,6 +47,8 @@ class People extends React.Component {
return 'Banned';
} else if (isSuspended(user)) {
return 'Suspended';
} else if (isAlwaysPremod(user)) {
return 'Always premoderated';
}
return '';
};
@@ -54,6 +61,10 @@ class People extends React.Component {
this.props.unbanUser(input);
};
removeAlwaysPremodUser = input => {
this.props.removeAlwaysPremodUser(input);
};
showBanUserDialog = input => {
this.props.showBanUserDialog(input);
};
@@ -62,13 +73,18 @@ class People extends React.Component {
this.props.showSuspendUserDialog(input);
};
showAlwaysPremodUserDialog = input => {
this.props.showAlwaysPremodUserDialog(input);
};
render() {
const {
onSearchChange,
onFilterChange,
users = [],
setUserRole,
viewUserDetail,
loadMore,
filters,
} = this.props;
const hasResults = !!users.nodes.length;
@@ -87,11 +103,44 @@ class People extends React.Component {
id="commenters-search"
type="text"
className={styles.searchBoxInput}
defaultValue=""
onChange={onSearchChange}
value={filters.search}
onChange={onFilterChange('search')}
placeholder={t('streams.search')}
/>
</div>
<div className={styles.filterHeader}>
{t('community.filter_users')}
</div>
<div className={styles.filterDetail}>{t('community.status')}</div>
<RadioGroup
name="statusFilter"
value={filters.status}
childContainer="div"
onChange={onFilterChange('status')}
className={styles.radioGroup}
>
<Radio value="">{t('community.all')}</Radio>
<Radio value="active">{t('community.active')}</Radio>
<Radio value="suspended">{t('community.suspended')}</Radio>
<Radio value="banned">{t('community.banned')}</Radio>
<Radio value="alwaysPremod">{t('community.always_premod')}</Radio>
</RadioGroup>
<div className={styles.filterDetail}>
{t('community.filter_role')}
</div>
<RadioGroup
name="roleFilter"
value={filters.role}
childContainer="div"
onChange={onFilterChange('role')}
className={styles.radioGroup}
>
<Radio value="">{t('community.all')}</Radio>
<Radio value={ADMIN}>{t('community.admin')}</Radio>
<Radio value={STAFF}>{t('community.staff')}</Radio>
<Radio value={MODERATOR}>{t('community.moderator')}</Radio>
<Radio value={COMMENTER}>{t('community.commenter')}</Radio>
</RadioGroup>
</div>
<div className={styles.mainContent}>
{hasResults ? (
@@ -154,6 +203,9 @@ class People extends React.Component {
user
),
[styles.actionsMenuBanned]: isBanned(user),
[styles.actionsMenuAlwaysPremod]: isAlwaysPremod(
user
),
},
'talk-admin-user-detail-actions-button'
)}
@@ -176,7 +228,7 @@ class People extends React.Component {
})
}
>
Suspend User
{t('modqueue.suspend')}
</ActionsMenuItem>
)}
@@ -195,7 +247,28 @@ class People extends React.Component {
})
}
>
Ban User
{t('modqueue.ban_user_actions')}
</ActionsMenuItem>
)}
{isAlwaysPremod(user) ? (
<ActionsMenuItem
onClick={() =>
this.removeAlwaysPremodUser({ id: user.id })
}
>
Remove Always Premoderate
</ActionsMenuItem>
) : (
<ActionsMenuItem
onClick={() =>
this.showAlwaysPremodUserDialog({
userId: user.id,
username: user.username,
})
}
>
{t('modqueue.always_premod_user_actions')}
</ActionsMenuItem>
)}
</ActionsMenu>
@@ -254,13 +327,16 @@ class People extends React.Component {
People.propTypes = {
users: PropTypes.object.isRequired,
onSearchChange: PropTypes.func.isRequired,
filters: PropTypes.object.isRequired,
onFilterChange: PropTypes.func.isRequired,
setUserRole: PropTypes.func.isRequired,
viewUserDetail: PropTypes.func.isRequired,
unbanUser: PropTypes.func.isRequired,
unsuspendUser: PropTypes.func.isRequired,
removeAlwaysPremodUser: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func,
showBanUserDialog: PropTypes.func,
showAlwaysPremodUserDialog: PropTypes.func,
loadMore: PropTypes.func.isRequired,
};
@@ -8,68 +8,76 @@ import {
withUnbanUser,
withUnsuspendUser,
withSetUserRole,
withRemoveAlwaysPremodUser,
} from 'coral-framework/graphql/mutations';
import { showBanUserDialog } from 'actions/banUserDialog';
import { showSuspendUserDialog } from 'actions/suspendUserDialog';
import { showAlwaysPremodUserDialog } from 'actions/alwaysPremodUserDialog';
import { viewUserDetail } from '../../../actions/userDetail';
import { appendNewNodes } from 'plugin-api/beta/client/utils';
import update from 'immutability-helper';
import { Spinner } from 'coral-ui';
import withQuery from 'coral-framework/hocs/withQuery';
class PeopleContainer extends React.Component {
class PeopleContainer extends React.PureComponent {
timer = null;
state = {
searchValue: '',
search: '',
role: '',
status: '',
};
onSearchChange = e => {
const { value } = e.target;
this.setState({ searchValue: value }, () => {
statusToQuery = {
active: { suspended: false, banned: false },
suspended: { suspended: true },
banned: { banned: true },
alwaysPremod: { alwaysPremod: true },
};
onFilterChange = filter => e =>
this.setState({ [filter]: e.target.value }, () => {
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.search(value);
}, 350);
this.timer = setTimeout(this.filter, 350);
});
getFilterState = () => {
const { role, status } = this.state;
return {
status: this.statusToQuery[status] || null,
role: role || null,
};
};
search = async value => {
return this.props.data.fetchMore({
query: SEARCH_QUERY,
filter = () =>
this.props.data.fetchMore({
query: FILTER_QUERY,
variables: {
value,
state: this.getFilterState(),
value: this.state.search,
limit: 10,
},
updateQuery: (previous, { fetchMoreResult: { users } }) => {
const updated = update(previous, {
updateQuery: (previous, { fetchMoreResult: { users } }) =>
update(previous, {
users: {
nodes: {
$set: users.nodes,
},
nodes: { $set: users.nodes },
hasNextPage: { $set: users.hasNextPage },
endCursor: { $set: users.endCursor },
},
});
return updated;
},
}),
});
};
setUserRole = async (id, role) => {
await this.props.setUserRole(id, role);
};
loadMore = () => {
return this.props.data.fetchMore({
loadMore = () =>
this.props.data.fetchMore({
query: LOAD_MORE_QUERY,
variables: {
value: this.state.searchValue,
limit: 5,
cursor: this.props.root.users.endCursor,
state: this.getFilterState(),
value: this.state.search,
limit: 5,
},
updateQuery: (previous, { fetchMoreResult: { users } }) => {
const updated = update(previous, {
updateQuery: (previous, { fetchMoreResult: { users } }) =>
update(previous, {
users: {
nodes: {
$apply: nodes => appendNewNodes(nodes, users.nodes),
@@ -77,11 +85,8 @@ class PeopleContainer extends React.Component {
hasNextPage: { $set: users.hasNextPage },
endCursor: { $set: users.endCursor },
},
});
return updated;
},
}),
});
};
render() {
if (this.props.data.error) {
@@ -98,17 +103,20 @@ class PeopleContainer extends React.Component {
return (
<People
onSearchChange={this.onSearchChange}
onFilterChange={this.onFilterChange}
viewUserDetail={this.props.viewUserDetail}
setUserRole={this.setUserRole}
setUserRole={this.props.setUserRole}
showSuspendUserDialog={this.props.showSuspendUserDialog}
showBanUserDialog={this.props.showBanUserDialog}
showAlwaysPremodUserDialog={this.props.showAlwaysPremodUserDialog}
unbanUser={this.props.unbanUser}
unsuspendUser={this.props.unsuspendUser}
removeAlwaysPremodUser={this.props.removeAlwaysPremodUser}
data={this.props.data}
root={this.props.root}
users={this.props.root.users}
loadMore={this.loadMore}
filters={this.state}
/>
);
}
@@ -118,87 +126,103 @@ PeopleContainer.propTypes = {
setUserRole: PropTypes.func.isRequired,
unbanUser: PropTypes.func.isRequired,
unsuspendUser: PropTypes.func.isRequired,
removeAlwaysPremodUser: PropTypes.func.isRequired,
viewUserDetail: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func,
showBanUserDialog: PropTypes.func,
showAlwaysPremodUserDialog: PropTypes.func,
data: PropTypes.object,
root: PropTypes.object,
};
const LOAD_MORE_QUERY = gql`
query TalkAdmin_Community_People_LoadMoreUsers(
$limit: Int
$cursor: Cursor
$value: String
$state: UserStateInput
) {
users(
query: { value: $value, limit: $limit, cursor: $cursor, state: $state }
) {
hasNextPage
endCursor
nodes {
__typename
id
username
role
created_at
profiles {
id
provider
}
state {
status {
banned {
status
}
alwaysPremod {
status
}
suspension {
until
}
}
}
}
}
}
`;
const FILTER_QUERY = gql`
query TalkAdmin_Community_People_FilterUsers(
$state: UserStateInput
$value: String
$limit: Int
) {
users(query: { state: $state, value: $value, limit: $limit }) {
hasNextPage
endCursor
nodes {
__typename
id
username
role
created_at
profiles {
id
provider
}
state {
status {
banned {
status
}
alwaysPremod {
status
}
suspension {
until
}
}
}
}
}
}
`;
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
viewUserDetail,
showSuspendUserDialog,
showBanUserDialog,
showAlwaysPremodUserDialog,
},
dispatch
);
const LOAD_MORE_QUERY = gql`
query TalkAdmin_Community_People_LoadMoreUsers(
$limit: Int
$cursor: Cursor
$value: String
) {
users(query: { value: $value, limit: $limit, cursor: $cursor }) {
hasNextPage
endCursor
nodes {
__typename
id
username
role
created_at
profiles {
id
provider
}
state {
status {
banned {
status
}
suspension {
until
}
}
}
}
}
}
`;
const SEARCH_QUERY = gql`
query TalkAdmin_Community_People_SearchUsers($value: String, $limit: Int) {
users(query: { value: $value, limit: $limit }) {
hasNextPage
endCursor
nodes {
__typename
id
username
role
created_at
profiles {
id
provider
}
state {
status {
banned {
status
}
suspension {
until
}
}
}
}
}
}
`;
export default compose(
connect(
null,
@@ -207,6 +231,7 @@ export default compose(
withSetUserRole,
withUnsuspendUser,
withUnbanUser,
withRemoveAlwaysPremodUser,
withQuery(
gql`
query TalkAdmin_Community_People {
@@ -228,6 +253,9 @@ export default compose(
banned {
status
}
alwaysPremod {
status
}
suspension {
until
}
@@ -53,7 +53,7 @@ class EmbedLink extends Component {
{t('embedlink.copy')}
</Button>
<div className={styles.copiedText}>
{this.state.copied && 'Copied!'}
{this.state.copied && t('embedlink.copied')}
</div>
</div>
</ConfigureCard>
@@ -155,9 +155,19 @@ class StreamSettings extends React.Component {
<ConfigureCard
checked={settings.infoBoxEnable}
onCheckbox={this.updateInfoBoxEnable}
title={t('configure.include_comment_stream')}
title={t('configure.code_of_conduct_summary')}
>
<p>{t('configure.include_comment_stream_desc')}</p>
<p>
{t('configure.code_of_conduct_summary_desc')}
&nbsp;
<a
target="_blank"
rel="noopener noreferrer"
href="https://guides.coralproject.net/create-a-code-of-conduct/"
>
Code of Conduct Guide.
</a>
</p>
<div
className={cn(
styles.configSettingInfoBox,
@@ -238,12 +248,14 @@ class StreamSettings extends React.Component {
onChange={this.updateClosedTimeout}
value={getTimeoutAmount(settings.closedTimeout)}
label={t('configure.closed_comments_label')}
disabled={!settings.autoCloseStream}
/>
<div className={styles.configTimeoutSelect}>
<SelectField
label="comments closed time window"
value={getTimeoutMeasure(settings.closedTimeout)}
onChange={this.updateClosedTimeoutMeasure}
readOnly={!settings.autoCloseStream}
>
<Option value={'hours'}>{t('configure.hours')}</Option>
<Option value={'days'}>{t('configure.days')}</Option>
@@ -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>
);
@@ -6,19 +6,23 @@ import ConfigureCard from 'coral-framework/components/ConfigureCard';
const Wordlist = ({ suspectWords, bannedWords, onChangeWordlist }) => (
<div>
<ConfigureCard title={t('configure.banned_words_title')}>
<ConfigureCard collapsible title={t('configure.banned_words_title')}>
<p>{t('configure.banned_word_text')}</p>
<TagsInput
value={bannedWords}
inputProps={{ placeholder: 'word or phrase' }}
inputProps={{
placeholder: t('configure.suspect_or_forbidden_words_placeholder'),
}}
onChange={tags => onChangeWordlist('banned', tags)}
/>
</ConfigureCard>
<ConfigureCard title={t('configure.suspect_word_title')}>
<ConfigureCard collapsible title={t('configure.suspect_word_title')}>
<p>{t('configure.suspect_word_text')}</p>
<TagsInput
value={suspectWords}
inputProps={{ placeholder: 'word or phrase' }}
inputProps={{
placeholder: t('configure.suspect_or_forbidden_words_placeholder'),
}}
onChange={tags => onChangeWordlist('suspect', tags)}
/>
</ConfigureCard>
@@ -32,6 +32,7 @@ export default compose(
settings: gql`
fragment TalkAdmin_TechSettings_settings on Settings {
customCssUrl
customAdminCssUrl
domains {
whitelist
}
@@ -61,13 +61,20 @@
line-height: 1.5;
font-weight: 300;
margin-bottom: 8px;
overflow-wrap: break-word;
word-break: break-word;
}
.body {
margin-top: 0px;
flex: 1;
color: black;
max-width: 500px;
/*
IE11 fix Next line was supposed to be:
max-width: 500px;
*/
padding-right: 20px;
/** IE11 fix end **/
font-weight: 300;
font-size: 16px;
overflow-wrap: break-word;
@@ -96,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;
@@ -104,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;
@@ -120,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;
@@ -133,7 +140,7 @@
white-space: nowrap;
&:hover {
text-decoration: underline;
opacity: .9;
opacity: 0.9;
}
i {
font-size: 12px;
@@ -188,4 +195,26 @@
.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;
}
@@ -14,12 +14,16 @@ import cn from 'classnames';
import ApproveButton from 'coral-admin/src/components/ApproveButton';
import RejectButton from 'coral-admin/src/components/RejectButton';
import CommentDeletedTombstone from '../../../components/CommentDeletedTombstone';
import TimeAgo from 'coral-framework/components/TimeAgo';
import t, { timeago } from 'coral-framework/services/i18n';
import { buildCommentURL } from 'coral-framework/utils/url';
import t from 'coral-framework/services/i18n';
class Comment extends React.Component {
ref = null;
state = { showingEditHistory: false };
handleRef = ref => (this.ref = ref);
handleFocusOrClick = () => {
@@ -43,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();
@@ -126,14 +154,23 @@ class Comment extends React.Component {
{comment.user.username}
</span>
<span className={styles.created}>
{timeago(comment.created_at)}
</span>
<TimeAgo
className={styles.created}
datetime={comment.created_at}
/>
{comment.editing && comment.editing.edited ? (
<span>
&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}>
@@ -168,7 +205,7 @@ class Comment extends React.Component {
<div className={styles.commentContentFooter}>
<a
className={styles.external}
href={`${comment.asset.url}?commentId=${comment.id}`}
href={buildCommentURL(comment.asset.url, comment.id)}
target="_blank"
rel="noopener noreferrer"
>
@@ -180,8 +217,7 @@ class Comment extends React.Component {
<div className={styles.sideActions}>
<IfHasLink text={comment.body}>
<span className={styles.hasLinks}>
{/* TODO: translate string */}
<Icon name="error_outline" /> Contains Link
<Icon name="error_outline" /> {t('common.contains_link')}
</span>
</IfHasLink>
<div className={`actions ${styles.actions}`}>
@@ -199,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}
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './Story.css';
import t from 'coral-framework/services/i18n';
const formatDate = date => {
const d = new Date(date);
@@ -14,7 +15,9 @@ const Story = ({ author, title, createdAt, open, id, goToStory }) => {
<div className={styles.meta}>
<span className={styles.author}>By {author}</span>
<span className={styles.createdAt}>{formatDate(createdAt)}</span>
<span className={styles.status}>{open ? 'Open' : 'Closed'}</span>
<span className={styles.status}>
{open ? t('streams.open') : t('streams.closed')}
</span>
</div>
</li>
);
@@ -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,
});
};
@@ -50,31 +48,40 @@ class StoriesContainer extends Component {
const { updateAssetState } = this.props;
try {
updateAssetState(id, closeStream ? Date.now() : null);
this.fetchAssets();
await updateAssetState(id, closeStream ? Date.now() : null);
} 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;
}
+1 -1
View File
@@ -4,7 +4,7 @@ import { createPostMessage } from 'coral-framework/services/postMessage';
document.addEventListener('DOMContentLoaded', () => {
const staticConfig = getStaticConfiguration();
const { STATIC_ORIGIN: origin } = staticConfig;
const { BASE_ORIGIN: origin } = staticConfig;
const postMessage = createPostMessage(origin);
// Get the auth element and parse it as JSON by decoding it.
@@ -1,6 +1,6 @@
import * as actions from '../constants/stream';
import { buildUrl } from 'coral-framework/utils/url';
import queryString from 'query-string';
import queryString from 'querystringify';
export const setActiveReplyBox = id => ({
type: actions.SET_ACTIVE_REPLY_BOX,
@@ -14,12 +14,13 @@ export const setSort = ({ sortOrder, sortBy }) => ({
});
export const viewAllComments = () => (dispatch, _, { pym }) => {
const search = queryString.stringify({
...queryString.parse(location.search),
comment_id: undefined,
});
const query = queryString.parse(location.search);
// remove the comment_id url param
delete query.comment_id;
const search = queryString.stringify(query);
const url = buildUrl({ ...location, search });
try {
@@ -5,6 +5,9 @@ const prefix = 'TALK_EMBED_STREAM';
export const ADDTL_COMMENTS_ON_LOAD_MORE = parseInt(
defaultTo(process.env.TALK_ADDTL_COMMENTS_ON_LOAD_MORE, '10')
);
export const ADDTL_REPLIES_ON_LOAD_MORE = parseInt(
defaultTo(process.env.TALK_ADDTL_REPLIES_ON_LOAD_MORE, '999999')
);
export const ASSET_COMMENTS_LOAD_DEPTH = parseInt(
defaultTo(process.env.TALK_ASSET_COMMENTS_LOAD_DEPTH, '10')
);
@@ -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);
},
},
@@ -175,6 +175,9 @@ const USER_BANNED_SUBSCRIPTION = gql`
banned {
status
}
alwaysPremod {
status
}
suspension {
until
}
@@ -196,6 +199,9 @@ const USER_SUSPENDED_SUBSCRIPTION = gql`
banned {
status
}
alwaysPremod {
status
}
suspension {
until
}
@@ -217,6 +223,9 @@ const USERNAME_REJECTED_SUBSCRIPTION = gql`
banned {
status
}
alwaysPremod {
status
}
suspension {
until
}
@@ -253,6 +262,9 @@ const EMBED_QUERY = gql`
banned {
status
}
alwaysPremod {
status
}
suspension {
until
}
@@ -1,6 +1,7 @@
import React from 'react';
import QuestionBox from '../../../components/QuestionBox';
import DefaultQuestionBoxIcon from '../../../components/DefaultQuestionBoxIcon';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './QuestionBoxBuilder.css';
import { Icon } from 'coral-ui';
@@ -12,6 +13,7 @@ const icons = [{ default: DefaultIcon }, 'forum', 'build', 'format_quote'];
class QuestionBoxBuilder extends React.Component {
render() {
const {
title,
questionBoxIcon,
questionBoxContent,
onContentChange,
@@ -20,7 +22,7 @@ class QuestionBoxBuilder extends React.Component {
return (
<div className={styles.root}>
<h4>Include an Icon</h4>
<h4>{title}</h4>
<ul className={styles.iconList}>
{icons.map(item => {
@@ -53,4 +55,12 @@ class QuestionBoxBuilder extends React.Component {
}
}
QuestionBoxBuilder.propTypes = {
title: PropTypes.string,
questionBoxIcon: PropTypes.string,
questionBoxContent: PropTypes.string,
onContentChange: PropTypes.func,
onIconChange: PropTypes.func,
};
export default QuestionBoxBuilder;
@@ -69,6 +69,7 @@ class Settings extends React.Component {
{questionBoxEnable && (
<div className={styles.questionBoxContainer}>
<QuestionBoxBuilder
title={t('configure.include_an_icon')}
questionBoxIcon={questionBoxIcon}
questionBoxContent={questionBoxContent}
onIconChange={onQuestionBoxIconChange}
@@ -9,6 +9,7 @@ import cn from 'classnames';
import { getTotalReactionsCount } from 'coral-framework/utils';
import t from 'coral-framework/services/i18n';
import { buildCommentURL } from 'coral-framework/utils/url';
class Comment extends React.Component {
render() {
@@ -79,9 +80,10 @@ class Comment extends React.Component {
<li>
<a
className={styles.viewLink}
href={`${this.props.comment.asset.url}?commentId=${
href={buildCommentURL(
this.props.comment.asset.url,
this.props.comment.id
}`}
)}
target="_parent"
>
<Icon name="open_in_new" className={styles.iconView} />
@@ -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')}
@@ -18,6 +18,11 @@
.comment {
position: relative;
padding-left: 15px;
display: flex;
}
.commentContainer {
width: 100%;
}
.commentLevel0 {
@@ -138,6 +143,7 @@
}
.commentAvatar {
margin-top: 10px;
max-width: 60px;
}
@@ -645,7 +645,7 @@ export default class Comment extends React.Component {
})}
onClick={this.onClickEdit}
>
Edit
{t('framework.edit')}
</a>
)}
</span>
@@ -38,5 +38,4 @@
position: relative;
margin-top: 28px;
padding-bottom: 50px;
min-height: 600px;
}
@@ -206,15 +206,30 @@ class Stream extends React.Component {
);
}
renderQuestionBox() {
const {
root,
asset,
asset: {
settings: { questionBoxEnable, questionBoxContent, questionBoxIcon },
},
} = this.props;
const slotPassthrough = { root, asset };
if (questionBoxEnable) {
return (
<QuestionBox content={questionBoxContent} icon={questionBoxIcon}>
<Slot fill="streamQuestionArea" passthrough={slotPassthrough} />
</QuestionBox>
);
}
}
render() {
const {
root,
appendItemArray,
asset,
asset: {
comment: highlightedComment,
settings: { questionBoxEnable },
},
asset: { comment: highlightedComment },
postComment,
notify,
updateItem,
@@ -260,14 +275,7 @@ class Stream extends React.Component {
content={asset.settings.infoBoxContent}
enable={asset.settings.infoBoxEnable}
/>
{questionBoxEnable && (
<QuestionBox
content={asset.settings.questionBoxContent}
icon={asset.settings.questionBoxIcon}
>
<Slot fill="streamQuestionArea" passthrough={slotPassthrough} />
</QuestionBox>
)}
{this.renderQuestionBox()}
{!banned &&
temporarilySuspended && (
<RestrictedMessageBox>
@@ -305,6 +313,7 @@ class Stream extends React.Component {
) : (
<Markdown content={asset.settings.disableCommentingMessage} />
)}
{this.renderQuestionBox()}
</div>
)}
@@ -6,6 +6,7 @@ import { bindActionCreators } from 'redux';
import {
ADDTL_COMMENTS_ON_LOAD_MORE,
ASSET_COMMENTS_LOAD_DEPTH,
ADDTL_REPLIES_ON_LOAD_MORE,
THREADING_LEVEL,
} from '../../../constants/stream';
import {
@@ -152,7 +153,9 @@ class StreamContainer extends React.Component {
return this.props.data.fetchMore({
query: LOAD_MORE_QUERY,
variables: {
limit: parent_id ? 999999 : ADDTL_COMMENTS_ON_LOAD_MORE,
limit: parent_id
? ADDTL_REPLIES_ON_LOAD_MORE
: ADDTL_COMMENTS_ON_LOAD_MORE,
cursor: comment.replies.endCursor,
parent_id,
asset_id: this.props.asset.id,
@@ -398,6 +401,9 @@ const fragments = {
banned {
status
}
alwaysPremod {
status
}
suspension {
until
}
+359
View File
@@ -0,0 +1,359 @@
import queryString from 'querystringify';
import URLSearchParams from '@ungap/url-search-params';
import pym from 'pym.js';
import EventEmitter from 'eventemitter2';
import { buildUrl } from 'coral-framework/utils/url';
import SnackBar from './SnackBar';
import onIntersect from './onIntersect';
import {
createStorage,
connectStorageToPym,
} from 'coral-framework/services/storage';
// Rebuild the origin if it isn't defined. This is our poor-mans polyfill
// for the location APIs.
if (!window.location.origin) {
window.location.origin = `${window.location.protocol}//${
window.location.hostname
}${window.location.port ? `:${window.location.port}` : ''}`;
}
const NOTIFICATION_OFFSET = 200;
// Ensure there is a trailing slash.
function ensureEndSlash(p) {
return p.match(/\/$/) ? p : `${p}/`;
}
// Build the URL to load in the pym iframe.
function buildStreamIframeUrl(talkBaseUrl, query) {
let url = talkBaseUrl + 'embed/stream?';
url += queryString.stringify(query);
return url;
}
// detectAssetURL will try to grab the canonical url from the head, if it isn't
// available, source the url from the current one.
function detectAssetURL() {
try {
// Try to get the url from the canonical tag on the page.
return document.querySelector('link[rel="canonical"]').href;
} catch (e) {
window.console.warn(
'This page does not include a canonical link tag. Talk has inferred this asset_url from the window object. Query params have been stripped, which may cause a single thread to be present across multiple pages.'
);
return window.location.origin + window.location.pathname;
}
}
function buildQuery({ asset_id, asset_url }) {
// Compose the query to send down to the Talk API so it knows what to load.
const query = {};
// Parse the url parameters to extract some of the information.
const search = new URLSearchParams(window.location.search);
// Pull the Comment ID out of the query string.
const commentID = search.get('commentId') || search.get('commentID');
if (commentID) {
query.comment_id = commentID;
}
// Insert the asset_id into the query.
if (asset_id) {
query.asset_id = asset_id;
}
// If the asset_url is defined, use it, otherwise, detect it.
if (asset_url) {
query.asset_url = asset_url;
} else {
query.asset_url = detectAssetURL();
}
return query;
}
// Get dimensions of viewport.
function viewportDimensions() {
let target = window;
let prefix = 'inner';
if (!('innerWidth' in window)) {
prefix = 'client';
target = document.documentElement || document.body;
}
return {
width: target[`${prefix}Width`],
height: target[`${prefix}Height`],
};
}
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,
{
// Pull out the URLs used to setup Talk.
talk: talkBaseUrl,
talkStaticUrl = talkBaseUrl,
// Default the following to null.
events = null,
snackBarStyles = null,
onAuthChanged = null,
// Determine if we're in lazy mode or not. By default, the build argument
// will determine the lazy render status. This default is primarily used
// when the embed code cannot be changed, but control of the Talk serving
// domain is available.
lazy = process.env.TALK_DEFAULT_LAZY_RENDER === 'TRUE',
// Any additional options are extracted to be sent to the embed via the
// pym bridge.
amp,
...opts
}
) {
this.pym = null;
this.element = element;
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 = amp ? null : new SnackBar(snackBarStyles || {});
this.onAuthChanged = onAuthChanged;
this.talkBaseUrl = ensureEndSlash(talkBaseUrl);
this.talkStaticUrl = ensureEndSlash(talkStaticUrl);
// Store queued operations in a queue that can be processed once the stream
// is rendered.
this.queued = [];
// Attach to the events emitted by the pym parent.
if (events) {
events(this.emitter);
}
// Start the embed loading process.
if (this.lazy) {
// When the dom element containing the talk embed container is in view,
// render the stream with force turned on so that it skips this portion.
onIntersect(this.element, () => this.load());
} else {
// We aren't being lazy, load it now!
this.load();
}
}
ensureRendered() {
// Check to see if the pym bridge is created, and the embed is loaded.
if (this.pym === null) {
throw new Error('Stream Embed must be rendered first');
}
}
queueWhenRendered(callback) {
// Check to see if the queue is alive, if it isn't, run the callback now,
// otherwise, push the callback to be processed when the stream has loaded.
if (this.queued !== null) {
this.queued.push(callback);
} else {
callback();
}
}
setupPym() {
const url = buildStreamIframeUrl(this.talkBaseUrl, this.query);
this.pym = new pym.Parent(this.element.id, url, {
title: this.opts.title,
id: `${this.element.id}_iframe`,
name: `${this.element.id}_iframe`,
});
// NOTE: Workaround for iOS Safari which ignores `width` but respects `min-width` value.
this.pym.el.firstChild.style.width = '1px';
this.pym.el.firstChild.style.minWidth = '100%';
// Resize parent iframe height when child height changes
let cachedHeight;
this.pym.onMessage('height', height => {
if (height !== cachedHeight) {
this.pym.el.firstChild.style.height = `${height}px`;
cachedHeight = height;
if (this.amp) {
window.parent.postMessage(
{
sentinel: 'amp',
type: 'embed-size',
height,
},
'*'
);
}
}
});
// Send the config back over the pym bridge if requested.
this.pym.onMessage('getConfig', () => {
this.pym.sendMessage('config', JSON.stringify(this.opts));
});
// If the auth changes, and someone is listening for it, then re-emit it.
if (this.onAuthChanged) {
this.pym.onMessage('coral-auth-changed', message => {
this.onAuthChanged(message ? JSON.parse(message) : null);
});
}
// Remove the permalink comment id from the search.
this.pym.onMessage('coral-view-all-comments', () => {
const query = queryString.parse(location.search);
// Remove the commentId/commentID url param.
delete query.commentId;
delete query.commentID;
// Rebuild the search field without the commentId in it.
const search = queryString.stringify(query);
// Change the url.
window.history.replaceState(
{},
document.title,
buildUrl({ ...location, search })
);
});
// Remove the permalink comment id from the hash.
this.pym.onMessage('coral-view-comment', id => {
const search = queryString.stringify({
...queryString.parse(location.search),
commentId: id,
});
// Change the url to the permalink url.
window.history.replaceState(
{},
document.title,
buildUrl({ ...location, search })
);
});
// Helps child show notifications at the right scrollTop.
this.pym.onMessage('getPosition', () => {
const { height } = viewportDimensions();
let position = height + document.body.scrollTop;
if (position > NOTIFICATION_OFFSET) {
position = position - NOTIFICATION_OFFSET;
}
this.pym.sendMessage('position', position);
});
// When end-user clicks link in iframe, open it in parent context
this.pym.onMessage('navigate', url => {
// Open the new window, detach the opener, and focus on it.
const w = window.open(url, '_blank');
w.opener = null;
w.focus();
});
// Pass events from iframe to the event emitter.
this.pym.onMessage('event', raw => {
const { eventName, value } = JSON.parse(raw);
this.emitter.emit(eventName, value);
});
}
/**
* load will configure the pym parent if it hasn't already been, and setup the
* snackBar. If there are any queued operations, it will run them first.
*/
load() {
if (this.pym !== null) {
throw new Error('Stream Embed already rendered');
}
// Setup Pym.
this.setupPym();
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);
// Listens to ${name}Storage requests on pym and relay it to
// ${name}Storage.
['local', 'session'].forEach(name => {
connectStorageToPym(
createStorage(`${name}Storage`),
this.pym,
`${name}Storage`
);
});
// Process any queued operations.
const queued = this.queued;
this.queued = null;
queued.forEach(callback => callback());
}
handleClick() {
this.pym.sendMessage('click');
}
enablePluginsDebug() {
this.pym.sendMessage('enablePluginsDebug');
}
disablePluginsDebug() {
this.pym.sendMessage('disablePluginsDebug');
}
login(token) {
this.pym.sendMessage('login', token);
}
logout() {
this.pym.sendMessage('logout');
}
remove() {
// Remove the event listeners.
document.removeEventListener('click', this.handleClick.bind(this));
this.emitter.removeAllListeners();
// Remove the snackbar.
if (this.snackBar) {
this.snackBar.remove();
this.snackBar = null;
}
// Remove the pym parent.
this.pym.remove();
this.pym = null;
}
}
@@ -7,17 +7,25 @@ const DEFAULT_STYLE = {
willChange: 'transform, opacity',
transition: 'transform .35s cubic-bezier(.55,0,.1,1), opacity .35s',
pointerEvents: 'none',
padding: '12px 18px',
padding: '12px 18px 0',
color: '#fff',
borderRadius: '3px 3px 0 0',
textAlign: 'center',
borderRadius: '4px',
maxWidth: '400px',
left: '50%',
opacity: 0,
transform: 'translate(-50%, 20px)',
bottom: 0,
bottom: '20px',
boxSizing: 'border-box',
fontFamily: 'Helvetica, "Helvetica Neue", Verdana, sans-serif',
display: 'flex',
};
const CLOSE_STYLE = {
fontSize: '20px',
cursor: 'pointer',
marginLeft: '10px',
position: 'relative',
top: '-4px',
};
export default class Snackbar {
@@ -26,6 +34,20 @@ export default class Snackbar {
this.el = document.createElement('div');
this.el.id = 'coral-notif';
const closeButton = document.createElement('div');
closeButton.className = 'coral-notif-close';
for (let key in CLOSE_STYLE) {
closeButton.style[key] = CLOSE_STYLE[key];
}
closeButton.textContent = '×';
closeButton.onclick = () => this.clear();
this.snackbarText = document.createElement('div');
this.snackbarText.className = 'coral-notif-text';
this.snackbarText.style.paddingBottom = '12px';
this.el.appendChild(this.snackbarText);
this.el.appendChild(closeButton);
// Apply custom styles to the snackbar.
const style = Object.assign({}, DEFAULT_STYLE, customStyle);
for (let key in style) {
@@ -35,6 +57,7 @@ export default class Snackbar {
clear() {
this.el.style.opacity = 0;
this.el.style.pointerEvents = 'none';
}
alert(message) {
@@ -42,7 +65,7 @@ export default class Snackbar {
this.el.style.transform = 'translate(-50%, 20px)';
this.el.style.opacity = 0;
this.el.className = `coral-notif-${type}`;
this.el.textContent = text;
this.snackbarText.textContent = text;
if (this.timeout) {
clearTimeout(this.timeout);
@@ -51,10 +74,12 @@ export default class Snackbar {
this.timeout = setTimeout(() => {
this.el.style.transform = 'translate(-50%, 0)';
this.el.style.opacity = 1;
this.el.style.pointerEvents = 'auto';
this.timeout = setTimeout(() => {
this.el.style.opacity = 0;
}, 7000);
this.el.style.pointerEvents = 'none';
}, 15000);
}, 0);
}
-195
View File
@@ -1,195 +0,0 @@
import queryString from 'query-string';
import pym from 'pym.js';
import EventEmitter from 'eventemitter2';
import { buildUrl } from 'coral-framework/utils/url';
import Snackbar from './Snackbar';
import {
createStorage,
connectStorageToPym,
} from 'coral-framework/services/storage';
const NOTIFICATION_OFFSET = 200;
// Build the URL to load in the pym iframe.
function buildStreamIframeUrl(talkBaseUrl, query) {
let url = [
talkBaseUrl,
talkBaseUrl.match(/\/$/) ? '' : '/', // make sure no double-'/' if opts.talk already ends with '/'
'embed/stream?',
].join('');
url += queryString.stringify(query);
return url;
}
// Get dimensions of viewport.
function viewportDimensions() {
let e = window,
a = 'inner';
if (!('innerWidth' in window)) {
a = 'client';
e = document.documentElement || document.body;
}
return {
width: e[`${a}Width`],
height: e[`${a}Height`],
};
}
export default class Stream {
constructor(el, talkBaseUrl, query, config) {
this.query = query;
// Extract the non-opts opts from the object.
const {
events = null,
snackBarStyles = null,
onAuthChanged = null,
...opts
} = config;
this.opts = opts;
this.emitter = new EventEmitter({ wildcard: true });
this.pym = new pym.Parent(el.id, buildStreamIframeUrl(talkBaseUrl, query), {
title: opts.title,
id: `${el.id}_iframe`,
name: `${el.id}_iframe`,
});
this.snackBar = new Snackbar(snackBarStyles || {});
// Workaround: IOS Safari ignores `width` but respects `min-width` value.
this.pym.el.firstChild.style.width = '1px';
this.pym.el.firstChild.style.minWidth = '100%';
// Resize parent iframe height when child height changes
let cachedHeight;
this.pym.onMessage('height', height => {
if (height !== cachedHeight) {
this.pym.el.firstChild.style.height = `${height}px`;
cachedHeight = height;
}
});
// Attach to the events emitted by the pym parent.
if (events) {
events(this.emitter);
}
this.pym.onMessage('getConfig', () => {
this.pym.sendMessage('config', JSON.stringify(opts));
});
// If the auth changes, and someone is listening for it, then re-emit it.
if (onAuthChanged) {
this.pym.onMessage('coral-auth-changed', message => {
onAuthChanged(message ? JSON.parse(message) : null);
});
}
// Attach the snackbar to the pym parent and to the body of the page.
this.snackBar.attach(window.document.body, this.pym);
// Remove the permalink comment id from the hash.
this.pym.onMessage('coral-view-all-comments', () => {
const search = queryString.stringify({
...queryString.parse(location.search),
commentId: undefined,
});
// Remove the commentId url param.
const url = buildUrl({ ...location, search });
// Change the url.
window.history.replaceState({}, document.title, url);
});
// Remove the permalink comment id from the hash.
this.pym.onMessage('coral-view-comment', id => {
const search = queryString.stringify({
...queryString.parse(location.search),
commentId: id,
});
// Remove the commentId url param.
const url = buildUrl({ ...location, search });
// Change the url.
window.history.replaceState({}, document.title, url);
});
// Helps child show notifications at the right scrollTop.
this.pym.onMessage('getPosition', () => {
const { height } = viewportDimensions();
let position = height + document.body.scrollTop;
if (position > NOTIFICATION_OFFSET) {
position = position - NOTIFICATION_OFFSET;
}
this.pym.sendMessage('position', position);
});
// When end-user clicks link in iframe, open it in parent context
this.pym.onMessage('navigate', url => {
window.open(url, '_blank').focus();
});
// Pass events from iframe to the event emitter.
this.pym.onMessage('event', raw => {
const { eventName, value } = JSON.parse(raw);
this.emitter.emit(eventName, value);
});
// If the user clicks outside the embed, then tell the embed.
document.addEventListener('click', this.handleClick.bind(this), true);
// Listens to local storage requests on pym and relay it to local storage.
connectStorageToPym(
createStorage('localStorage'),
this.pym,
'localStorage'
);
// Listens to session storage requests on pym and relay it to session storage.
connectStorageToPym(
createStorage('sessionStorage'),
this.pym,
'sessionStorage'
);
}
enablePluginsDebug() {
this.pym.sendMessage('enablePluginsDebug');
}
disablePluginsDebug() {
this.pym.sendMessage('disablePluginsDebug');
}
login(token) {
this.pym.sendMessage('login', token);
}
logout() {
this.pym.sendMessage('logout');
}
remove() {
// Remove the event listeners.
document.removeEventListener('click', this.handleClick.bind(this));
this.emitter.removeAllListeners();
// Remove the snackbar.
this.snackBar.remove();
// Remove the pym parent.
this.pym.remove();
}
handleClick() {
this.pym.sendMessage('click');
}
}
-33
View File
@@ -1,33 +0,0 @@
export default class StreamInterface {
constructor(stream) {
this._stream = stream;
}
on(eventName, callback) {
return this._stream.emitter.on(eventName, callback);
}
off(eventName, callback) {
return this._stream.emitter.off(eventName, callback);
}
login(token) {
return this._stream.login(token);
}
logout() {
return this._stream.logout();
}
remove() {
return this._stream.remove();
}
enablePluginsDebug() {
return this._stream.enablePluginsDebug();
}
disablePluginsDebug() {
return this._stream.disablePluginsDebug();
}
}
+56 -90
View File
@@ -1,106 +1,72 @@
import URLSearchParams from 'url-search-params';
import Stream from './Stream';
import StreamInterface from './StreamInterface';
// Polyfill IntersectionObserver always, the alternative is that we have to also
// polyfill for Promise, which itself adds 1KB gziped, which means that the
// 4KB that the intersection observer really doesn't take up that much in terms
// of size.
import 'intersection-observer';
// Rebuild the origin if it isn't defined. This is our poor-mans polyfill
// for the location API's.
if (!window.location.origin) {
window.location.origin = `${window.location.protocol}//${
window.location.hostname
}${window.location.port ? `:${window.location.port}` : ''}`;
}
import Bridge from './Bridge';
import wrapBridge from './wrapBridge';
// parses the Asset URL from the config variable
function parseAssetURL() {
try {
// Try to get the url from the canonical tag on the page.
return document.querySelector('link[rel="canonical"]').href;
} catch (e) {
window.console.warn(
'This page does not include a canonical link tag. Talk has inferred this asset_url from the window object. Query params have been stripped, which may cause a single thread to be present across multiple pages.'
/**
* validateElement will throw an error when the element is not configured
* correctly or is not an HTMLElement at all.
*
* @param {HTMLElement} element the HTMLElement where the stream will be rendered
*/
function validateElement(element) {
if (!element) {
throw new Error(
'Please provide Coral.Talk.render() the HTMLElement you want to render Talk in.'
);
}
return window.location.origin + window.location.pathname;
if (typeof element !== 'object') {
throw new Error(
`Coral.Talk.render() expected HTMLElement but got ${element} (${typeof element})`
);
}
// Ensure element has an id, as pym can't directly accept the HTMLElement.
if (!element.id) {
element.id = `_${Math.random()}`;
}
}
export class Talk {
/**
* validateConfig is the configuration validation tool.
*
* @param {Object} config the configuration that will be used to setup Talk.
*/
function validateConfig(config) {
if (!config || typeof config !== 'object' || !config.talk) {
throw new Error(
'Coral.Talk.render() expected configuration with at least opts.talk as the Talk Base URL, none found'
);
}
}
export const Talk = {
/**
* Render a Talk stream
* @param {HTMLElement} element - Element to render the stream in
* @param {Object} config - Configuration options for talk
* @param {String} config.talk - Talk base URL
* @param {String} [config.title] - Title of Stream (rendered in iframe)
* @param {String} [config.asset_url] - Asset URL
* @param {String} [config.asset_id] - Asset ID
* @param {String} config.talk - URL to the Talk installation
* @param {String} [config.asset_id] - (optional) ID for the Asset
* @param {String} [config.asset_url] - (optional) URL where the Asset is located
* @param {String} [config.auth_token] - (optional) A jwt representing the session
* @param {String} [config.lazy] - (optional) If set the stream will only render lazily
* @param {String} [config.talkStaticUrl] - (optional) Static URL used to serve Talk
* @param {Boolean} [config.amp] - (optional) Run Talk in AMP mode
* @return {Object}
*
* Example:
* ```
* const embed = Talk.render(document.getElementById('talkStreamEmbed'), config);
*
* // trigger a login with optional token.
* embed.login(token);
*
* // trigger a logout.
* embed.logout();
*
* // listen to events (in this case all events).
* embed.on('**', function(value) {
* console.log(this.event, value);
* });
* ```
*/
static render(element, config) {
if (!element) {
throw new Error(
'Please provide Coral.Talk.render() the HTMLElement you want to render Talk in.'
);
}
if (typeof element !== 'object') {
throw new Error(
`Coral.Talk.render() expected HTMLElement but got ${element} (${typeof element})`
);
}
if (!config || typeof config !== 'object' || !config.talk) {
throw new Error(
'Coral.Talk.render() expected configuration with at least opts.talk as the Talk Base URL, none found'
);
}
render: (element, config) => {
// Validate the element.
validateElement(element);
// Ensure el has an id, as pym can't directly accept the HTMLElement.
if (!element.id) {
element.id = `_${Math.random()}`;
}
// Validate the configuration.
validateConfig(config);
// Compose the query to send down to the Talk API so it knows what to load.
const query = {};
// Parse the url parameters to extract some of the information.
const search = new URLSearchParams(window.location.search);
// Pull the commentID out from the query params.
const commentID = search.get('commentId') || search.get('commentID');
if (commentID) {
query.comment_id = commentID;
}
// Extract the asset id from the options.
if (config.asset_id) {
query.asset_id = config.asset_id;
}
// Parse the Asset URL.
query.asset_url = config.asset_url;
if (!query.asset_url) {
query.asset_url = parseAssetURL();
}
// Create the new Stream.
const stream = new Stream(element, config.talk, query, config);
// Return the public interface for the stream.
return new StreamInterface(stream);
}
}
// Create the new Bridge, and wrap it up.
return wrapBridge(new Bridge(element, config));
},
};
+30
View File
@@ -0,0 +1,30 @@
export default function onIntersect(el, callback) {
// Ensure that the intersection observer is available.
if (!IntersectionObserver) {
// tslint:disable-next-line:no-console
window.console.warn('IntersectionObserver not available, rendering now');
callback();
return;
}
// Create the Intersection Observer that will wait till the embed is within
// view and will then call the callback.
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting) {
// Stop receiving intersection events.
observer.disconnect();
// Fire the callback.
callback();
}
},
{
rootMargin: '100px',
threshold: 1.0,
}
);
// Start observing the element for visibility.
observer.observe(el);
}
+31
View File
@@ -0,0 +1,31 @@
export default bridge => {
// whenRendered will call the callback when the stream has rendered (or now if
// the stream is already rendered).
const whenRendered = callback => bridge.queueWhenRendered(callback);
// onlyWhenRendered will guard the callback unless the stream is rendered.
const onlyWhenRendered = callback => {
bridge.ensureRendered();
callback();
};
// Return the limited stream interface.
return {
// Directly map the on/off from the event emitter to the stream.
on: (eventName, callback) => bridge.emitter.on(eventName, callback),
off: (eventName, callback) => bridge.emitter.off(eventName, callback),
// Queue up the login operation until the stream has been rendered.
login: token => whenRendered(() => bridge.login(token)),
// Queue up the logout operation until the stream has been rendered.
logout: () => whenRendered(() => bridge.logout()),
// Remove the stream if it's already been rendered.
remove: () => onlyWhenRendered(() => bridge.remove()),
// Queue up the plugin config until the embed has rendered.
enablePluginsDebug: () => whenRendered(() => bridge.enablePluginsDebug()),
disablePluginsDebug: () => whenRendered(() => bridge.disablePluginsDebug()),
};
};
+20 -16
View File
@@ -1,10 +1,5 @@
import * as actions from '../constants/auth';
import jwtDecode from 'jwt-decode';
function cleanAuthData(localStorage) {
localStorage.removeItem('token');
localStorage.removeItem('exp');
}
import { setStorageAuthToken, clearStorageAuthToken } from '../services/auth';
export const checkLogin = () => (
dispatch,
@@ -15,7 +10,7 @@ export const checkLogin = () => (
rest('/auth')
.then(result => {
if (!result.user) {
cleanAuthData(localStorage);
clearStorageAuthToken(localStorage);
dispatch(checkLoginSuccess(null));
client.resetWebsocket();
return;
@@ -23,12 +18,16 @@ export const checkLogin = () => (
dispatch(checkLoginSuccess(result.user));
pym.sendMessage('coral-auth-changed', JSON.stringify(result.user));
client.resetWebsocket();
// We don't need to reset the websocket here because if the request
// returned that there was a user (which is the case here), then the
// original request has already succeeded, or a previous call to a token
// set handler has already reset it.
})
.catch(error => {
if (error.status && error.status === 401 && localStorage) {
// Unauthorized.
cleanAuthData(localStorage);
clearStorageAuthToken(localStorage);
client.resetWebsocket();
} else {
console.error(error);
@@ -49,14 +48,20 @@ const checkLoginSuccess = user => ({
user,
});
export const setAuthToken = token => (dispatch, _, { localStorage }) => {
localStorage.setItem('exp', jwtDecode(token).exp);
localStorage.setItem('token', token);
export const setAuthToken = token => (
dispatch,
_,
{ localStorage, client }
) => {
setStorageAuthToken(localStorage, token);
// Dispatch the set auth token action. For some browsers and situations, we
// may not be able to persist the auth token any other way. Keep it in redux!
dispatch({ type: actions.SET_AUTH_TOKEN, token });
// Now that we set a token, let's reset the subscriptions.
client.resetWebsocket();
dispatch(checkLogin());
};
@@ -65,9 +70,7 @@ export const handleSuccessfulLogin = (user, token) => (
_,
{ client, localStorage, postMessage }
) => {
const { exp } = jwtDecode(token);
localStorage.setItem('exp', exp);
localStorage.setItem('token', token);
setStorageAuthToken(localStorage, token);
// Send the message via the messages service to the window.opener if it
// exists.
@@ -79,6 +82,7 @@ export const handleSuccessfulLogin = (user, token) => (
);
}
// Now that we just set a token, set the token!
client.resetWebsocket();
dispatch({
@@ -105,7 +109,7 @@ export const logout = () => async (
}
// Clear the auth data persisted to localStorage.
cleanAuthData(localStorage);
clearStorageAuthToken(localStorage);
// Reset the websocket.
client.resetWebsocket();
@@ -1,13 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import { timeago } from 'coral-framework/services/i18n';
import cn from 'classnames';
import styles from './CommentTimestamp.css';
import TimeAgo from 'coral-framework/components/TimeAgo';
const CommentTimestamp = ({ className, created_at }) => (
<div className={cn(className, styles.timestamp, 'talk-comment-timestamp')}>
{timeago(created_at)}
</div>
<TimeAgo
className={cn(className, styles.timestamp, 'talk-comment-timestamp')}
datetime={created_at}
/>
);
CommentTimestamp.propTypes = {
@@ -1,50 +1,48 @@
.card {
margin-bottom: 20px;
align-items: flex-start;
min-height: 100px;
max-width: 600px;
min-height: 100px;
flex-direction: row;
align-items: flex-start;
margin-bottom: 20px;
overflow: visible;
}
.collapsibleCard {
min-height: auto;
}
.enabledCard {
border-left: 7px solid #00796b;
}
.action {
flex-shrink: 0;
margin-right: 12px;
}
.wrapper {
flex-grow: 1;
}
.header {
margin-top: 3px;
margin-bottom: 7px;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 2px;
}
.title {
font-size: 18px;
font-weight: 500;
}
.wrapper {
width: 100%;
.body {
margin-top: 7px;
font-size: 14px;
letter-spacing: 0;
}
.action {
display: inline-block;
position: absolute;
top: 0;
left: 0;
padding: 20px;
}
.content {
display: inline-block;
padding: 0px 30px;
box-sizing: border-box;
}
.enabledSetting {
border-left-color: #00796b;
border-left-style: solid;
border-left-width: 7px;
}
.disabledSetting {
padding-left: 22px;
}
.disabledSettingText {
.disabledBody {
color: #ccc;
pointer-events: none;
}
@@ -1,46 +1,69 @@
import React from 'react';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import styles from './ConfigureCard.css';
import classnames from 'classnames/bind';
import { Card } from 'coral-ui';
import { Checkbox } from 'react-mdl';
import cn from 'classnames';
import { Checkbox, IconButton } from 'react-mdl';
const ConfigureCard = ({
title,
children,
className,
onCheckbox,
checked,
...rest
}) => (
<Card
{...rest}
className={cn(styles.card, className, {
[styles.enabledSetting]: checked === true,
[styles.disabledSetting]: checked === false,
})}
>
{checked !== undefined && (
<div className={styles.action}>
<Checkbox onChange={onCheckbox} checked={checked} />
</div>
)}
<div
className={cn(styles.wrapper, {
[styles.content]: checked !== undefined,
})}
>
<div className={styles.header}>{title}</div>
<div
className={cn({
[styles.disabledSettingText]: checked === false,
import styles from './ConfigureCard.css';
const cn = classnames.bind(styles);
class ConfigureCard extends PureComponent {
state = {
isOpen: !this.props.collapsible,
};
toggle = () => this.setState({ isOpen: !this.state.isOpen });
render() {
const {
title,
children,
className,
onCheckbox,
checked,
collapsible,
...rest
} = this.props;
const { isOpen } = this.state;
const iconName = isOpen ? 'keyboard_arrow_up' : 'keyboard_arrow_down';
return (
<Card
{...rest}
className={cn(styles.card, className, {
enabledCard: checked === true,
collapsibleCard: collapsible,
})}
>
{children}
</div>
</div>
</Card>
);
{checked !== undefined && (
<div className={styles.action}>
<Checkbox onChange={onCheckbox} checked={checked} />
</div>
)}
<div className={styles.wrapper}>
<div className={styles.header}>
<div className={styles.title}>{title}</div>
{collapsible && (
<IconButton onClick={this.toggle} name={iconName} ripple />
)}
</div>
{isOpen && (
<div
className={cn(styles.body, {
disabledBody: checked === false,
})}
>
{children}
</div>
)}
</div>
</Card>
);
}
}
ConfigureCard.propTypes = {
title: PropTypes.string,
@@ -48,6 +71,7 @@ ConfigureCard.propTypes = {
onCheckbox: PropTypes.func,
checked: PropTypes.bool,
children: PropTypes.node,
collapsible: PropTypes.bool,
};
export default ConfigureCard;
@@ -0,0 +1,3 @@
.timeago {
cursor: pointer;
}
@@ -0,0 +1,42 @@
import React from 'react';
import PropTypes from 'prop-types';
import { timeago } from 'coral-framework/services/i18n';
import cn from 'classnames';
import styles from './TimeAgo.css';
class TimeAgo extends React.Component {
constructor(props) {
super(props);
this.state = { timeago: true };
}
toggleDate = () => this.setState({ timeago: !this.state.timeago });
render() {
var displayTime = this.state.timeago
? timeago(this.props.datetime)
: new Date(this.props.datetime).toLocaleString();
var titleDate = !this.state.timeago
? timeago(this.props.datetime)
: new Date(this.props.datetime).toLocaleString();
return (
<span
onClick={this.toggleDate}
className={cn(
this.props.className,
styles.timeago,
'talk-comment-timeago'
)}
title={titleDate}
>
{displayTime}
</span>
);
}
}
TimeAgo.propTypes = {
datetime: PropTypes.string,
className: PropTypes.string,
};
export default TimeAgo;
+2 -2
View File
@@ -1,7 +1,7 @@
import url from 'url';
import parse from 'url-parse';
const base = document.querySelector('base');
const baseUrl = (base && url.parse(base.href)) || {};
const baseUrl = (base && parse(base.href)) || {};
export const BASE_URL = base.href;
export const BASE_PATH = baseUrl.pathname;
@@ -3,6 +3,7 @@ import { createDefaultResponseFragments } from '../utils';
// fragments defined here are automatically registered.
export default {
...createDefaultResponseFragments(
'AlwaysPremodUserResponse',
'BanUsersResponse',
'ChangeUsernameResponse',
'CloseAssetResponse',
@@ -14,6 +15,7 @@ export default {
'IgnoreUserResponse',
'ModifyTagResponse',
'PostFlagResponse',
'RemoveAlwaysPremodUserResponse',
'SetCommentStatusResponse',
'SetUsernameResponse',
'SetUsernameStatusResponse',
@@ -412,6 +412,48 @@ export const withSetUsername = withMutation(
}
);
export const withAlwaysPremodUser = withMutation(
gql`
mutation AlwaysPremodUser($input: AlwaysPremodUserInput!) {
alwaysPremodUser(input: $input) {
...AlwaysPremodUserResponse
}
}
`,
{
props: ({ mutate }) => ({
alwaysPremodUser: input => {
return mutate({
variables: {
input,
},
});
},
}),
}
);
export const withRemoveAlwaysPremodUser = withMutation(
gql`
mutation RemoveAlwaysPremodUser($input: RemoveAlwaysPremodUserInput!) {
removeAlwaysPremodUser(input: $input) {
...RemoveAlwaysPremodUserResponse
}
}
`,
{
props: ({ mutate }) => ({
removeAlwaysPremodUser: input => {
return mutate({
variables: {
input,
},
});
},
}),
}
);
export const withBanUser = withMutation(
gql`
mutation BanUser($input: BanUserInput!) {
+11
View File
@@ -0,0 +1,11 @@
import jwtDecode from 'jwt-decode';
export const setStorageAuthToken = (storage, token) => {
storage.setItem('exp', jwtDecode(token).exp);
storage.setItem('token', token);
};
export const clearStorageAuthToken = storage => {
storage.removeItem('token');
storage.removeItem('exp');
};
+32 -2
View File
@@ -22,6 +22,7 @@ import {
} from 'coral-framework/services/storage';
import { createHistory } from 'coral-framework/services/history';
import { createIntrospection } from 'coral-framework/services/introspection';
import { setStorageAuthToken } from 'coral-framework/services/auth';
import introspectionData from 'coral-framework/graphql/introspection.json';
import coreReducers from '../reducers';
import { checkLogin as checkLoginAction } from '../actions/auth';
@@ -46,7 +47,30 @@ const getAuthToken = (store, storage) => {
// capable of storing the token in localStorage, then we would have
// persisted it to the redux state.
return state.config.auth_token || state.auth.token;
} else if (!bowser.safari && !bowser.ios && storage) {
} else if (location.hash && location.hash.startsWith('#access_token=')) {
// Check to see if the access token is living in the URL as a hash.
const token = location.hash.substring(14);
history.replaceState(
{},
document.title,
window.location.pathname + window.location.search
);
// Once we clear the hash above, this login method will not persist across
// refreshes. We will need to persist the token to storage if it's
// available.
if (storage) {
setStorageAuthToken(storage, token);
}
return token;
} else if (
!bowser.safari &&
!bowser.ios &&
storage &&
storage.getItem('token')
) {
// Use local storage auth tokens where there's a stable api.
return storage.getItem('token');
}
@@ -135,8 +159,13 @@ export async function createContext({
token,
});
const rest2 = createRestClient({
uri: `${BASE_PATH}api/v2`,
token,
});
const staticConfig = getStaticConfiguration();
let { LIVE_URI: liveUri, STATIC_ORIGIN: origin } = staticConfig;
let { LIVE_URI: liveUri, BASE_ORIGIN: origin } = staticConfig;
if (liveUri == null) {
// The protocol must match the origin protocol, secure/insecure.
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
@@ -169,6 +198,7 @@ export async function createContext({
plugins,
eventEmitter,
rest,
rest2,
graphql,
notification,
localStorage,
+15 -13
View File
@@ -88,21 +88,23 @@ export function createClient(options = {}) {
});
client.resetWebsocket = () => {
// Close socket connection which will also unregister subscriptions on the server-side.
wsClient.close();
if (wsClient.client) {
// Close socket connection which will also unregister subscriptions on the server-side.
wsClient.close(true);
// Reconnect to the server.
wsClient.connect();
// Reconnect to the server.
wsClient.connect();
// Reregister all subscriptions (uses non public api).
// See: https://github.com/apollographql/subscriptions-transport-ws/issues/171
Object.keys(wsClient.operations).forEach(id => {
wsClient.sendMessage(
id,
MessageTypes.GQL_START,
wsClient.operations[id].options
);
});
// Re-register all subscriptions (uses non public api).
// See: https://github.com/apollographql/subscriptions-transport-ws/issues/171
Object.keys(wsClient.operations).forEach(id => {
wsClient.sendMessage(
id,
MessageTypes.GQL_START,
wsClient.operations[id].options
);
});
}
};
return client;
+23 -7
View File
@@ -12,6 +12,8 @@ import 'moment/locale/da';
import 'moment/locale/de';
import 'moment/locale/es';
import 'moment/locale/fr';
import 'moment/locale/he';
import 'moment/locale/it';
import 'moment/locale/nl';
import 'moment/locale/pt-br';
@@ -22,6 +24,8 @@ import daTA from 'timeago.js/locales/da';
import deTA from 'timeago.js/locales/de';
import esTA from 'timeago.js/locales/es';
import frTA from 'timeago.js/locales/fr';
import heTA from 'timeago.js/locales/he';
import itTA from 'timeago.js/locales/it';
import nlTA from 'timeago.js/locales/nl';
import pt_BRTA from 'timeago.js/locales/pt_BR';
import zh_CNTA from 'timeago.js/locales/zh_CN';
@@ -34,6 +38,8 @@ import da from '../../../locales/da.yml';
import de from '../../../locales/de.yml';
import es from '../../../locales/es.yml';
import fr from '../../../locales/fr.yml';
import he from '../../../locales/he.yml';
import it from '../../../locales/it.yml';
import nl_NL from '../../../locales/nl_NL.yml';
import pt_BR from '../../../locales/pt_BR.yml';
import zh_CN from '../../../locales/zh_CN.yml';
@@ -61,6 +67,8 @@ export const translations = {
...de,
...es,
...fr,
...he,
...it,
...nl_NL,
...pt_BR,
...zh_CN,
@@ -75,10 +83,15 @@ let TIMEAGO_INSTANCE;
// detectLanguage will try to get the locale from storage if available,
// otherwise will try to get it from the navigator, otherwise, it will fallback
// to the default language.
const detectLanguage = () =>
first(
const detectLanguage = () => {
var browserLanguages = navigator.languages;
//IE11 and MS-EDGE do not provide navigator.languages
if (!browserLanguages) {
browserLanguages = [navigator.language];
}
return first(
negotiateLanguages(
navigator.languages,
browserLanguages,
whitelistedLanguages || supportedLocales,
{
defaultLocale,
@@ -86,6 +99,7 @@ const detectLanguage = () =>
}
)
);
};
export function setupTranslations() {
// locale
@@ -100,10 +114,12 @@ export function setupTranslations() {
ta.register('da', daTA);
ta.register('de', deTA);
ta.register('fr', frTA);
ta.register('nl-NL', nlTA);
ta.register('pt-BR', pt_BRTA);
ta.register('zh-CN', zh_CNTA);
ta.register('zh-TW', zh_TWTA);
ta.register('he', heTA);
ta.register('it', itTA);
ta.register('nl_NL', nlTA);
ta.register('pt_BR', pt_BRTA);
ta.register('zh_CN', zh_CNTA);
ta.register('zh_TW', zh_TWTA);
TIMEAGO_INSTANCE = ta();
}
+2 -2
View File
@@ -14,9 +14,9 @@ import {
export function createStore(reducers, middlewares = []) {
const enhancers = [applyMiddleware(...middlewares)];
if (window.devToolsExtension) {
if (window.__REDUX_DEVTOOLS_EXTENSION__) {
// we can't have the last argument of compose() be undefined
enhancers.push(window.devToolsExtension());
enhancers.push(window.__REDUX_DEVTOOLS_EXTENSION__());
}
return reduxCreateStore(combineReducers(reducers), {}, compose(...enhancers));
+1
View File
@@ -6,6 +6,7 @@ import { capitalize } from 'coral-framework/helpers/strings';
import assignWith from 'lodash/assignWith';
import mapValues from 'lodash/mapValues';
export * from 'coral-framework/helpers/strings';
export * from './url';
export const getTotalActionCount = (type, comment) => {
return comment.action_summaries
+9
View File
@@ -1,3 +1,5 @@
import parse from 'url-parse';
export function buildUrl(
{ protocol, hostname, port, pathname, search, hash } = window.location
) {
@@ -10,3 +12,10 @@ export function buildUrl(
port ? `:${port}` : ''
}${pathname}${search}${hash}`;
}
export function buildCommentURL(assetURL, commentID) {
const u = parse(assetURL, true);
u.query.commentId = commentID;
u.set('query', u.query);
return u.href;
}
+9
View File
@@ -35,6 +35,15 @@ export const isBanned = user => {
return get(user, 'state.status.banned.status');
};
/**
* isAlwaysPremod
* retrieves boolean based on the user premod status
*/
export const isAlwaysPremod = user => {
return get(user, 'state.status.alwaysPremod.status');
};
/**
* isUsernameRejected
* retrieves boolean based on the username status
+10 -2
View File
@@ -16,7 +16,8 @@
text-align: center;
vertical-align: middle;
line-height: 30px;
width: 30px;
min-width: 30px;
padding: 5px;
user-select: none;
}
@@ -24,7 +25,14 @@
font-size: 1.8em;
}
.active {
.page:hover {
background-color: #ababab;
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);
a {
color: white;
}
}
.active, .active:hover {
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);
a {
+49 -1
View File
@@ -30,7 +30,7 @@ const CONFIG = {
ENABLE_TRACING: Boolean(process.env.APOLLO_ENGINE_KEY),
// EMAIL_SUBJECT_PREFIX is the string before emails in the subject.
EMAIL_SUBJECT_PREFIX: process.env.TALK_EMAIL_SUBJECT_PREFIX || '[Talk]',
EMAIL_SUBJECT_PREFIX: process.env.TALK_EMAIL_SUBJECT_PREFIX,
// DEFAULT_LANG is the default language used for server sent emails and
// rendered text.
@@ -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',
@@ -59,6 +85,9 @@ const CONFIG = {
// as report CSP violations.
ENABLE_STRICT_CSP: process.env.TALK_ENABLE_STRICT_CSP === 'TRUE',
// TRUST_PROXY allows control over the `trust proxy` configuration on express.
TRUST_PROXY: process.env.TALK_TRUST_PROXY || '1',
// LOGGING_LEVEL specifies the logging level used by the bunyan logger.
LOGGING_LEVEL: ['fatal', 'error', 'warn', 'info', 'debug', 'trace'].includes(
process.env.TALK_LOGGING_LEVEL
@@ -76,6 +105,10 @@ const CONFIG = {
// on the scraper when it makes requests.
SCRAPER_HEADERS: process.env.TALK_SCRAPER_HEADERS || '{}',
// HTTP_X_REQUEST_ID is a string which represents the request header where we
// should source the request ID from, otherwise, a new one will be generated.
HTTP_X_REQUEST_ID: process.env.TALK_HTTP_X_REQUEST_ID || null,
//------------------------------------------------------------------------------
// JWT based configuration
//------------------------------------------------------------------------------
@@ -213,6 +246,9 @@ const CONFIG = {
// messages through the websocket to keep the socket alive.
KEEP_ALIVE: process.env.TALK_KEEP_ALIVE || '30s',
// CONCURRENCY is the number of workers that will serve traffic.
CONCURRENCY: parseInt(process.env.TALK_CONCURRENCY || '1'),
//------------------------------------------------------------------------------
// Cache configuration
//------------------------------------------------------------------------------
@@ -220,6 +256,14 @@ const CONFIG = {
CACHE_EXPIRY_COMMENT_COUNT:
process.env.TALK_CACHE_EXPIRY_COMMENT_COUNT || '1hr',
// EMBED_EXPIRY_TIME is the time that the embed will be cacheable for, sent as
// the max-age= directive on the Cache-Control header.
EMBED_EXPIRY_TIME: ms(process.env.TALK_EMBED_EXPIRY || '24hr'),
// EMBED_EXPIRY_TIME is the time that the rest of the static files will be
// cacheable for, sent as the max-age= directive on the Cache-Control header.
STATIC_EXPIRY_TIME: ms(process.env.TALK_STATIC_EXPIRY || '1w'),
//------------------------------------------------------------------------------
// Recaptcha configuration
//------------------------------------------------------------------------------
@@ -271,6 +315,10 @@ const CONFIG = {
// CONFIG VALIDATION
//==============================================================================
if (typeof CONFIG.EMAIL_SUBJECT_PREFIX === 'undefined') {
CONFIG.EMAIL_SUBJECT_PREFIX = '[Talk]';
}
if (process.env.NODE_ENV === 'test') {
if (!CONFIG.ROOT_URL) {
CONFIG.ROOT_URL = `http://${localAddress}:3001`;
+135 -101
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,108 +76,142 @@ 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: Slack
url: /v5/integrating/slack/
- 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: Integrating
children:
- title: Authentication
url: /integrating/authentication/
- title: Asset Management
url: /integrating/asset-management/
- 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: 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
-3
View File
@@ -1,3 +0,0 @@
/ /talk/
/talk/reference/server/ /talk/api/server/
/talk/reference/graphql/ /talk/api/graphql/
+54 -37
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,13 +31,10 @@ to persist data. The following versions are supported:
- MongoDB 3.2+
- Redis 3.2.5+
An optional dependency for Talk is
[Docker](https://www.docker.com/community-edition#/download).
It is used during development to set up the database and can be
used to [install via Docker](#installation-from-docker). We have tested Talk
and this documentation with versions 17.06.2+.
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+.
Another optional dependency for Talk is
An optional dependency for Talk is
[Docker Compose](https://docs.docker.com/compose/install/). It
can be used to setup your environment easily for testing. We have tested Talk
and this documentation with versions 1.14.0+.
@@ -38,17 +44,18 @@ 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. The following is a `docker-compose.yml` file that can
be used to setup Talk:
provided docker image.
Start by making a new directory and create a file called `docker-compose.yml` and copy the following:
```yml
# 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"
@@ -79,10 +86,9 @@ volumes:
external: false
```
This is the bare minimum needed to run the demo, for more configuration
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
@@ -104,8 +110,7 @@ Creating talk_1 ...
Creating talk_1 ... done
```
And when you run `docker-compose ps`, you should see something like:
Once everything has completed, run `docker-compose ps`, and you should see something like:
```
Name Command State Ports
@@ -115,9 +120,8 @@ redis_1 docker-entrypoint.sh redis ... Up 6379/tcp
talk_1 yarn start Up 0.0.0.0:3000->3000/tcp
```
Continue onto the [Running](#running) section for details on how to complete the
installation and get started using Talk.
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.
### Installation from Source
@@ -151,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._
@@ -178,39 +181,43 @@ You can now start the application by running:
yarn watch:server
```
Continue onto the [Running](#running) section for details on how to complete the
Continue onto the [Setup](#setup) section for details on how to complete the
installation and get started using Talk.
## Running
## Setup
You can now navigate to
### 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 go through the admin installation. There you will be prompted to create your
first admin account, and specify the domain whitelist for domains that are
allowed to have the comment box on.
and walk through the initial setup steps.
- First, enter your **Organization Name** and **Organization Contact Email**. This will appear in emails when inviting new team members.
- Next, create your Admin user. You can specify an **Email Address**, **Username**, and **Password**
- Finally, enter your list of **Permitted Domains**, read [here](/talk/configuring-talk/#permitted-domains) about whitelisting domains
_During development, ensure you whitelist 127.0.0.1:3000 otherwise the
[http://127.0.0.1:3000/](http://127.0.0.1:3000/) page will not
load._
Once you've completed the installation, you can visit
[http://127.0.0.1:3000/](http://127.0.0.1:3000/) where you can
view our development area where we test out features in Talk where you can write
comments and see them in the admin interface where you can do moderation and
reconfigure the user experience.
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.
## Demo
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
If you've followed the documentation above, you'll now have a running copy of
Talk. To demonstrate what your own self-hosted copy of Talk can do, below
you'll find a demo that can be used to test the copy that is running now on your
Talk. To demonstrate what your own self-hosted copy of Talk can do, we created the demo below
that can be used to test the copy that is running now on your
machine.
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.
@@ -220,6 +227,16 @@ Once you have added the domain of these docs, you can click the button below.
<div class="mount"></div>
</div>
### Developer Endpoints
With your local instance of Talk running in development mode (env variable `NODE_ENV=development`) you should now also be able to access the following developer routes:
- [http://127.0.0.1:3000/dev](http://127.0.0.1:3000/dev]) provides a sample comment stream
- [http://127.0.0.1:3000/dev/assets](http://127.0.0.1:3000/dev/assets]) provides a list of all stories in Talk and can generate new sample assets
### Conclusion
At this point you've successfully installed, configured, and ran your very own
instance of Talk! Continue through this documentation on this site to learn more
on how to configure, develop with, and contribute to Talk!

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