Compare commits

...
187 Commits
Author SHA1 Message Date
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
Kim Gardner c3e162dd56 Merge pull request #1770 from icco/patch-1
Fix small typo in integrating/configuring-admin.md
2018-08-01 16:14:37 +01:00
Wyatt Johnson 3cdd061afd Merge branch 'master' into patch-1 2018-07-31 18:25:05 +00:00
Kim Gardner aaaf37b8db Small typo fix (#1772) 2018-07-31 18:24:52 +00:00
Nat Welch 7af5f04e45 Fix small typo in integrating/configuring-admin.md 2018-07-30 11:42:01 -04:00
Kim Gardner 65a166b860 Merge pull request #1767 from coralproject/token-fix
Token Handling
2018-07-27 21:25:07 +01:00
Kim Gardner eb4bcb71c4 Merge branch 'master' into token-fix 2018-07-27 21:12:52 +01:00
Wyatt Johnson 6a04b66d84 fix: token handling
- Fixes #1728
2018-07-27 14:08:53 -06:00
Kim Gardner 30ae9a82c1 Merge pull request #1764 from coralproject/user-fix
Username Status Fix
2018-07-27 20:50:57 +01:00
Wyatt Johnson ec06f2a392 chore: update 4.6.0 2018-07-27 13:37:46 -06:00
Wyatt Johnson f461f0884b Merge branch 'master' into user-fix 2018-07-27 19:29:05 +00:00
Wyatt Johnson d55c329f76 chore: version bump 2018-07-27 13:27:17 -06:00
Wyatt Johnson 0f63e076a6 fix: username status fix 2018-07-27 13:25:58 -06:00
Fabian Marz 1012b6b28b Fixed UI component path reference. (#1761) 2018-07-26 22:09:03 +00:00
Fabian Marz 421352c75c Fixed code markup for action name. (#1746) 2018-07-13 16:19:00 +00:00
Kim Gardner a170c107e2 Add FAQ and Troubleshooting docs (#1742)
* Add FAQ and Troubleshooting docs

* Add to nav

* Fix links

* Fix css formatting and fix rel links
2018-07-12 19:05:08 +00:00
Kim Gardner 104617d644 Merge pull request #1739 from memz/ar-translations-update-1
updated translation files with new Arabic translations
2018-07-09 17:33:37 -04:00
Kim Gardner 5b1773ba26 Merge branch 'master' into ar-translations-update-1 2018-07-09 16:33:31 -04:00
Nat Welch cd0aeca87c Add suggested index to comments (#1740) 2018-07-09 16:20:05 +00:00
Melhem Fawaz 5c65702032 updated translation files with new Arabic translations 2018-07-09 17:07:02 +04:00
Kim Gardner 66445aae59 Merge pull request #1737 from coralproject/username-status
findOrCreateExternalUser Deprecation
2018-07-06 17:18:32 -04:00
Wyatt Johnson 23156f3d5e feat: added deprecations, docs 2018-07-06 14:28:25 -06:00
Kim Gardner 76bb6df084 Merge pull request #1735 from coralproject/update-doc-versions
Update versions to 4.5
2018-07-06 13:01:03 -04:00
Kim Gardner 54c99c7fcc Update versions to 4.5 2018-07-06 12:52:40 -04:00
Kim Gardner 80fc5e86ff Upgrade mongoose (#1734)
* Upgrade Mongoose

* Upgrade Mongoose 5.2.1

* Yarn
2018-07-05 12:27:20 -06:00
Davis Shaver eddd63ae1a [Request for feedback] Heroku fix (#1721)
* Remove package-lock.json file, as Talk uses Yarn
* Silence ENV rewrite errors in Heroku
2018-07-05 09:06:49 -06:00
Wyatt Johnson e55ed489f2 fix: added disable polyfill arg to onbuild (#1732) 2018-07-04 11:08:47 -06:00
Kim Gardner 95fb33e41b Update version to 4.5.1 (#1718) 2018-06-29 13:08:14 -06:00
Kim Gardner 404c900de7 Merge pull request #1716 from coralproject/readme-link
Fix Talk link in README
2018-06-29 14:47:37 -04:00
Kim Gardner dc773f486b Merge branch 'master' into readme-link 2018-06-29 14:31:49 -04:00
Kim Gardner db679fb6ca Merge pull request #1712 from coralproject/fix-broken-links
Fix some broken links
2018-06-29 14:31:37 -04:00
Kim Gardner a2458e0dd0 Merge branch 'master' into readme-link 2018-06-29 14:12:14 -04:00
Wyatt Johnson f22507e313 Merge branch 'master' into fix-broken-links 2018-06-29 12:06:55 -06:00
Kim Gardner 8cfc0caad3 Merge pull request #1715 from coralproject/custom-displayname
Search - Display Name
2018-06-29 14:01:40 -04:00
Kim Gardner 7d8b36e00c Fix Talk link in README 2018-06-29 13:43:29 -04:00
Wyatt Johnson 50b920b799 feat: added support for metadata.displayName for searches 2018-06-29 11:17:21 -06:00
Kim Gardner b4f5121382 Update links 2018-06-28 16:19:38 -04:00
Kim Gardner b9828bcf11 Merge pull request #1706 from coralproject/snyk-fix-svo35q
[Snyk] Fix for 1 vulnerable dependencies
2018-06-28 15:43:48 -04:00
Kim Gardner 0ad0577ba3 Update lockfile 2018-06-28 15:16:05 -04:00
Kim Gardner 46d6c3a8c6 Merge branch 'master' into snyk-fix-svo35q 2018-06-28 15:11:34 -04:00
Kim Gardner b40ed1d217 Merge pull request #1710 from coralproject/losowsky-patch-1
Added Rich Text to checklist
2018-06-28 14:54:04 -04:00
Andrew Losowsky ab3ceb151a Added Rich Text to checklist 2018-06-27 17:57:23 -05:00
Kim Gardner d393c6fc4d Set versions to 4.5.0 instead of latest (#1708) 2018-06-26 19:35:46 +00:00
snyk-bot e6c6391958 fix: package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/npm:braces:20180219
2018-06-26 02:49:35 +00:00
Kim Gardner f2857a8085 Merge pull request #1705 from coralproject/redirect-uri
Email Confirm Redirect URI
2018-06-25 15:28:41 -04:00
Wyatt Johnson 30a5a08769 Merge branch 'master' into redirect-uri 2018-06-25 18:31:40 +00:00
Wyatt Johnson 16a884d778 fix: extract and add redirect uri 2018-06-25 12:28:25 -06:00
Andrew Losowsky f403281978 added strategy tutorial link (#1704) 2018-06-22 22:44:59 +00:00
Kim Gardner c1d547e1b1 Merge pull request #1702 from coralproject/plugins
Plugin Fixes
2018-06-21 12:49:30 -04:00
Kim Gardner d94003475c Merge branch 'master' into plugins 2018-06-21 12:30:36 -04:00
Kim Gardner dd8a11e241 Merge pull request #1701 from coralproject/code-of-conduct
Update COC
2018-06-21 12:30:21 -04:00
Wyatt Johnson 7699e440e4 fix: updated yarn.lock 2018-06-21 08:37:48 -06:00
Kim Gardner 0ad2da9baa Remove auth-checkbox 2018-06-20 23:57:26 -04:00
Wyatt Johnson c287b842f3 review: spelling 2018-06-20 21:08:32 -06:00
Wyatt Johnson b6d2092048 added more fallbacks 2018-06-20 21:06:55 -06:00
Wyatt Johnson ae9f5d4e8e fixes #1691 2018-06-20 21:03:23 -06:00
Kim Gardner eab0b40e2c Update COC 2018-06-20 18:37:34 -04:00
Wyatt Johnson 0aee78ac5f Delete package-lock.json (#1699) 2018-06-19 21:21:07 +00:00
Andrew Losowsky 56cb8962bd Fixed blog links for new site (#1697)
* Fixed blog links for new site

* Fix links
2018-06-19 21:15:02 +00:00
Kim Gardner 5747fd84ad Merge pull request #1687 from coralproject/update-readme
Add versioning info, cleanup README, add Product Guide link to admin dropdown
2018-06-19 21:06:32 +01:00
Wyatt Johnson d3666a9388 Merge branch 'master' into update-readme 2018-06-19 15:22:20 +00:00
Andrew Losowsky d836e8203f Fixes, more info (#1698)
• removed Heroku (since we don't support in production now)
• fixed Google typo
• Added Akismet info
• Added Slack channel info
2018-06-19 15:21:07 +00:00
Kim Gardner 458927a6e3 Remove Heroku one-click (#1696) 2018-06-18 08:59:37 -07:00
Kim Gardner 0267782386 Merge pull request #1694 from coralproject/feature/talk-installation-checklist
Pre Launch Checklist
2018-06-18 16:46:15 +01:00
Kim Gardner 32bdf1c1af Merge branch 'master' into feature/talk-installation-checklist 2018-06-18 13:43:35 +01:00
Kim Gardner a977ce4ac9 Merge branch 'master' into update-readme 2018-06-18 13:43:15 +01:00
Kim Gardner 18f6121797 Merge pull request #1695 from conversario/neutral-reactions-icon-whatshot
Use the flame/What's hot as the generic reactions icon.
2018-06-18 13:38:42 +01:00
Fabian Neumann 59531f32dc oops 2018-06-18 11:24:38 +02:00
Fabian Neumann 03ae85f5ac Use the flame/What's hot as the generic reactions icon. 2018-06-18 11:15:20 +02:00
Kim Gardner 8ff27d5e8c Merge branch 'master' into feature/talk-installation-checklist 2018-06-15 17:42:00 +01:00
Kim Gardner 7ecf51a159 Merge pull request #1692 from conversario/neutral-reactions-icon
Use neutral icon for aggregated reactions count
2018-06-15 17:41:32 +01:00
Kim Gardner f4513912d4 Layout and wording 2018-06-15 17:27:37 +01:00
Jeff Nelson a7f7e6fa9e fix links 2018-06-15 08:38:44 -07:00
Jeff Nelson 73464361e4 add pre launch checklist 2018-06-15 08:36:56 -07:00
Kim Gardner 56c6633ef6 Merge branch 'master' into neutral-reactions-icon 2018-06-14 14:03:52 +01:00
Fabian Neumann 143d23bf9d Use neutral icon for aggregated reactions count 2018-06-14 11:44:10 +02:00
Kim Gardner 111b1b744f Update dependencies 2018-06-13 17:58:59 +01:00
Kim Gardner 29f8762ed7 Add translation for product guide link 2018-06-13 17:56:28 +01:00
Kim Gardner 959e5beb3c Merge branch 'update-readme' of git+ssh://github.com/coralproject/talk into update-readme 2018-06-13 17:54:09 +01:00
Kim Gardner 7417a972b6 Merge branch 'master' into update-readme 2018-06-13 17:51:01 +01:00
Kim Gardner 3da62f3471 Merge pull request #1688 from coralproject/feature/GH-1685
GH-1685: Fix deploy to Heroku
2018-06-13 17:00:26 +01:00
Kim Gardner cc668ef439 Fix grammar 2018-06-13 16:34:44 +01:00
Jeff Nelson 18fa98d8ea add config for MAILGUN_SMTP_PASSWORD to app.json 2018-06-13 07:13:39 -07:00
Kim Gardner a245c2e7ea Merge branch 'master' into update-readme 2018-06-13 14:21:08 +01:00
Kim Gardner a1745b26f7 Merge pull request #1686 from cristiandean/fix_translation
Add translations in some missing pages and fix others
2018-06-13 14:20:55 +01:00
Kim Gardner 35ca970650 Put Product Guide link in gear dropdown 2018-06-12 20:06:42 +01:00
Kim Gardner 6e8ab4f141 Tweak 2018-06-11 20:52:04 +01:00
Kim Gardner e48c2b9fd5 Add versioning info and cleanup README 2018-06-11 20:15:48 +01:00
Cristian 8c20376c95 Add translations in some missing pages and fix others 2018-06-11 12:34:54 -03:00
Kim Gardner 93813adf97 Merge pull request #1678 from coralproject/user-detail
User Detail Drawer Fixes
2018-06-06 12:38:05 +01:00
Wyatt Johnson ee81a3d280 Merge branch 'master' into user-detail 2018-06-05 17:12:47 -06:00
Kiwi 41fab3e11c Introduce final form, i18n helper, validators and refactor AddEmailAddress (#1636)
* Introduce final form, validators and refactor AddEmailAddress

* First i18n script

* Sort locales

* Add delete and copy action

* Copy over available validator translations

* Add comments

* Export validations in plugin-api

* Linting

* yarn.lock

* Sort locales

* Drop unused translations

* Add translations for validators

* Add action drop-unused

* Add comments

* Add note about limitation

* Fix desc
2018-06-05 17:12:30 -06:00
Wyatt Johnson dae4eb123c Merge branch 'master' into user-detail 2018-06-05 17:11:45 -06:00
Kim Gardner 6b6e59791a Merge pull request #1680 from conversario/translation-reply
Translation fix for DE `common.reply`
2018-06-05 20:44:09 +01:00
Wyatt Johnson ef97ef7217 Merge branch 'master' into translation-reply 2018-06-05 13:22:22 -06:00
Fabian Neumann d407ac0c4a Small grammar fix (#1681) 2018-06-05 13:22:10 -06:00
Kim Gardner 82b580d3a3 Merge branch 'master' into translation-reply 2018-06-05 13:46:48 +01:00
Kim Gardner c2a9e0290c Merge pull request #1523 from coralproject/reject-username
Reject username
2018-06-05 13:44:01 +01:00
Fabian Neumann 01609a51f5 Translation fix for DE common.reply 2018-06-05 13:45:50 +02:00
Chi Vinh Le ccff6ed63b Resolve linter issues 2018-06-05 04:28:54 +02:00
Chi Vinh Le d9a1d739dc Set React version for linters 2018-06-05 04:28:31 +02:00
Chi Vinh Le 05088a7744 Set specific parser, as prettier now has no default 2018-06-05 04:18:55 +02:00
Chi Vinh Le 2387b46ba1 Resolve className and id conflicts 2018-06-05 03:32:15 +02:00
okbel 375c8789a4 updating yarn and nightwatch/selenium-standalone 2018-06-04 21:48:51 -03:00
Wyatt Johnson 7a13b64fbe feat: cleanup of user details drawer 2018-06-04 16:23:58 -06:00
Kiwi b8a12aef2f Merge branch 'master' into reject-username 2018-06-04 17:49:18 -03:00
Kim Gardner 8feb67f2ea Merge pull request #1676 from coralproject/nvmrc
Added .nvmrc file
2018-06-04 21:41:31 +01:00
Chi Vinh Le 773eb34c3e Merge branch 'reject-username' of ssh://github.com/coralproject/talk into reject-username 2018-06-04 22:38:21 +02:00
Chi Vinh Le 5835845092 Fix write fragment 2018-06-04 22:37:31 +02:00
Kiwi 7627d37e22 Merge branch 'master' into reject-username 2018-06-04 17:32:44 -03:00
Chi Vinh Le 6674b657ab Move helper out of class 2018-06-04 22:32:23 +02:00
Chi Vinh Le 9c3d9e5943 Merge branch 'reject-username' of ssh://github.com/coralproject/talk into reject-username 2018-06-04 22:30:49 +02:00
Chi Vinh Le 636bc9efa4 Manage cache 2018-06-04 22:30:12 +02:00
Kim Gardner 6f66ff6ccf Merge branch 'master' into nvmrc 2018-06-04 21:21:55 +01:00
Kim Gardner bdf5b0c6a1 Merge pull request #1677 from coralproject/version-bump
Version Bump
2018-06-04 21:21:25 +01:00
Wyatt Johnson ef86938a0e Update package.json 2018-06-04 13:45:49 -06:00
Wyatt Johnson 01f7aa52a0 Merge branch 'master' into nvmrc 2018-06-04 13:44:39 -06:00
Cristian Dean 6c73e5b818 Add scraper proxy support (#1674)
* Add scraper proxy support
* review: doc updates
2018-06-04 13:43:00 -06:00
Wyatt Johnson 3ac392e358 locked node version at 8 via nvmrc 2018-06-04 13:32:40 -06:00
Kim Gardner 18d58e4ea8 Remove pell from plugin directory (#1675)
* Remove pell from plugin directory

* review: removed pell refernce
2018-06-04 12:58:26 -06:00
okbel 2d343029a1 updated - getUserStatusArray 2018-06-04 15:25:30 -03:00
Kim Gardner 56d9fee486 Merge pull request #1673 from conversario/markdown-asset-closed-message-master
Use Markdown for the "asset is closed" message.
2018-06-04 17:31:51 +01:00
Kim Gardner cf98232109 Merge branch 'master' into markdown-asset-closed-message-master 2018-06-04 15:54:13 +01:00
Kim Gardner 01c19a9995 Merge pull request #1672 from coralproject/human-karma
Humanized Karma
2018-06-04 15:42:13 +01:00
Chi Vinh Le f142150b3e Fix unexpected error 2018-06-04 16:04:37 +02:00
Chi Vinh Le b783ee8c0b Ignore already exists error 2018-06-04 15:45:31 +02:00
Fabian Neumann 2f4dfa080b Use Markdown for the "asset is closed" message. 2018-06-04 15:25:20 +02:00
Wyatt Johnson 54b0ce27fb Merge branch 'master' into human-karma 2018-06-01 15:19:31 -06:00
Kim Gardner 0e8d410c98 Merge branch 'master' into reject-username 2018-06-01 22:19:18 +01:00
Wyatt Johnson d3158f5c7e GraphiQL Fixes (#1671)
* bug: fixed graphiql style, upgraded
2018-06-01 15:14:49 -06:00
Wyatt Johnson f580608a31 feat: humanize karma number 2018-06-01 15:13:58 -06:00
Kim Gardner fefd4d281a Merge branch 'master' into reject-username 2018-06-01 17:11:56 +01:00
okbel ed539d2ee1 Merge branch 'reject-username' of github.com:coralproject/talk into reject-username
* 'reject-username' of github.com:coralproject/talk:
  Small tweaks
  Small typo
  resolved custom css loading issue
  Update copy
  Update copy
  Translation Fixes
  Fix DropDown display bug
  Fixed double encoding
  fix to target handler
  Added missing translation pt_BR
  remove unused method
2018-06-01 12:34:50 -03:00
okbel 25852d68bd updated styles 2018-06-01 12:06:53 -03:00
Wyatt Johnson 795389c0c8 Merge pull request #1528 from cristiandean/master
Check if token is in localStorage before logout
2018-05-31 15:03:17 -06:00
Wyatt Johnson 66f2d41033 always call the rest logout command, don't vomit on the error 2018-05-31 14:56:18 -06:00
Wyatt Johnson 01a0d0d587 Merge branch 'master' into master 2018-05-31 14:12:00 -06:00
Belén Curcio 7262a9ab50 Merge branch 'master' into reject-username 2018-05-31 16:25:53 -03:00
Wyatt Johnson 189d27c13f Merge pull request #1668 from coralproject/translations
Translation Fixes
2018-05-31 13:00:33 -06:00
Kim Gardner eb7f2abd97 Small tweaks 2018-05-31 14:37:53 -04:00
Wyatt Johnson 250a8ec2f1 Merge branch 'master' into translations 2018-05-31 12:26:26 -06:00
Wyatt Johnson 375ef09d78 Merge pull request #1670 from coralproject/templates
Custom CSS Fix
2018-05-31 11:50:41 -06:00
Kim Gardner 135f0e300f Small typo 2018-05-31 13:33:34 -04:00
Wyatt Johnson 9d752685cd resolved custom css loading issue 2018-05-31 11:27:02 -06:00
Kim Gardner c5a6f8a0b3 Merge branch 'master' into reject-username 2018-05-31 11:23:21 -04:00
Kim Gardner b7768ccaf6 Update copy 2018-05-31 11:07:12 -04:00
Kim Gardner e68d5c8a14 Update copy 2018-05-31 11:03:17 -04:00
Wyatt Johnson f7899bc670 Translation Fixes
- Removed duplicated translations
- Fixed redundant text
2018-05-30 14:41:55 -06:00
Wyatt Johnson 5f74c827f5 Merge branch 'master' into reject-username 2018-05-30 10:15:35 -06:00
okbel 09ddd82800 Merge branch 'reject-username' of github.com:coralproject/talk into reject-username
* 'reject-username' of github.com:coralproject/talk:
2018-05-30 11:11:30 -03:00
okbel 87aa1af654 Merge branch 'master' of github.com:coralproject/talk into reject-username
* 'master' of github.com:coralproject/talk:
  Typo
  Fix warnings
  Activate Websocket and add browser console logging
  çhanges
  changes and updates to the e2e
  updated yarn.lock
  Workaround RTE Button focus bug on IOS
  Use standard buttons to fix IOS bug
  add missing dependency
2018-05-30 11:10:20 -03:00
Kim Gardner c3ffb13a79 Merge branch 'master' into reject-username 2018-05-29 15:27:19 -04:00
Belén Curcio ba8466bacf Merge branch 'master' into reject-username 2018-05-29 13:27:04 -03:00
okbel 595e19231e if username changed, reject username should be disabled 2018-05-29 13:24:53 -03:00
okbel 4f76163541 Adding translations 2018-05-29 13:00:24 -03:00
okbel 6f06ac760c Translations 2018-05-29 12:58:26 -03:00
okbel 25947cb278 Reject Username fully working 2018-05-29 12:41:54 -03:00
okbel b0d76f2f6a progress 2018-05-28 14:52:03 -03:00
okbel 6d35c648d6 updates 2018-05-28 12:13:02 -03:00
okbel b4e7dd54cf Merge branch 'master' of github.com:coralproject/talk into reject-username
* 'master' of github.com:coralproject/talk: (33 commits)
  apply @cvle suggestion for fix
  Take karma into account when doing e2e
  Bump version to 4.4.2
  move placeholder right behind contentEditable
  added pivotal tracker ref
  moved event handler to bundle
  Update stream.njk
  disable CSP until we can work on configuration + webpack issues
  added missing translation
  Remove redundant period. It's in the translation strings already.
  Remove redundancy
  One more missing German translation
  Add missing translations (en/de) for plugins ignore-user and local-auth.
  text-replace error
  support the __webpack_nonce__ parameter
  csp fixes
  added comment
  `karma` -> `karmaThresholds`
  name changes
  Fix Touch issues on IOS Safari (iPad)
  ...
2018-05-28 11:23:34 -03:00
Kim Gardner 6411c1cf0d Merge branch 'master' into reject-username 2018-05-25 14:16:02 -04:00
Belén Curcio 0f16d0a7a3 Merge branch 'master' into reject-username 2018-05-24 13:31:59 -03:00
Wyatt Johnson 1a29f374a5 Merge branch 'master' into reject-username 2018-05-16 11:20:28 -06:00
Kim Gardner 5911f3e28e Merge branch 'master' into master 2018-05-07 20:35:28 -04:00
Kim Gardner 040447833c Merge branch 'master' into master 2018-05-02 12:00:19 -04:00
Kim Gardner c60ed59a75 Merge branch 'master' into reject-username 2018-04-24 17:12:41 -04:00
Kim Gardner 976f92cb11 Merge branch 'master' into master 2018-04-24 14:12:46 -04:00
Cristian Dean ded8367b07 Merge branch 'master' into master 2018-04-18 10:04:49 -03:00
Wyatt Johnson 1071e86f96 Check if token is in localStorage before logout 2018-04-18 10:02:48 -03:00
Wyatt Johnson da68b222b7 Merge branch 'master' into reject-username 2018-04-17 13:10:45 -06:00
okbel 42ca5743aa flow checks 2018-04-16 16:07:00 -03:00
Wyatt Johnson c2b14fca10 fixed mutation 2018-04-13 14:34:39 -06:00
Wyatt Johnson 9e2b1c5af3 Merge branch 'master' into reject-username 2018-04-12 11:04:30 -06:00
okbel 8f0d05da2e Merge branch 'reject-username' of github.com:coralproject/talk into reject-username
* 'reject-username' of github.com:coralproject/talk: (36 commits)
  Authors cannot see their own status history
  Permission Updates
  Added User Roles to the menu
  Bullets replaced with asterisks per markdown
  Update 03-04-user-roles.md
  Rename 03-03-user-roles.md to 03-04-user-roles.md
  Create 03-03-user-roles.md
  Rename 03-06-product-guide-trust.md to 03-07-product-guide-trust.md
  Rename 03-05-product-guide-trust.md to 03-06-product-guide-trust.md
  Rename 03-04-product-guide-trust.md to 03-05-product-guide-trust.md
  Refactored Errors
  removed unused coral-docs
  updated version
  FrenchCorrections
  New version of the translation
  added missing translations
  add missing strings in fr locale
  improved support
  Logging Improvements
  Add Finnish translation
  ...
2018-04-12 00:14:21 -03:00
okbel 3d3a46070c Adding translations 2018-04-12 00:09:33 -03:00
Belén Curcio fbc551a76e Merge branch 'master' into reject-username 2018-04-11 23:58:10 -03:00
okbel 8563ddffd3 Adding functionality and design functionality 2018-04-11 23:55:50 -03:00
okbel 7955b28a75 Adding rejectUsername 2018-04-04 07:59:13 -03:00
263 changed files with 10225 additions and 7693 deletions
+5
View File
@@ -2,5 +2,10 @@
"env": {
"jest": true
},
"settings": {
"react": {
"version": "15.0"
}
},
"extends": "@coralproject/eslint-config-talk"
}
+2
View File
@@ -0,0 +1,2 @@
8
+4
View File
@@ -0,0 +1,4 @@
overrides:
- files: "bin/cli*"
options:
parser: babylon
+4 -76
View File
@@ -1,78 +1,6 @@
# Contributor Covenant Code of Conduct
# Mozilla Community Participation Guidelines
## Our Pledge
## Version 2.3 - Updated May 16, 2017
We expect everyone contributing to The Coral Project to follow this code of conduct. That means the team, contractors we employ, contributors, as well as anyone posting to our public or internal-facing channels.
We created it not because we anticipate any unacceptable 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 commit to enforce and evolve this code over the duration of the project.
## Expected behavior
* Be supportive of each other.
* Be collaborative. Involve others in brainstorms, sketching sessions, code reviews, planning documents, and the like. Its not only okay to ask for help or feedback often, its unacceptable not to do so.
* Be generous and kind in both giving and accepting critique. Critique is a natural and important part of our culture. Good critiques are kind, respectful, clear, and constructive, focused on goals and requirements rather than personal preferences. You are expected to give and receive criticism with grace.
* Be humane. Be polite and friendly in all forms of communication, especially remote communication, where opportunities for misunderstanding are greater. Use sarcasm carefully. Tone is hard to decipher online; make judicious use of emoji to aid in communication.
* Be considerate.
* Be tolerant.
* Respect peoples boundaries.
* Do not make it personal.
* Use welcoming and inclusive language.
* Offer to help if you see someone struggling or otherwise in need of assistance (taking care not to be patronizing or disrespectful).
* If someone approaches you looking for help, be generous with your time; if youre under a deadline, direct them to someone else who may be of assistance.
* Go out of your way to include people in jokes or memes, recognizing that we want to build an environment free of cliques.
* Show empathy towards other community members
## Unacceptable behavior
We are committed to providing a welcoming and safe environment for people of all races, gender identities, gender expressions, sexual orientations, physical abilities, physical appearances, socioeconomic backgrounds, nationalities, ages, religions, and beliefs.
We expect that you will refrain from demeaning, discriminatory, or harassing behavior and speech.
Harassment includes, but is not limited to: deliberate intimidation; stalking; unwanted photography or recording; sustained or willful disruption of talks or other events; inappropriate physical contact; use of sexual or discriminatory imagery, comments, or jokes; and unwelcome sexual attention.
Furthermore, any behavior or language which is unwelcoming—whether or not it rises to the level of harassment—is also strongly discouraged. Much exclusionary behavior takes the form of microaggressions—subtle put-downs which may be unconsciously delivered. Regardless of intent, microaggressions can have a significant negative impact on victims and have no place on our team.
Other inappropriate behavior:
* Threats
* Slurs
* Pornography
* Spam
* Bullying
* Copyright infringement
* Impersonation of someone else
* Violating someones privacy
If you feel that someone has harassed you or otherwise treated you or someone else inappropriately, please alert the project lead at [andrewl@mozillafoundation.org](mailto:andrewl@mozillafoundation.org).
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
These guidelines are ambitious, and were not always going to succeed in meeting them. When something goes wrong—whether its a microaggression or an instance of harassment — there are a number of things you can do to address the situation. Depending on your comfort level and the severity of the situation, here are some suggestions:
* Address it directly. If youre comfortable bringing up the incident with the person who instigated it, pull them aside to discuss how it affected you. Be sure to approach these conversations in a forgiving spirit: an angry or tense conversation will not do either of you any good. If youre unsure how to go about that, try discussing with your manager or with the people and culture team first—they might have some advice about how to make this conversation happen.
If youre too frustrated to have a direct conversation, there are a number of alternate routes you can take.
* Talk to a peer or mentor. Your colleagues are likely to have personal and professional experience on which to draw that could be of use to you. If you have someone youre comfortable approaching, reach out and discuss the situation with them. They may be able to advise on how they would handle it, or direct you to someone who can. The flip side of this, of course, is that you should also be available when your colleagues reach out to you.
* Contact the project lead, Andrew Losowsky, [andrewl@mozillafoundation.org](mailto:andrewl@mozillafoundation.org), or the technical lead. We will work with you to help you figure out how to ensure that any conflict doesnt interfere with your work, in confidence if you would prefer.
* Talk to Chris Lawrence. Chris oversees the project. He can be contacted at [clawrence@mozillafoundation.org](mailto:clawrence@mozillafoundation.org).
If you feel you have been unfairly accused of violating this code of conduct, you should contact Chris with a concise description of your grievance.
## Conclusion
We welcome your feedback on this and every other aspect of what we do as The Coral Project, and we thank you for working with us to make it a safe, enjoyable, and friendly experience for everyone involved in the project and what we do.
Above text is licensed CC BY-SA 4.0, adapted from the SRCCON code of conduct, FreeBSDs code of conduct, Vox Medias product team code of conduct, Mediums code of conduct, as well as adapted from the Contributor Covenant.
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/
+1
View File
@@ -6,6 +6,7 @@ ONBUILD ARG TALK_ASSET_COMMENTS_LOAD_DEPTH=10
ONBUILD ARG TALK_REPLY_COMMENTS_LOAD_DEPTH=3
ONBUILD ARG TALK_THREADING_LEVEL=3
ONBUILD ARG TALK_DEFAULT_STREAM_TAB=all
ONBUILD ARG TALK_DISABLE_EMBED_POLYFILL=FALSE
ONBUILD ARG TALK_DEFAULT_LANG=en
ONBUILD ARG TALK_WHITELISTED_LANGUAGES
ONBUILD ARG TALK_PLUGINS_JSON
+12 -10
View File
@@ -1,18 +1,12 @@
# Talk · [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk) · [![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) · [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md#pull-requests)
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/products/talk.html).
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 & Mozilla.
Built with <3 by The Coral Project.
## Try Talk!
## Getting Started
You're just one click away from trying Talk - all you need is a Heroku account and a few minutes of your time.
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://dashboard.heroku.com/new?template=https%3A%2F%2Fgithub.com%2Fcoralproject%2Ftalk&env[TALK_FACEBOOK_APP_ID]=ignore&env[TALK_FACEBOOK_APP_SECRET]=ignore)
## Technical Documentation
From getting up and running, to advanced configuration, to how to scale Talk, our [Talk Technical Docs](https://docs.coralproject.net/talk/) have everything you need to know.
Check out our Quickstart and Install guides to get started with Talk in our [Technical Docs](https://docs.coralproject.net/talk/).
## Product Guide
@@ -22,6 +16,14 @@ Learn more about Talk, including a deep dive into features for commenters and mo
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/).
## 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.
## 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.
## More Resources
- [Talk Product Roadmap](https://www.pivotaltracker.com/n/projects/1863625)
+4
View File
@@ -18,6 +18,10 @@
"value": "",
"required": true
},
"MAILGUN_SMTP_PASSWORD": {
"value": "",
"required": true
},
"NODE_ENV": "production",
"REWRITE_ENV": "TALK_MONGO_URL:MONGO_URI,TALK_REDIS_URL:REDIS_URL,TALK_SMTP_HOST:MAILGUN_SMTP_SERVER,TALK_SMTP_PORT:MAILGUN_SMTP_PORT,TALK_SMTP_USERNAME:MAILGUN_SMTP_LOGIN,TALK_SMTP_PASSWORD:MAILGUN_SMTP_PASSWORD",
"NPM_CONFIG_PRODUCTION": "false"
+37 -24
View File
@@ -143,9 +143,9 @@ async function reconcileRemotePlugins({ dryRun, upgradeRemote }) {
if (fetchable.length > 0) {
console.log(
`$ yarn add --ignore-scripts --ignore-workspace-root-check ${fetchable.map(
({ name, version }) => `${name}@${version}`.cyan
)}`
`$ yarn add --ignore-scripts --ignore-workspace-root-check ${fetchable
.map(({ name, version }) => `${name}@${version}`.cyan)
.join(' ')}`
);
if (!dryRun) {
@@ -162,7 +162,7 @@ async function reconcileRemotePlugins({ dryRun, upgradeRemote }) {
if (output.status) {
throw new Error(
'Could not install external plugins, errors occured during install'
'Could not install external plugins, errors occurred during install'
);
}
@@ -189,7 +189,7 @@ async function reconcileRemotePlugins({ dryRun, upgradeRemote }) {
if (output.status) {
throw new Error(
'Could not install external plugins, errors occured during install'
'Could not install external plugins, errors occurred during install'
);
}
@@ -329,28 +329,41 @@ async function createSeedPlugin() {
if (answers.addPluginsJson) {
const pluginsJson = path.resolve(__dirname, '..', 'plugins.json');
fs
.readJson(pluginsJson)
.then(j => {
// This is a client-side plugin, let's push this.
if (answers.client) {
j.client.push(answers.pluginName);
let j;
try {
j = await fs.readJson(pluginsJson);
} catch (err) {
// Fallback to plugins.default.json if the plugins.json file does not
// exist.
const defaultPluginsJson = path.resolve(
__dirname,
'..',
'plugins.default.json'
);
const output = JSON.stringify(j, null, 2);
fs.writeFileSync(pluginsJson, output);
}
try {
j = await fs.readJson(defaultPluginsJson);
} catch (err) {
// Fallback to an empty one if that also, doesn't exist.
j = { client: [], server: [] };
}
}
// This is a server-side plugin, let's push this.
if (answers.server) {
j.server.push(answers.pluginName);
// This is a client-side plugin, let's push this.
if (answers.client) {
j.client.push(answers.pluginName);
const output = JSON.stringify(j, null, 2);
fs.writeFileSync(pluginsJson, output);
}
})
.catch(err => {
console.error(err);
});
const output = JSON.stringify(j, null, 2);
fs.writeFileSync(pluginsJson, output);
}
// This is a server-side plugin, let's push this.
if (answers.server) {
j.server.push(answers.pluginName);
const output = JSON.stringify(j, null, 2);
fs.writeFileSync(pluginsJson, output);
}
}
console.log(
+4 -1
View File
@@ -49,7 +49,10 @@ async function revokeToken(tokenID) {
async function createToken(userID, tokenName) {
try {
let { pat: { id }, jwt } = await TokensService.create(userID, tokenName);
let {
pat: { id },
jwt,
} = await TokensService.create(userID, tokenName);
console.log(`Created Token[${id}] for User[${userID}] = ${jwt}`);
@@ -0,0 +1,14 @@
import {
SHOW_REJECT_USERNAME_DIALOG,
HIDE_REJECT_USERNAME_DIALOG,
} from '../constants/rejectUsernameDialog';
export const showRejectUsernameDialog = ({ userId, username }) => ({
type: SHOW_REJECT_USERNAME_DIALOG,
userId,
username,
});
export const hideRejectUsernameDialog = () => ({
type: HIDE_REJECT_USERNAME_DIALOG,
});
@@ -76,7 +76,7 @@ ActionsMenu.propTypes = {
icon: PropTypes.string,
children: PropTypes.node,
className: PropTypes.string,
label: PropTypes.string,
label: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
buttonClassNames: PropTypes.string,
};
@@ -15,7 +15,7 @@ const ActionsMenuItem = props => (
ActionsMenuItem.propTypes = {
className: PropTypes.string,
children: PropTypes.string,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
};
export default ActionsMenuItem;
+11 -12
View File
@@ -5,6 +5,7 @@ import styles from './NotFound.css';
import { Button, TextField, Alert, Success } from 'coral-ui';
import Recaptcha from 'react-recaptcha';
import cn from 'classnames';
import t from 'coral-framework/services/i18n';
class AdminLogin extends React.Component {
constructor(props) {
@@ -41,13 +42,13 @@ class AdminLogin extends React.Component {
{errorMessage && <Alert>{errorMessage}</Alert>}
<TextField
id="email"
label="Email Address"
label={t('login.email_address')}
value={this.state.email}
onChange={e => this.setState({ email: e.target.value })}
/>
<TextField
id="password"
label="Password"
label={t('login.password')}
value={this.state.password}
onChange={e => this.setState({ password: e.target.value })}
type="password"
@@ -60,10 +61,10 @@ class AdminLogin extends React.Component {
full
onClick={this.handleSignIn}
>
Sign In
{t('login.sign_in_button')}
</Button>
<p className={styles.forgotPasswordCTA}>
Forgot your password?{' '}
{t('login.forgot_password')}{' '}
<a
href="#"
className={styles.forgotPasswordLink}
@@ -72,7 +73,7 @@ class AdminLogin extends React.Component {
this.setState({ requestPassword: true });
}}
>
Request a new one.
{t('login.request_passowrd')}
</a>
</p>
{loginMaxExceeded && (
@@ -95,14 +96,14 @@ class AdminLogin extends React.Component {
>
{this.props.passwordRequestSuccess}{' '}
<a className={styles.signInLink} href="#">
Sign in
{t('login.sign_in')}
</a>
<Success />
</p>
) : (
<form onSubmit={this.handleRequestPassword}>
<TextField
label="Email Address"
label={t('login.email_address')}
value={this.state.email}
onChange={e => this.setState({ email: e.target.value })}
/>
@@ -112,17 +113,15 @@ class AdminLogin extends React.Component {
full
onClick={this.handleRequestPassword}
>
Reset Password
{t('login.reset_password')}
</Button>
</form>
);
return (
<Layout fixedDrawer restricted={true}>
<div className={cn(styles.loginLayout, 'talk-admin-login')}>
<h1 className={styles.loginHeader}>Team sign in</h1>
<p className={styles.loginCTA}>
Sign in to interact with your community.
</p>
<h1 className={styles.loginHeader}>{t('login.team_sign_in')}</h1>
<p className={styles.loginCTA}>{t('login.sign_in_message')}</p>
{this.state.requestPassword ? requestPasswordForm : signInForm}
</div>
</Layout>
@@ -19,7 +19,9 @@ class BanUserDialog extends React.Component {
}
handleMessageChange = e => {
const { target: { value: message } } = e;
const {
target: { value: message },
} = e;
this.setState({ message });
};
@@ -1,20 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './CountBadge.css';
import t from 'coral-framework/services/i18n';
import { humanizeNumber } from 'coral-framework/helpers/numbers';
const CountBadge = ({ count }) => {
let number = count;
// shorten large counts to abbreviations
if (number / 1e9 > 1) {
number = `${(number / 1e9).toFixed(1)}${t('modqueue.billion')}`;
} else if (number / 1e6 > 1) {
number = `${(number / 1e6).toFixed(1)}${t('modqueue.million')}`;
} else if (number / 1e3 > 1) {
number = `${(number / 1e3).toFixed(1)}${t('modqueue.thousand')}`;
}
number = humanizeNumber(number);
return <span className={styles.count}>{number}</span>;
};
@@ -43,21 +43,21 @@ class ForgotPassword extends React.Component {
<form onSubmit={this.handleSubmit}>
{errorMessage && <Alert>{errorMessage}</Alert>}
<TextField
label="Email Address"
label={t('login.email_address')}
value={email}
onChange={this.handleEmailChange}
/>
<Button type="submit" cStyle="black" full>
Reset Password
{t('login.reset_password')}
</Button>
<p className={styles.cta}>
Go back to{' '}
{t('login.go_back')}{' '}
<a
href="#"
className={styles.signInLink}
onClick={this.handleSignInLink}
>
Sign In
{t('login.sign_in')}
</a>
.
</p>
+11 -2
View File
@@ -77,13 +77,22 @@ const CoralHeader = ({
<MenuItem onClick={() => showShortcuts(true)}>
{t('configure.shortcuts')}
</MenuItem>
<MenuItem>
<a
href="https://docs.coralproject.net/talk/how-talk-works/"
target="_blank"
rel="noopener noreferrer"
>
{t('configure.product_guide_link')}
</a>
</MenuItem>
<MenuItem>
<a
href="https://github.com/coralproject/talk/releases"
target="_blank"
rel="noopener noreferrer"
>
View latest version
{t('configure.view_last_version')}
</a>
</MenuItem>
<MenuItem>
@@ -92,7 +101,7 @@ const CoralHeader = ({
target="_blank"
rel="noopener noreferrer"
>
Report a bug or give feedback
{t('configure.report_bug_or_feedback')}
</a>
</MenuItem>
{currentUser && (
@@ -27,7 +27,9 @@ class KarmaTooltip extends React.Component {
};
render() {
const { thresholds: { unreliable } } = this.props;
const {
thresholds: { unreliable },
} = this.props;
const { menuVisible } = this.state;
return (
@@ -68,6 +70,7 @@ class KarmaTooltip extends React.Component {
className={styles.link}
href={t('user_detail.karma_docs_link')}
target="_blank"
rel="noopener noreferrer"
>
{t('user_detail.learn_more')}
</a>
+3 -2
View File
@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
import styles from './Login.css';
import Layout from 'coral-admin/src/components/Layout';
import cn from 'classnames';
import t from 'coral-framework/services/i18n';
class LoginContainer extends Component {
renderForm() {
@@ -19,8 +20,8 @@ class LoginContainer extends Component {
return (
<Layout fixedDrawer restricted={true}>
<div className={cn(styles.layout, 'talk-admin-login')}>
<h1 className={styles.header}>Team sign in</h1>
<p className={styles.cta}>Sign in to interact with your community.</p>
<h1 className={styles.header}>{t('login.team_sign_in')}</h1>
<p className={styles.cta}>{t('login.sign_in_message')}</p>
{this.renderForm()}
</div>
</Layout>
@@ -0,0 +1,80 @@
.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;
}
.close {
display: block;
position: absolute;
top: 24px;
right: 20px;
}
.closeButton {
font-size: 24px;
line-height: 14px;
color: #363636;
&:hover {
color: #6b6b6b;
}
}
.legend {
padding: 0;
font-weight: bold;
}
div.radioGroup {
margin-top: 6px;
}
label.radioGroup {
&:global(.is-checked) > :global(.mdl-radio__outer-circle),
> :global(.mdl-radio__outer-circle) {
border-color: #212121;
}
> :global(.mdl-radio__inner-circle) {
background: #212121;
}
> :global(.mdl-radio__label) {
font-size: 14px;
line-height: 20px;
}
}
.messageInput {
border-radius: 3px;
width: 100%;
padding: 10px;
font-size: 14px;
box-sizing: border-box;
}
.cancel {
margin-right: 5px;
}
.perform {
min-width: 90px;
}
.buttons {
margin-top: 8px;
margin-bottom: 6px;
text-align: right;
}
@@ -0,0 +1,138 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Dialog, BareButton } from 'coral-ui';
import styles from './RejectUsernameDialog.css';
import cn from 'classnames';
import { RadioGroup, Radio } from 'react-mdl';
import Button from 'coral-ui/components/Button';
import { username as flagReason } from 'coral-framework/graphql/flagReasons';
import t from 'coral-framework/services/i18n';
const initialState = { reason: flagReason.offensive, message: '' };
class RejectUsernameDialog extends React.Component {
state = initialState;
componentWillReceiveProps(next) {
if (this.props.open && !next.open) {
this.setState(initialState);
}
}
handleReasonChange = event => {
this.setState({ reason: event.target.value });
};
handleMessageChange = event => {
this.setState({ message: event.target.value });
};
handlePerform = () => {
this.props.onPerform({
reason: this.state.reason,
message: this.state.message,
});
};
render() {
const { open, onCancel } = this.props;
const { reason, message } = this.state;
return (
<Dialog
className={cn(styles.dialog, 'talk-admin-reject-username-dialog')}
id="rejectUsernameDialog"
onCancel={onCancel}
open={open}
>
<div className={styles.close}>
<BareButton
aria-label="Close"
onClick={onCancel}
className={styles.closeButton}
>
×
</BareButton>
</div>
<section className="talk-admin-reject-username-dialog-section">
<h1 className={styles.header}>
{t('reject_username_dialog.title')}: {this.props.username}
</h1>
<p className={styles.description}>
{t('reject_username_dialog.description')}
</p>
<fieldset>
<legend className={styles.legend}>
{t('reject_username_dialog.reason')}
</legend>
<RadioGroup
name="reason"
value={reason}
childContainer="div"
onChange={this.handleReasonChange}
className={styles.radioGroup}
>
<Radio value={flagReason.offensive}>
{t('flag_reasons.username.offensive')}
</Radio>
<Radio value={flagReason.nolike}>
{t('flag_reasons.username.nolike')}
</Radio>
<Radio value={flagReason.impersonating}>
{t('flag_reasons.username.impersonating')}
</Radio>
<Radio value={flagReason.spam}>
{t('flag_reasons.username.spam')}
</Radio>
<Radio value={flagReason.other}>
{t('flag_reasons.username.other')}
</Radio>
</RadioGroup>
{reason === flagReason.other && (
<fieldset>
<legend className={styles.legend}>
{t('reject_username_dialog.message')}
</legend>
<textarea
rows={5}
className={styles.messageInput}
value={message}
onChange={this.handleMessageChange}
/>
</fieldset>
)}
</fieldset>
<div className={styles.buttons}>
<Button
cStyle="white"
className={styles.cancel}
onClick={onCancel}
raised
>
{t('reject_username_dialog.cancel')}
</Button>
<Button
cStyle="black"
className={cn(
styles.perform,
'talk-admin-reject-username-dialog-continue'
)}
onClick={this.handlePerform}
raised
>
{t('reject_username_dialog.reject_username')}
</Button>
</div>
</section>
</Dialog>
);
}
}
RejectUsernameDialog.propTypes = {
open: PropTypes.bool.isRequired,
onPerform: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
username: PropTypes.string,
};
export default RejectUsernameDialog;
+6 -5
View File
@@ -5,6 +5,7 @@ import { Button, TextField, Alert } from 'coral-ui';
import cn from 'classnames';
import Recaptcha from 'coral-framework/components/Recaptcha';
import External from './External';
import t from 'coral-framework/services/i18n';
class SignIn extends React.Component {
recaptcha = null;
@@ -40,13 +41,13 @@ class SignIn extends React.Component {
{errorMessage && <Alert>{errorMessage}</Alert>}
<TextField
id="email"
label="Email Address"
label={t('login.email_address')}
value={email}
onChange={this.handleEmailChange}
/>
<TextField
id="password"
label="Password"
label={t('login.password')}
value={password}
onChange={this.handlePasswordChange}
type="password"
@@ -68,17 +69,17 @@ class SignIn extends React.Component {
cStyle="black"
full
>
Sign In
{t('login.sign_in')}
</Button>
<p className={styles.forgotPasswordCTA}>
{/* TODO: translate */}
Forgot your password?{' '}
{t('login.forgot_password')}{' '}
<a
href="#"
className={styles.forgotPasswordLink}
onClick={this.handleForgotPasswordLink}
>
Request a new one.
{t('login.request_passowrd')}
</a>
</p>
</form>
+127 -26
View File
@@ -6,7 +6,15 @@ import styles from './UserDetail.css';
import UserHistory from './UserHistory';
import { Slot } from 'coral-framework/components';
import UserDetailCommentList from '../components/UserDetailCommentList';
import { isSuspended, isBanned, getKarma } from 'coral-framework/utils/user';
import {
isSuspended,
isUsernameRejected,
isUsernameChanged,
isBanned,
getKarma,
} from 'coral-framework/utils/user';
import ButtonCopyToClipboard from './ButtonCopyToClipboard';
import ClickOutside from 'coral-framework/components/ClickOutside';
import {
@@ -23,6 +31,25 @@ import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
import UserInfoTooltip from './UserInfoTooltip';
import KarmaTooltip from './KarmaTooltip';
import t from 'coral-framework/services/i18n';
import { humanizeNumber } from 'coral-framework/helpers/numbers';
const filterOutLocalProfiles = ({ provider }) => provider !== 'local';
/**
* getUserStatusArray
* returns an array of active status(es)
* i.e if suspension is active, it returns suspension
*/
function getUserStatusArray(user) {
const statusMap = {
suspended: isSuspended,
banned: isBanned,
usernameRejected: isUsernameRejected,
usernameChanged: isUsernameChanged,
};
return Object.keys(statusMap).filter(k => statusMap[k](user));
}
class UserDetail extends React.Component {
changeTab = tab => {
@@ -41,6 +68,12 @@ class UserDetail extends React.Component {
username: this.props.root.user.username,
});
showRejectUsernameDialog = () =>
this.props.showRejectUsernameDialog({
userId: this.props.root.user.id,
username: this.props.root.user.username,
});
renderLoading() {
return (
<ClickOutside onClickOutside={this.props.hideUserDetail}>
@@ -61,18 +94,46 @@ class UserDetail extends React.Component {
);
}
getActionMenuLabel() {
const { root: { user } } = this.props;
getActionMenuLabel(user) {
const userStatusArr = getUserStatusArray(user);
const count = userStatusArr.length;
if (isBanned(user)) {
return 'Banned';
} else if (isSuspended(user)) {
return 'Suspended';
if (count > 1) {
return `Status (${count})`;
} else {
const activeStatus = userStatusArr[0];
switch (activeStatus) {
case 'suspended':
return t('user_detail.suspended');
case 'banned':
return t('user_detail.banned');
case 'usernameRejected':
return (
<span>
{t('user_detail.username')}
{` `}
<Icon name="cancel" />
</span>
);
case 'usernameChanged':
return (
<span>
{t('user_detail.username')}
{` `}
<Icon name="access_time" />
</span>
);
default:
return activeStatus;
}
}
return '';
}
goToReportedUsernames = () => {
const { router } = this.props;
router.push('/admin/community/flagged');
};
renderLoaded() {
const {
root,
@@ -100,7 +161,7 @@ class UserDetail extends React.Component {
} = this.props;
// if totalComments is 0, you're dividing by zero
let rejectedPercent = rejectedComments / totalComments * 100;
let rejectedPercent = (rejectedComments / totalComments) * 100;
if (rejectedPercent === Infinity || isNaN(rejectedPercent)) {
rejectedPercent = 0;
@@ -108,6 +169,8 @@ class UserDetail extends React.Component {
const banned = isBanned(user);
const suspended = isSuspended(user);
const usernameRejected = isUsernameRejected(user);
const usernameChanged = isUsernameChanged(user);
const slotPassthrough = {
root,
@@ -140,7 +203,7 @@ class UserDetail extends React.Component {
},
'talk-admin-user-detail-actions-button'
)}
label={this.getActionMenuLabel()}
label={this.getActionMenuLabel(user)}
>
{suspended ? (
<ActionsMenuItem onClick={() => unsuspendUser({ id: user.id })}>
@@ -167,6 +230,27 @@ class UserDetail extends React.Component {
{t('user_detail.ban')}
</ActionsMenuItem>
)}
{usernameChanged && (
<ActionsMenuItem onClick={this.goToReportedUsernames}>
{t('user_detail.username_needs_approval')}
{` `}
<Icon name="launch" />
</ActionsMenuItem>
)}
{usernameRejected && !usernameChanged ? (
<ActionsMenuItem disabled>
{t('user_detail.username_rejected')}
</ActionsMenuItem>
) : (
<ActionsMenuItem
onClick={this.showRejectUsernameDialog}
disabled={me.id === user.id || usernameChanged}
>
{t('user_detail.reject_username')}
</ActionsMenuItem>
)}
</ActionsMenu>
)}
@@ -180,6 +264,18 @@ class UserDetail extends React.Component {
<div>
<ul className={styles.userDetailList}>
<li className={styles.userDetailItem}>
<Icon name="perm_identity" />
<span className={styles.userDetailItem}>
{t('user_detail.id')}:
</span>
{user.id}{' '}
<ButtonCopyToClipboard
className={styles.copyButton}
icon="content_copy"
copyText={user.id}
/>
</li>
<li className={styles.userDetailItem}>
<Icon name="assignment_ind" />
<span className={styles.userDetailItem}>
@@ -201,20 +297,22 @@ class UserDetail extends React.Component {
/>
</li>
{user.profiles.map(({ provider, id }) => (
<li key={id} className={styles.userDetailItem}>
<Icon name="device_hub" />
<span className={styles.userDetailItem}>
{capitalize(provider)} {t('user_detail.id')}:
</span>
{id}{' '}
<ButtonCopyToClipboard
className={styles.copyButton}
icon="content_copy"
copyText={id}
/>
</li>
))}
{user.profiles
.filter(filterOutLocalProfiles)
.map(({ provider, id }) => (
<li key={id} className={styles.userDetailItem}>
<Icon name="device_hub" />
<span className={styles.userDetailItem}>
{capitalize(provider)} {t('user_detail.id')}:
</span>
{id}{' '}
<ButtonCopyToClipboard
className={styles.copyButton}
icon="content_copy"
copyText={id}
/>
</li>
))}
</ul>
<ul className={styles.stats}>
@@ -243,7 +341,7 @@ class UserDetail extends React.Component {
styles[getKarma(user.reliable.commenter)]
)}
>
{user.reliable.commenterKarma}
{humanizeNumber(user.reliable.commenterKarma)}
</span>
</div>
<KarmaTooltip thresholds={karmaThresholds.comment} />
@@ -358,6 +456,7 @@ class UserDetail extends React.Component {
}
UserDetail.propTypes = {
router: PropTypes.object.isRequired,
userId: PropTypes.string.isRequired,
hideUserDetail: PropTypes.func.isRequired,
root: PropTypes.object.isRequired,
@@ -374,11 +473,13 @@ UserDetail.propTypes = {
selectedCommentIds: PropTypes.array.isRequired,
viewUserDetail: PropTypes.any.isRequired,
loadMore: PropTypes.any.isRequired,
showRejectUsernameDialog: PropTypes.func,
showSuspendUserDialog: PropTypes.func,
showBanUserDialog: PropTypes.func,
unbanUser: PropTypes.func.isRequired,
unsuspendUser: PropTypes.func.isRequired,
modal: PropTypes.bool,
rejectUsername: PropTypes.func.isRequired,
};
export default UserDetail;
@@ -114,6 +114,7 @@ class UserDetailComment extends React.Component {
className={styles.external}
href={`${comment.asset.url}?commentId=${comment.id}`}
target="_blank"
rel="noopener noreferrer"
>
<Icon name="open_in_new" /> {t('comment.view_context')}
</a>
@@ -10,7 +10,10 @@ import ApproveButton from './ApproveButton';
const UserDetailCommentList = props => {
const {
root,
root: { user, comments: { nodes, hasNextPage } },
root: {
user,
comments: { nodes, hasNextPage },
},
acceptComment,
rejectComment,
selectedCommentIds,
@@ -0,0 +1,2 @@
export const SHOW_REJECT_USERNAME_DIALOG = 'SHOW_REJECT_USERNAME_DIALOG';
export const HIDE_REJECT_USERNAME_DIALOG = 'HIDE_REJECT_USERNAME_DIALOG';
@@ -77,7 +77,10 @@ const mapDispatchToProps = dispatch => ({
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
withBanUser,
withSetCommentStatus
)(BanUserDialogContainer);
+6 -1
View File
@@ -6,6 +6,7 @@ import Login from '../containers/Login';
import { FullLoading } from '../components/FullLoading';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import RejectUsernameDialog from './RejectUsernameDialog';
import { toggleModal as toggleShortcutModal } from '../actions/moderation';
import { logout } from 'coral-framework/actions/auth';
import { can } from 'coral-framework/services/perms';
@@ -41,6 +42,7 @@ class LayoutContainer extends React.Component {
>
<BanUserDialog />
<SuspendUserDialog />
<RejectUsernameDialog />
<UserDetail />
{children}
</Layout>
@@ -79,4 +81,7 @@ const mapDispatchToProps = dispatch =>
dispatch
);
export default connect(mapStateToProps, mapDispatchToProps)(LayoutContainer);
export default connect(
mapStateToProps,
mapDispatchToProps
)(LayoutContainer);
@@ -0,0 +1,92 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import RejectUsernameDialog from '../components/RejectUsernameDialog';
import { hideRejectUsernameDialog } from '../actions/rejectUsernameDialog';
import {
withRejectUsername,
withPostFlag,
} from 'coral-framework/graphql/mutations';
import { notify } from 'coral-framework/actions/notification';
import { compose } from 'react-apollo';
import { getErrorMessages } from 'coral-framework/utils';
class RejectUsernameDialogContainer extends Component {
rejectUsername = async ({ reason, message }) => {
const {
postFlag,
rejectUsername,
hideRejectUsernameDialog,
userId,
} = this.props;
// First flag the user.
try {
await postFlag({
item_id: userId,
item_type: 'USERS',
reason,
message,
});
} catch (error) {
// Ignore already exists error, otherwise show error.
if (
error.errors &&
(error.errors.length !== 1 ||
error.errors[0].translation_key !== 'ALREADY_EXISTS')
) {
notify('error', getErrorMessages(error));
}
}
await rejectUsername(userId);
hideRejectUsernameDialog();
};
render() {
return (
<RejectUsernameDialog
open={this.props.open}
onPerform={this.rejectUsername}
onCancel={this.props.hideRejectUsernameDialog}
username={this.props.username}
/>
);
}
}
RejectUsernameDialogContainer.propTypes = {
rejectUsername: PropTypes.func.isRequired,
hideRejectUsernameDialog: PropTypes.func,
open: PropTypes.bool,
userId: PropTypes.string,
username: PropTypes.string,
};
const mapStateToProps = ({
rejectUsernameDialog: { open, userId, username },
}) => ({
open,
userId,
username,
});
const mapDispatchToProps = dispatch => ({
...bindActionCreators(
{
hideRejectUsernameDialog,
notify,
},
dispatch
),
});
export default compose(
connect(
mapStateToProps,
mapDispatchToProps
),
withRejectUsername,
withPostFlag({ notifyOnError: false })
)(RejectUsernameDialogContainer);
+4 -1
View File
@@ -55,4 +55,7 @@ SignInContainer.propTypes = {
requireRecaptcha: PropTypes.bool.isRequired,
};
export default compose(withSignIn, withPopupAuthHandler)(SignInContainer);
export default compose(
withSignIn,
withPopupAuthHandler
)(SignInContainer);
@@ -86,7 +86,10 @@ const mapDispatchToProps = dispatch => ({
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
withSuspendUser,
withSetCommentStatus,
withOrganizationName
@@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import UserDetail from '../components/UserDetail';
import withQuery from 'coral-framework/hocs/withQuery';
import { withRouter } from 'react-router';
import {
getDefinitionName,
getSlotFragmentSpreads,
@@ -21,11 +22,14 @@ import {
withSetCommentStatus,
withUnbanUser,
withUnsuspendUser,
withRejectUsername,
withPostFlag,
} from 'coral-framework/graphql/mutations';
import UserDetailComment from './UserDetailComment';
import update from 'immutability-helper';
import { showBanUserDialog } from 'actions/banUserDialog';
import { showSuspendUserDialog } from 'actions/suspendUserDialog';
import { showRejectUsernameDialog } from 'actions/rejectUsernameDialog';
const commentConnectionFragment = gql`
fragment CoralAdmin_UserDetail_CommentConnection on CommentConnection {
@@ -131,6 +135,7 @@ class UserDetailContainer extends React.Component {
loading={loading}
error={this.props.data && this.props.data.error}
loadMore={this.loadMore}
rejectUsername={this.props.rejectUsername}
{...this.props}
/>
);
@@ -148,6 +153,7 @@ UserDetailContainer.propTypes = {
selectedCommentIds: PropTypes.array,
unbanUser: PropTypes.func.isRequired,
unsuspendUser: PropTypes.func.isRequired,
rejectUsername: PropTypes.func.isRequired,
userId: PropTypes.string,
};
@@ -275,6 +281,7 @@ const mapDispatchToProps = dispatch => ({
{
showBanUserDialog,
showSuspendUserDialog,
showRejectUsernameDialog,
changeTab,
clearUserDetailSelections,
toggleSelectCommentInUserDetail,
@@ -287,9 +294,15 @@ const mapDispatchToProps = dispatch => ({
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
withUserDetailQuery,
withSetCommentStatus,
withUnbanUser,
withUnsuspendUser
withUnsuspendUser,
withRejectUsername,
withPostFlag,
withRouter
)(UserDetailContainer);
+32 -62
View File
@@ -66,7 +66,11 @@ export default {
});
},
}),
SuspendUser: ({ variables: { input: { id, until } } }) => ({
SuspendUser: ({
variables: {
input: { id, until },
},
}) => ({
update: proxy => {
const fragmentId = `User_${id}`;
@@ -92,7 +96,11 @@ export default {
});
},
}),
UnsuspendUser: ({ variables: { input: { id } } }) => ({
UnsuspendUser: ({
variables: {
input: { id },
},
}) => ({
update: proxy => {
const fragmentId = `User_${id}`;
const data = proxy.readFragment({
@@ -117,7 +125,11 @@ export default {
});
},
}),
BanUser: ({ variables: { input: { id } } }) => ({
BanUser: ({
variables: {
input: { id },
},
}) => ({
update: proxy => {
const fragmentId = `User_${id}`;
const data = proxy.readFragment({
@@ -142,7 +154,11 @@ export default {
});
},
}),
UnbanUser: ({ variables: { input: { id } } }) => ({
UnbanUser: ({
variables: {
input: { id },
},
}) => ({
update: proxy => {
const fragmentId = `User_${id}`;
const data = proxy.readFragment({
@@ -194,6 +210,12 @@ export default {
},
updateQueries: {
TalkAdmin_Community_FlaggedAccounts: (prev, { mutationResult }) => {
// No need to update, when user was not in the flagged users queue.
// TODO: this should be more generic, e.g. looking at the history.
if (!prev.flaggedUsers.nodes.find(node => node.id === id)) {
return prev;
}
const decrement = {
flaggedUsernamesCount: { $apply: count => count - 1 },
};
@@ -214,35 +236,6 @@ export default {
return updated;
},
},
update: proxy => {
proxy.writeFragment({
fragment: gql`
fragment Talk_ApproveUsername on User {
state {
status {
username {
status
}
}
}
}
`,
id: `User_${id}`,
data: {
__typename: 'User',
state: {
__typename: 'UserState',
status: {
__typename: 'UserStatus',
username: {
__typename: 'UsernameStatus',
status: 'APPROVED',
},
},
},
},
});
},
}),
RejectUsername: ({ variables: { id } }) => ({
optimisticResponse: {
@@ -254,6 +247,12 @@ export default {
},
updateQueries: {
TalkAdmin_Community_FlaggedAccounts: (prev, { mutationResult }) => {
// No need to update, when user was not in the flagged users queue.
// TODO: this should be more generic, e.g. looking at the history.
if (!prev.flaggedUsers.nodes.find(node => node.id === id)) {
return prev;
}
const decrement = {
flaggedUsernamesCount: { $apply: count => count - 1 },
};
@@ -274,35 +273,6 @@ export default {
return updated;
},
},
update: proxy => {
proxy.writeFragment({
fragment: gql`
fragment Talk_RejectUsername on User {
state {
status {
username {
status
}
}
}
}
`,
id: `User_${id}`,
data: {
__typename: 'User',
state: {
__typename: 'UserState',
status: {
__typename: 'UserStatus',
username: {
__typename: 'UsernameStatus',
status: 'REJECTED',
},
},
},
},
});
},
}),
UpdateSettings: ({ variables: { input } }) => ({
updateQueries: {
+2
View File
@@ -5,10 +5,12 @@ import moderation from './moderation';
import install from './install';
import banUserDialog from './banUserDialog';
import suspendUserDialog from './suspendUserDialog';
import rejectUsernameDialog from './rejectUsernameDialog';
import userDetail from './userDetail';
import ui from './ui';
export default {
rejectUsernameDialog,
banUserDialog,
configure,
suspendUserDialog,
@@ -0,0 +1,29 @@
import {
SHOW_REJECT_USERNAME_DIALOG,
HIDE_REJECT_USERNAME_DIALOG,
} from '../constants/rejectUsernameDialog';
const initialState = {
open: false,
userId: null,
username: '',
};
export default function rejectUsernameDialog(state = initialState, action) {
switch (action.type) {
case SHOW_REJECT_USERNAME_DIALOG:
return {
...state,
open: true,
userId: action.userId,
username: action.username,
};
case HIDE_REJECT_USERNAME_DIALOG:
return {
...state,
open: false,
};
default:
return state;
}
}
@@ -71,7 +71,7 @@ class RejectUsernameDialog extends Component {
<Dialog
className={cn(
styles.suspendDialog,
'talk-admin-reject-username-dialog'
'talk-admin-reject-reported-username-dialog'
)}
id="rejectUsernameDialog"
open={open}
@@ -85,7 +85,7 @@ class RejectUsernameDialog extends Component {
<div
className={cn(
styles.container,
`talk-admin-reject-username-dialog-step-${stage}`
`talk-admin-reject-reported-username-dialog-step-${stage}`
)}
>
<div className={styles.description}>
@@ -101,7 +101,7 @@ class RejectUsernameDialog extends Component {
<div className={styles.emailContainer}>
<textarea
rows={5}
className={cn(styles.emailInput, 'talk-admin-reject-username-dialog-suspension-message')}
className={cn(styles.emailInput, 'talk-admin-reject-reported-username-dialog-suspension-message')}
value={this.state.email}
onChange={this.onEmailChange}/>
</div>
@@ -110,7 +110,7 @@ class RejectUsernameDialog extends Component {
<div
className={cn(
styles.modalButtons,
'talk-admin-reject-username-dialog-buttons'
'talk-admin-reject-reported-username-dialog-buttons'
)}
>
{Object.keys(stages[stage].options).map((key, i) => (
@@ -118,7 +118,7 @@ class RejectUsernameDialog extends Component {
key={i}
className={cn(
'talk-admin-username-dialog-button',
`talk-admin-reject-username-dialog-button-${key}`
`talk-admin-reject-reported-username-dialog-button-${key}`
)}
onClick={this.onActionClick(stage, i)}
>
@@ -46,7 +46,11 @@ class FlaggedAccountsContainer extends Component {
document: USERNAME_FLAGGED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { usernameFlagged: user } } }
{
subscriptionData: {
data: { usernameFlagged: user },
},
}
) => {
return handleFlaggedAccountsChange(prev, user, () => {
const msg = t(
@@ -62,7 +66,11 @@ class FlaggedAccountsContainer extends Component {
document: USERNAME_APPROVED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { usernameApproved: user } } }
{
subscriptionData: {
data: { usernameApproved: user },
},
}
) => {
return handleFlaggedAccountsChange(prev, user, () => {
const msg = t(
@@ -78,7 +86,11 @@ class FlaggedAccountsContainer extends Component {
document: USERNAME_REJECTED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { usernameRejected: user } } }
{
subscriptionData: {
data: { usernameRejected: user },
},
}
) => {
return handleFlaggedAccountsChange(prev, user, () => {
const msg = t(
@@ -96,7 +108,9 @@ class FlaggedAccountsContainer extends Component {
prev,
{
subscriptionData: {
data: { usernameChanged: { previousUsername, user } },
data: {
usernameChanged: { previousUsername, user },
},
},
}
) => {
@@ -297,7 +311,10 @@ const mapDispatchToProps = dispatch =>
);
export default compose(
connect(null, mapDispatchToProps),
connect(
null,
mapDispatchToProps
),
withApproveUsername,
withQuery(
gql`
@@ -15,7 +15,11 @@ class IndicatorContainer extends Component {
document: USERNAME_FLAGGED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { usernameFlagged: user } } }
{
subscriptionData: {
data: { usernameFlagged: user },
},
}
) => {
return handleIndicatorChange(prev, user);
},
@@ -24,7 +28,11 @@ class IndicatorContainer extends Component {
document: USERNAME_APPROVED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { usernameApproved: user } } }
{
subscriptionData: {
data: { usernameApproved: user },
},
}
) => {
return handleIndicatorChange(prev, user);
},
@@ -33,7 +41,11 @@ class IndicatorContainer extends Component {
document: USERNAME_REJECTED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { usernameRejected: user } } }
{
subscriptionData: {
data: { usernameRejected: user },
},
}
) => {
return handleIndicatorChange(prev, user);
},
@@ -42,7 +54,13 @@ class IndicatorContainer extends Component {
document: USERNAME_CHANGED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { usernameChanged: { user } } } }
{
subscriptionData: {
data: {
usernameChanged: { user },
},
},
}
) => {
return handleIndicatorChange(prev, user);
},
@@ -98,9 +116,15 @@ const fields = `
status {
username {
status
history {
status
}
}
}
}
action_summaries {
count
}
`;
const USERNAME_FLAGGED_SUBSCRIPTION = gql`
@@ -200,7 +200,10 @@ const SEARCH_QUERY = gql`
`;
export default compose(
connect(null, mapDispatchToProps),
connect(
null,
mapDispatchToProps
),
withSetUserRole,
withUnsuspendUser,
withUnbanUser,
@@ -19,6 +19,9 @@ const mapDispatchToProps = dispatch =>
);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
withRejectUsername
)(RejectUsernameDialog);
@@ -106,6 +106,23 @@ export function handleFlaggedAccountsChange(root, user, notify) {
}
}
export const wasUsernameReported = user => {
const previousStatus =
user.state.status.username.history[
user.state.status.username.history.length - 2
];
// Check for correct previous status
if (!['SET', 'CHANGES'].includes(previousStatus)) {
return false;
}
// Check for flags
if (user.action_summaries.every(as => as.count === 0)) {
return false;
}
return true;
};
/**
* Track indicator status
* @param {Object} root current state of the store
@@ -119,7 +136,9 @@ export function handleIndicatorChange(root, user) {
return incrementFlaggedUserCount(root);
case 'APPROVED':
case 'REJECTED':
return decrementFlaggedUserCount(root);
if (wasUsernameReported(user)) {
return decrementFlaggedUserCount(root);
}
default:
}
}
@@ -34,7 +34,7 @@ class EmbedLink extends Component {
`;
return (
<ConfigureCard title={'Embed Comment Stream'}>
<ConfigureCard title={t('configure.embed_comment_stream')}>
<p>{t('configure.copy_and_paste')}</p>
<textarea
rows={5}
@@ -3,7 +3,7 @@ import { SelectField, Option } from 'react-mdl-selectfield';
import t from 'coral-framework/services/i18n';
import styles from './StreamSettings.css';
import { Textfield } from 'react-mdl';
import { Icon, TextArea } from 'coral-ui';
import { Icon } from 'coral-ui';
import PropTypes from 'prop-types';
import Slot from 'coral-framework/components/Slot';
import MarkdownEditor from 'coral-framework/components/MarkdownEditor';
@@ -66,8 +66,8 @@ class StreamSettings extends React.Component {
this.props.updatePending({ updater });
};
updateClosedMessage = event => {
const updater = { closedMessage: { $set: event.target.value } };
updateClosedMessage = value => {
const updater = { closedMessage: { $set: value } };
this.props.updatePending({ updater });
};
@@ -178,7 +178,7 @@ class StreamSettings extends React.Component {
>
<p>{t('configure.closed_comments_desc')}</p>
<div>
<TextArea
<MarkdownEditor
className={styles.descriptionBox}
onChange={this.updateClosedMessage}
value={settings.closedMessage}
@@ -174,7 +174,10 @@ const mapDispatchToProps = dispatch =>
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
withUpdateSettings,
withConfigureQuery,
withMergedSettings('root.settings', 'pending', 'mergedSettings')
@@ -42,7 +42,10 @@ export default compose(
}
`,
}),
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
mapProps(({ root, settings, updatePending, errors, ...rest }) => ({
slotPassthrough: {
root,
@@ -37,7 +37,10 @@ export default compose(
}
`,
}),
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
mapProps(({ root, settings, updatePending, errors, ...rest }) => ({
slotPassthrough: {
root,
@@ -45,7 +45,10 @@ export default compose(
}
`,
}),
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
mapProps(({ root, settings, updatePending, errors, ...rest }) => ({
slotPassthrough: {
root,
@@ -39,7 +39,10 @@ export default compose(
}
`,
}),
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
mapProps(({ root, settings, updatePending, errors, ...rest }) => ({
slotPassthrough: {
root,
@@ -83,4 +83,7 @@ const mapDispatchToProps = dispatch =>
dispatch
);
export default connect(mapStateToProps, mapDispatchToProps)(InstallContainer);
export default connect(
mapStateToProps,
mapDispatchToProps
)(InstallContainer);
@@ -170,6 +170,7 @@ class Comment extends React.Component {
className={styles.external}
href={`${comment.asset.url}?commentId=${comment.id}`}
target="_blank"
rel="noopener noreferrer"
>
<Icon name="open_in_new" /> {t('comment.view_context')}
</a>
@@ -3,9 +3,13 @@ import PropTypes from 'prop-types';
import styles from './StorySearch.css';
import { Button, Spinner, Icon } from 'coral-ui';
import Story from './Story';
import t from 'coral-framework/services/i18n';
const StorySearch = props => {
const { root: { assets }, data: { loading } } = props;
const {
root: { assets },
data: { loading },
} = props;
if (!props.moderation.storySearchVisible) {
return null;
@@ -33,14 +37,14 @@ const StorySearch = props => {
onClick={props.search}
raised
>
Search
{t('streams.search')}
</Button>
</div>
<div className={styles.results}>
{props.assetId && (
<div className={styles.cta}>
<a onClick={props.goToModerateAll}>
Moderate comments on All Stories
{t('moderate_all_streams')}
</a>
</div>
)}
@@ -48,13 +52,15 @@ const StorySearch = props => {
{props.moderation.storySearchString ? (
<div className={styles.searchResults}>
<Icon name="search" />
<span className={styles.headlineRecent}>Search Results</span>
<span className={styles.headlineRecent}>
{t('streams.search_results')}
</span>
</div>
) : (
<div className={styles.searchResults}>
<Icon name="access_time" />
<span className={styles.headlineRecent}>
Most Recent Stories
{t('streams.most_recent_stories')}
</span>
</div>
)}
@@ -82,7 +88,9 @@ const StorySearch = props => {
)}
{assets.nodes.length === 0 && (
<div className={styles.noResults}>No results</div>
<div className={styles.noResults}>
{t('streams.search_results')}
</div>
)}
</div>
</div>
@@ -22,7 +22,11 @@ class IndicatorContainer extends Component {
document: COMMENT_ADDED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { commentAdded: comment } } }
{
subscriptionData: {
data: { commentAdded: comment },
},
}
) => {
return this.handleCommentChange(prev, comment);
},
@@ -31,7 +35,11 @@ class IndicatorContainer extends Component {
document: COMMENT_FLAGGED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { commentFlagged: comment } } }
{
subscriptionData: {
data: { commentFlagged: comment },
},
}
) => {
return this.handleCommentChange(prev, comment);
},
@@ -40,7 +48,11 @@ class IndicatorContainer extends Component {
document: COMMENT_EDITED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { commentEdited: comment } } }
{
subscriptionData: {
data: { commentEdited: comment },
},
}
) => {
return this.handleCommentChange(prev, comment);
},
@@ -49,7 +61,11 @@ class IndicatorContainer extends Component {
document: COMMENT_ACCEPTED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { commentAccepted: comment } } }
{
subscriptionData: {
data: { commentAccepted: comment },
},
}
) => {
return this.handleCommentChange(prev, comment);
},
@@ -58,7 +74,11 @@ class IndicatorContainer extends Component {
document: COMMENT_REJECTED_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { commentRejected: comment } } }
{
subscriptionData: {
data: { commentRejected: comment },
},
}
) => {
return this.handleCommentChange(prev, comment);
},
@@ -67,7 +87,11 @@ class IndicatorContainer extends Component {
document: COMMENT_RESET_SUBSCRIPTION,
updateQuery: (
prev,
{ subscriptionData: { data: { commentReset: comment } } }
{
subscriptionData: {
data: { commentReset: comment },
},
}
) => {
return this.handleCommentChange(prev, comment);
},
@@ -71,7 +71,9 @@ class ModerationContainer extends Component {
};
get activeTab() {
const { root: { asset, settings } } = this.props;
const {
root: { asset, settings },
} = this.props;
const id = getAssetId(this.props);
const tab = getTab(this.props);
@@ -94,7 +96,11 @@ class ModerationContainer extends Component {
variables,
updateQuery: (
prev,
{ subscriptionData: { data: { commentAdded: comment } } }
{
subscriptionData: {
data: { commentAdded: comment },
},
}
) => {
return this.handleCommentChange(prev, comment);
},
@@ -104,7 +110,11 @@ class ModerationContainer extends Component {
variables,
updateQuery: (
prev,
{ subscriptionData: { data: { commentAccepted: comment } } }
{
subscriptionData: {
data: { commentAccepted: comment },
},
}
) => {
const user =
comment.status_history[comment.status_history.length - 1]
@@ -125,7 +135,11 @@ class ModerationContainer extends Component {
variables,
updateQuery: (
prev,
{ subscriptionData: { data: { commentRejected: comment } } }
{
subscriptionData: {
data: { commentRejected: comment },
},
}
) => {
const user =
comment.status_history[comment.status_history.length - 1]
@@ -146,7 +160,11 @@ class ModerationContainer extends Component {
variables,
updateQuery: (
prev,
{ subscriptionData: { data: { commentReset: comment } } }
{
subscriptionData: {
data: { commentReset: comment },
},
}
) => {
const user =
comment.status_history[comment.status_history.length - 1]
@@ -167,7 +185,11 @@ class ModerationContainer extends Component {
variables,
updateQuery: (
prev,
{ subscriptionData: { data: { commentEdited: comment } } }
{
subscriptionData: {
data: { commentEdited: comment },
},
}
) => {
return this.handleCommentChange(prev, comment);
},
@@ -177,7 +199,11 @@ class ModerationContainer extends Component {
variables,
updateQuery: (
prev,
{ subscriptionData: { data: { commentFlagged: comment } } }
{
subscriptionData: {
data: { commentFlagged: comment },
},
}
) => {
return this.handleCommentChange(prev, comment);
},
@@ -289,7 +315,11 @@ class ModerationContainer extends Component {
};
render() {
const { root, root: { asset, settings }, data } = this.props;
const {
root,
root: { asset, settings },
data,
} = this.props;
const assetId = getAssetId(this.props);
if (assetId) {
@@ -546,7 +576,10 @@ const mapDispatchToProps = dispatch => ({
export default compose(
withQueueConfig(baseQueueConfig),
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
withSetCommentStatus,
withModQueueQuery
)(ModerationContainer);
@@ -112,4 +112,7 @@ export const withAssetSearchQuery = withQuery(
}
);
export default compose(withRouter, withAssetSearchQuery)(StorySearchContainer);
export default compose(
withRouter,
withAssetSearchQuery
)(StorySearchContainer);
@@ -113,4 +113,7 @@ StoriesContainer.propTypes = {
updateAssetState: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(StoriesContainer);
export default connect(
mapStateToProps,
mapDispatchToProps
)(StoriesContainer);
@@ -41,7 +41,13 @@ class EmbedContainer extends React.Component {
document: USER_BANNED_SUBSCRIPTION,
updateQuery: (
_,
{ subscriptionData: { data: { userBanned: { state } } } }
{
subscriptionData: {
data: {
userBanned: { state },
},
},
}
) => {
notify('info', t('your_account_has_been_banned'));
props.updateStatus(state.status);
@@ -51,7 +57,13 @@ class EmbedContainer extends React.Component {
document: USER_SUSPENDED_SUBSCRIPTION,
updateQuery: (
_,
{ subscriptionData: { data: { userSuspended: { state } } } }
{
subscriptionData: {
data: {
userSuspended: { state },
},
},
}
) => {
notify('info', t('your_account_has_been_suspended'));
props.updateStatus(state.status);
@@ -61,7 +73,13 @@ class EmbedContainer extends React.Component {
document: USERNAME_REJECTED_SUBSCRIPTION,
updateQuery: (
_,
{ subscriptionData: { data: { usernameRejected: { state } } } }
{
subscriptionData: {
data: {
usernameRejected: { state },
},
},
}
) => {
notify('info', t('your_username_has_been_rejected'));
props.updateStatus(state.status);
@@ -324,7 +342,10 @@ const mapDispatchToProps = dispatch =>
export default compose(
withPopupAuthHandler,
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
branch(props => !props.checkedInitialLogin, renderComponent(Spinner)),
withEmbedQuery
)(EmbedContainer);
+24 -4
View File
@@ -126,7 +126,9 @@ export default {
},
mutations: {
PostComment: ({
variables: { input: { asset_id, body, parent_id, tags = [] } },
variables: {
input: { asset_id, body, parent_id, tags = [] },
},
state: { auth },
}) => ({
optimisticResponse: {
@@ -193,7 +195,13 @@ export default {
updateQueries: {
CoralEmbedStream_Embed: (
prev,
{ mutationResult: { data: { createComment: { comment } } } }
{
mutationResult: {
data: {
createComment: { comment },
},
},
}
) => {
if (
(![ADMIN, MODERATOR].includes(prev.me.role) &&
@@ -208,7 +216,13 @@ export default {
},
CoralEmbedStream_Profile: (
prev,
{ mutationResult: { data: { createComment: { comment } } } }
{
mutationResult: {
data: {
createComment: { comment },
},
},
}
) => {
return update(prev, {
me: {
@@ -224,7 +238,13 @@ export default {
updateQueries: {
CoralEmbedStream_Embed: (
prev,
{ mutationResult: { data: { editComment: { comment } } } }
{
mutationResult: {
data: {
editComment: { comment },
},
},
}
) => {
if (
!['PREMOD', 'REJECTED', 'SYSTEM_WITHHELD'].includes(comment.status)
@@ -139,7 +139,10 @@ const mapDispatchToProps = dispatch =>
);
const enhance = compose(
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
withSettingsFragments,
withUpdateAssetSettings,
withMergedSettings('asset.settings', 'pending', 'mergedSettings')
@@ -34,7 +34,7 @@ class Comment extends React.Component {
{ [styles.countZero]: reactionCount === 0 }
)}
>
<Icon name="thumb_up" />
<Icon name="whatshot" />
<span
className={cn(
styles.reactionCount,
@@ -22,7 +22,14 @@ class CommentHistoryContainer extends Component {
limit: 5,
cursor: this.props.root.me.comments.endCursor,
},
updateQuery: (previous, { fetchMoreResult: { me: { comments } } }) => {
updateQuery: (
previous,
{
fetchMoreResult: {
me: { comments },
},
}
) => {
const updated = update(previous, {
me: {
comments: {
@@ -97,7 +104,10 @@ const mapStateToProps = state => ({
});
export default compose(
connect(mapStateToProps, null),
connect(
mapStateToProps,
null
),
withCommentHistoryFragments,
withFetchMore
)(CommentHistoryContainer);
@@ -80,6 +80,7 @@ const mapStateToProps = state => ({
currentUser: state.auth.user,
});
export default compose(connect(mapStateToProps), withProfileQuery)(
ProfileContainer
);
export default compose(
connect(mapStateToProps),
withProfileQuery
)(ProfileContainer);
@@ -55,7 +55,10 @@ export default compose(
${Settings.fragments.root}
`,
}),
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
withSlotElements({
slot: 'profileSettings',
propName: 'profileSettingsSlotElements',
@@ -117,8 +117,12 @@ export default class Comment extends React.Component {
}
componentWillReceiveProps(next) {
const { comment: { replies: prevReplies } } = this.props;
const { comment: { replies: nextReplies } } = next;
const {
comment: { replies: prevReplies },
} = this.props;
const {
comment: { replies: nextReplies },
} = next;
if (
prevReplies &&
nextReplies &&
@@ -243,7 +247,10 @@ export default class Comment extends React.Component {
}
loadNewReplies = () => {
const { comment: { replies, replyCount, id }, emit } = this.props;
const {
comment: { replies, replyCount, id },
emit,
} = this.props;
if (replyCount > replies.nodes.length) {
this.setState({ loadingState: 'loading' });
this.props
@@ -292,7 +299,11 @@ export default class Comment extends React.Component {
// getVisibileReplies returns a list containing comments
// which were authored by current user or comes before the `idCursor`.
getVisibileReplies() {
const { comment: { replies }, currentUser, liveUpdates } = this.props;
const {
comment: { replies },
currentUser,
liveUpdates,
} = this.props;
const idCursor = this.state.idCursors[0];
const userId = currentUser ? currentUser.id : null;
@@ -18,6 +18,7 @@ const ModerationLink = props =>
className="talk-embed-stream-moderation-link"
href={`${BASE_PATH}admin/moderate/${props.assetId}`}
target="_blank"
rel="noopener noreferrer"
>
{t('moderate_this_stream')}
</a>
@@ -4,11 +4,11 @@ import StreamError from './StreamError';
import Comment from '../containers/Comment';
import BannedAccount from '../../../components/BannedAccount';
import ChangeUsername from '../containers/ChangeUsername';
import Markdown from 'coral-framework/components/Markdown';
import Slot from 'coral-framework/components/Slot';
import InfoBox from './InfoBox';
import { can } from 'coral-framework/services/perms';
import ModerationLink from './ModerationLink';
import Markdown from 'coral-framework/components/Markdown';
import RestrictedMessageBox from 'coral-framework/components/RestrictedMessageBox';
import t, { timeago } from 'coral-framework/services/i18n';
import CommentBox from '../containers/CommentBox';
@@ -211,7 +211,10 @@ class Stream extends React.Component {
root,
appendItemArray,
asset,
asset: { comment: highlightedComment, settings: { questionBoxEnable } },
asset: {
comment: highlightedComment,
settings: { questionBoxEnable },
},
postComment,
notify,
updateItem,
@@ -298,7 +301,7 @@ class Stream extends React.Component {
) : (
<div>
{asset.isClosed ? (
<p>{asset.settings.closedMessage}</p>
<Markdown content={asset.settings.closedMessage} />
) : (
<Markdown content={asset.settings.disableCommentingMessage} />
)}
@@ -215,7 +215,10 @@ const mapStateToProps = state => ({
const enhance = compose(
withHooks(['preSubmit', 'postSubmit']),
connect(mapStateToProps, null)
connect(
mapStateToProps,
null
)
);
export default enhance(CommentBox);
@@ -33,4 +33,7 @@ const mapDispatchToProps = dispatch =>
dispatch
);
export default connect(null, mapDispatchToProps)(CommentNotFound);
export default connect(
null,
mapDispatchToProps
)(CommentNotFound);
@@ -54,7 +54,11 @@ class StreamContainer extends React.Component {
},
updateQuery: (
prev,
{ subscriptionData: { data: { commentEdited } } }
{
subscriptionData: {
data: { commentEdited },
},
}
) => {
// Ignore mutations from me.
// TODO: need way to detect mutations created by this client, and allow mutations from other clients.
@@ -87,7 +91,14 @@ class StreamContainer extends React.Component {
variables: {
assetId: this.props.asset.id,
},
updateQuery: (prev, { subscriptionData: { data: { commentAdded } } }) => {
updateQuery: (
prev,
{
subscriptionData: {
data: { commentAdded },
},
}
) => {
// Ignore mutations from me.
// TODO: need way to detect mutations created by this client, and allow mutations from other clients.
if (
@@ -488,7 +499,10 @@ const mapDispatchToProps = dispatch =>
export default compose(
withFragments(fragments),
withEmit,
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
withPostComment,
// `talk-plugin-flags` has a custom error handling logic.
withPostFlag({ notifyOnError: false }),
+10 -4
View File
@@ -17,19 +17,19 @@ export const checkLogin = () => (
if (!result.user) {
cleanAuthData(localStorage);
dispatch(checkLoginSuccess(null));
client.resetWebsocket();
return;
}
// Reset the websocket.
client.resetWebsocket();
dispatch(checkLoginSuccess(result.user));
pym.sendMessage('coral-auth-changed', JSON.stringify(result.user));
client.resetWebsocket();
})
.catch(error => {
if (error.status && error.status === 401 && localStorage) {
// Unauthorized.
cleanAuthData(localStorage);
client.resetWebsocket();
} else {
console.error(error);
}
@@ -96,7 +96,13 @@ export const logout = () => async (
_,
{ rest, client, pym, localStorage }
) => {
await rest('/auth', { method: 'DELETE' });
try {
await rest('/auth', { method: 'DELETE' });
} catch (err) {
// We ignore any REST related errors from the delete action, which may/may
// not have had a cookie/token attached to it. The logout action was still
// called, so we still want to cleanup.
}
// Clear the auth data persisted to localStorage.
cleanAuthData(localStorage);
@@ -78,7 +78,12 @@ function markLinks(body, keyPrefix) {
matches.forEach((match, i) => {
content.push(body.substring(index, match.index));
content.push(
<a key={`${keyPrefix}_${i}`} href={match.url} target="_blank">
<a
key={`${keyPrefix}_${i}`}
href={match.url}
target="_blank"
rel="noopener noreferrer"
>
{match.text}
</a>
);
+4 -1
View File
@@ -103,5 +103,8 @@ export default compose(
size: props => props.size,
defaultComponent: props => props.defaultComponent,
}),
connect(mapStateToProps, null)
connect(
mapStateToProps,
null
)
)(Slot);
@@ -242,6 +242,18 @@ export const withUnsuspendUser = withMutation(
}
);
const SetUsernameStatusFragment = gql`
fragment Talk_SetUsernameStatus on User {
state {
status {
username {
status
}
}
}
}
`;
export const withApproveUsername = withMutation(
gql`
mutation ApproveUsername($id: ID!) {
@@ -257,6 +269,27 @@ export const withApproveUsername = withMutation(
variables: {
id,
},
update: proxy => {
const fragmentId = `User_${id}`;
const data = {
__typename: 'User',
state: {
__typename: 'UserState',
status: {
__typename: 'UserStatus',
username: {
__typename: 'UsernameStatus',
status: 'APPROVED',
},
},
},
};
proxy.writeFragment({
fragment: SetUsernameStatusFragment,
id: fragmentId,
data,
});
},
});
},
}),
@@ -278,6 +311,27 @@ export const withRejectUsername = withMutation(
variables: {
id,
},
update: proxy => {
const fragmentId = `User_${id}`;
const data = {
__typename: 'User',
state: {
__typename: 'UserState',
status: {
__typename: 'UserStatus',
username: {
__typename: 'UsernameStatus',
status: 'REJECTED',
},
},
},
};
proxy.writeFragment({
fragment: SetUsernameStatusFragment,
id: fragmentId,
data,
});
},
});
},
}),
+22
View File
@@ -0,0 +1,22 @@
import t from 'coral-framework/services/i18n';
const DECIMAL_AMOUNT = 1;
export function humanizeNumber(number) {
const abs = Math.abs(number);
if (abs >= 1e9) {
number = `${(number / 1e9).toFixed(DECIMAL_AMOUNT)}${t(
'modqueue.billion'
)}`;
} else if (abs >= 1e6) {
number = `${(number / 1e6).toFixed(DECIMAL_AMOUNT)}${t(
'modqueue.million'
)}`;
} else if (abs >= 1e3) {
number = `${(number / 1e3).toFixed(DECIMAL_AMOUNT)}${t(
'modqueue.thousand'
)}`;
}
return number;
}
+4 -1
View File
@@ -2,5 +2,8 @@ import { connect } from 'react-redux';
export default (mapStateToProps, ...rest) => BaseComponent => {
BaseComponent.mapStateToProps = mapStateToProps;
return connect(mapStateToProps, ...rest)(BaseComponent);
return connect(
mapStateToProps,
...rest
)(BaseComponent);
};
@@ -95,7 +95,10 @@ const mapDispatchToProps = dispatch =>
bindActionCreators({ updateUsername, updateStatus }, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
connect(
mapStateToProps,
mapDispatchToProps
),
withSetUsernameMutation,
withSetUsername
);
+4 -1
View File
@@ -121,4 +121,7 @@ const withSignUp = hoistStatics(WrappedComponent => {
return WithSignUp;
});
export default compose(withSettingsQuery, withSignUp);
export default compose(
withSettingsQuery,
withSignUp
);
@@ -198,5 +198,11 @@ const mapStateToProps = state => ({
* })(MyComponent);
*/
export default settings => {
return compose(connect(mapStateToProps, null), createHOC(settings));
return compose(
connect(
mapStateToProps,
null
),
createHOC(settings)
);
};
+91
View File
@@ -0,0 +1,91 @@
import t from 'coral-framework/services/i18n';
/**
* createNotificationService returns a notification services based on pym.
* @param {func} condition callback that checks that the given argument is valid.
* @param {error} string error that is displayed when validation fails.
* @return {func} validator function
*/
export function createValidator(condition, error) {
return (v, values) => (condition(v, values) ? undefined : error);
}
/**
* composeValidators chains validators and runs them in sequence until
* one validator fails and returns an error message.
* @param {func[]} validators array of validator functions.
* @return {func} validator fuction
*/
export function composeValidators(...validators) {
return value =>
validators.reduce(
(error, validator) => error || validator(value),
undefined
);
}
/**
* required checks that the value is truthy.
* @return {func} validator fuction
*/
export const required = createValidator(v => !!v, t('validators.required'));
/**
* Confirm will check that this field has the same value as another.
* @param {string} key key of other form field to compare with.
* @param {error} string error that is displayed when validation fails.
* @return {func} validator fuction
*/
export const confirm = (key, error) =>
createValidator((v, data) => v === data[key], error);
/**
* Verify email.
* @return {func} validator fuction
*/
export const verifyEmail = createValidator(
email => /^.+@.+\..+$/.test(email),
t('validators.verify_email')
);
/**
* Confirm email.
* @param {string} key key of other form field to compare with.
* @return {func} validator fuction
*/
export const confirmEmail = key => confirm(key, t('validators.confirm_email'));
/**
* Verify password.
* @return {func} validator fuction
*/
export const verifyPassword = createValidator(
pass => /^(?=.{8,}).*$/.test(pass),
t('validators.verify_password')
);
/**
* Confirm password.
* @param {string} key key of other form field to compare with.
* @return {func} validator fuction
*/
export const confirmPassword = key =>
confirm(key, t('validators.confirm_password'));
/**
* Verify username.
* @return {func} validator fuction
*/
export const verifyUsername = createValidator(
username => /^[a-zA-Z0-9_]+$/.test(username),
t('validators.verify_username')
);
/**
* Verify organization name.
* @return {func} validator fuction
*/
export const verifyOrganizationName = createValidator(
org => /^[a-zA-Z0-9_ ]+$/.test(org),
t('validators.verify_organization_name')
);
+9 -2
View File
@@ -3,9 +3,11 @@ import {
ENABLE_PLUGINS_DEBUG,
DISABLE_PLUGINS_DEBUG,
} from '../constants/config';
import { LOGOUT } from '../constants/auth';
import { LOGOUT, SET_AUTH_TOKEN } from '../constants/auth';
const initialState = {};
const initialState = {
auth_token: null,
};
export default function config(state = initialState, action) {
switch (action.type) {
@@ -30,6 +32,11 @@ export default function config(state = initialState, action) {
...state,
auth_token: null,
};
case SET_AUTH_TOKEN:
return {
...state,
auth_token: action.token || null,
};
case MERGE_CONFIG:
return {
...state,
+4 -8
View File
@@ -42,17 +42,13 @@ const getAuthToken = (store, storage) => {
let state = store.getState();
if (state.config && state.config.auth_token) {
// if an auth_token exists in config, use it.
return state.config.auth_token;
// If the embed is called with `embed.login(token)`, and the browser is not
// 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) {
// Use local storage auth tokens where there's a stable api.
return storage.getItem('token');
} else if (state.auth && state.auth.token) {
// Use the redux token state if the remaining methods fall out. If the embed
// is called with `embed.login(token)`, and the browser is not capable of
// storing the token in localStorage, then we would have persisted it to the
// redux state.
return state.auth.token;
}
return null;
+5 -1
View File
@@ -9,7 +9,11 @@ export function createReduxEmitter(eventEmitter) {
// Handle apollo actions.
if (action.type.startsWith('APOLLO_')) {
if (action.type === 'APOLLO_SUBSCRIPTION_RESULT') {
const { operationName, variables, result: { data } } = action;
const {
operationName,
variables,
result: { data },
} = action;
eventEmitter.emit(`subscription.${operationName}.data`, {
variables,
data,
+18
View File
@@ -35,6 +35,24 @@ export const isBanned = user => {
return get(user, 'state.status.banned.status');
};
/**
* isUsernameRejected
* retrieves boolean based on the username status
*/
export const isUsernameRejected = user => {
return get(user, 'state.status.username.status') === 'REJECTED';
};
/**
* isUsernameChanged
* retrieves boolean based on the username status
*/
export const isUsernameChanged = user => {
return get(user, 'state.status.username.status') === 'CHANGED';
};
/**
* canUsernameBeUpdated
* retrieves boolean whether a username can be updated or not
+6 -1
View File
@@ -64,7 +64,9 @@ const CONFIG = {
process.env.TALK_LOGGING_LEVEL
)
? process.env.TALK_LOGGING_LEVEL
: process.env.NODE_ENV === 'test' ? 'fatal' : 'info',
: process.env.NODE_ENV === 'test'
? 'fatal'
: 'info',
// REVISION_HASH when using the docker build will contain the build hash that
// it was built at.
@@ -204,6 +206,9 @@ const CONFIG = {
// STATIC_URI is the base uri where static files are hosted.
STATIC_URI: process.env.TALK_STATIC_URI || process.env.TALK_ROOT_URL,
// SCRAPER_PROXY_URL is the url to be used as a proxy by the scraper
SCRAPER_PROXY_URL: process.env.TALK_SCRAPER_PROXY_URL || null,
// The keepalive timeout (in ms) that should be used to send keep alive
// messages through the websocket to keep the socket alive.
KEEP_ALIVE: process.env.TALK_KEEP_ALIVE || '30s',
+11 -1
View File
@@ -90,6 +90,8 @@ sidebar:
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
@@ -100,7 +102,7 @@ sidebar:
children:
- title: Authentication
url: /integrating/authentication/
- title: Asset Managment
- title: Asset Management
url: /integrating/asset-management/
- title: Configuring the Comment Stream
url: /integrating/configuring-comment-stream/
@@ -162,10 +164,18 @@ sidebar:
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/
+2 -2
View File
@@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"hexo": {
"version": "3.6.0"
"version": "3.7.1"
},
"scripts": {
"start": "hexo serve",
@@ -24,4 +24,4 @@
"devDependencies": {
"nodemon": "^1.14.12"
}
}
}
+2 -2
View File
@@ -6,7 +6,7 @@ permalink: /
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/products/talk.html). The
goals [here](https://coralproject.net/talk). The
documentation available here is pertaining to the technical details for
installing, configuring, and deploying Talk.
@@ -48,7 +48,7 @@ be used to setup Talk:
version: '2'
services:
talk:
image: coralproject/talk:latest
image: coralproject/talk:4.5
restart: always
ports:
- "3000:3000"
@@ -43,7 +43,7 @@ be used to setup Talk:
version: '2'
services:
talk:
image: coralproject/talk:latest
image: coralproject/talk:4.5
restart: always
ports:
- "3000:3000"
@@ -121,7 +121,7 @@ base installation with additional custom plugins. Images can be created with the
most basic of `Dockerfile`'s:
```docker
FROM coralproject/talk:latest-onbuild
FROM coralproject/talk:4.5-onbuild
```
And running the following to build the docker image:
@@ -153,7 +153,7 @@ your containerized infrastructure. The versioning of our Docker tags as well
lets you do something like:
```docker
FROM coralproject/talk:4.0-onbuild
FROM coralproject/talk:4.5-onbuild
```
Which would pin your image to `4.0` release's.
Which would pin your image to `4.5.x release's.
+75
View File
@@ -0,0 +1,75 @@
---
title: Pre-Launch Checklist
permalink: /pre-launch-checklist/
---
- [ ] Where do you plan to host Talk?
- On your own bare metal servers
- In the cloud:
- [AWS](/talk/planning-architecture)
- [Google Cloud](/talk/planning-architecture/)
- [Digital Ocean](/talk/planning-architecture/)
- [ ] Do you have a domain name for Talk?
- **Recommended**: You should host Talk on a subdomain on your main site (e.g. if your site is mysitefornews.org, you should serve Talk from a subdomain like talk.mysitefornews.org) to avoid issues with third-party cookie sharing.
- [ ] Do you have a MongoDB instance?
- A MongoDB [Docker instance](/talk/installation-from-docker/) hosted alongside Talk?
- In an external MongoDB cluster?
- [MongoDB Atlas](https://www.mongodb.com/cloud/atlas)
- [mLab](https://mlab.com/)
- [ ] Do you need to migrate comments from a legacy system? We currently support Disqus, Livefyre, and Civil Comments.
- Use the [Talk Import](https://github.com/coralproject/talk-importer) framework
- [ ] Do you want to provide single sign-on (SSO) by integrating with an external auth system?
- See [Authenticating with Talk](/talk/integrating/authentication/)
- [ ] Do you want to use Social sign-on?
- Facebook
- Install [talk-plugin-facebook-auth](/talk/plugin/talk-plugin-facebook-auth/)
- Google
- Install [talk-plugin-google-auth](/talk/plugin/talk-plugin-google-auth/)
- Other
- See [Authenticating with Talk](/talk/integrating/authentication/)
- [ ] Do you want to use our Toxic Comments Plugin to help you automatically moderate comments based on their likelihood of being toxic?
- [Request a free API Key from Google](https://github.com/conversationai/perspectiveapi/blob/master/quickstart.md)
- Install [talk-plugin-toxic-comments](/talk/plugin/talk-plugin-toxic-comments/)
- [ ] Do you want to automatically prevent spam using the Akismet Spam Detection Plugin?
- [Request / pay for API Key from WordPress](https://akismet.com/)
- Install [talk-plugin-akismet](/talk/plugin/talk-plugin-akismet/)
- [ ] Do you want to setup Email Notifications?
- See [Notifications](/talk/integrating/notifications/)
- [ ] Do you want to setup rich text (bold, italics, quotes)?
- Install [talk-plugin-rich-text](/talk/plugin/talk-plugin-rich-text)
- [ ] Do you want to display comment counts?
- Use the GraphQL [CommentCountQuery](https://docs.coralproject.net/talk/api/graphql/#CommentCountQuery)
- Install [talk-plugin-deep-reply-count](/talk/plugin/talk-plugin-deep-reply-count) if necessary.
- [ ] Do you want to translate Talk to a different language?
- See [Translations and i18n](/talk/integrating/translations-i18n)
- [ ] Do you want to send all new comments or all reported comments to a Slack channel?
- See [our blog for more information](https://coralproject.net/blog/slacking-on/)
- [ ] Has your community team configured Talk to match your community strategy?
- See [our tutorial for more information](https://docs.coralproject.net/talk/when-youve-installed-talk/)
@@ -60,6 +60,10 @@ When `TRUE`, disables flagging of comments that match the suspect word filter. (
## TALK_DISABLE_EMBED_POLYFILL
This is a **Build Variable** and must be consumed during build. If using the
[Docker-onbuild](/talk/installation-from-docker/#onbuild)
image you can specify it with `--build-arg TALK_DISABLE_EMBED_POLYFILL=TRUE`.
When set to `TRUE`, the build process will not include the
[babel-polyfill](https://babeljs.io/docs/usage/polyfill/)
in the embed.js target that is loaded on the page that loads the embed. (Default
@@ -568,3 +572,7 @@ A JSON string representing the configuration passed to the
[fetch](https://www.npmjs.com/package/node-fetch) call for the scraper. It
can be used to set an authorization header, or change the user agent. (Default
`{}`)
## TALK_SCRAPER_PROXY_URL
Sets a specific HTTP/S proxy to be used by the Asset Scraper using [https-proxy-agent](https://www.npmjs.com/package/https-proxy-agent). (Default `null`)
@@ -19,7 +19,7 @@ Talk (0.8 or 80% is the default), which works like this:
queues
Read more about Corals take on toxicity
[on our blog](https://blog.coralproject.net/toxic-avenging/).
[on our blog](https://coralproject.net/blog/toxic-avenging/).
## What is the Perspective API?
@@ -19,7 +19,7 @@ Allows toggling of pre-moderating comments that have links.
### Ask Readers a Question & Question Icons
Admins can choose to Ask Readers a Question in order to help guide the
discussion. [Read more about why this is important on our blog](https://blog.coralproject.net/the-empty-box/).
discussion. [Read more about why this is important on our blog](https://coralproject.net/blog/the-empty-box/).
There are a selection of icons to display different messaging other than a
question on a particular stream, like an announcement, or general information
+15 -15
View File
@@ -24,7 +24,7 @@ If you don't launch with a clear strategy for your community, the most disruptiv
In Talk, the summary of your community code goes into the box at the top of the comments. You enter that text by clicking on the Configure tab at the top, and scroll down to Include Comment Stream Description:
![[IMAGE] A screenshot of the Configure options in Talk, with a pink arrow pointing to the place where Comment Stream Description can be added](http://blog.coralproject.net/wp-content/uploads/2018/03/streamdescription.png)
![[IMAGE] A screenshot of the Configure options in Talk, with a pink arrow pointing to the place where Comment Stream Description can be added](http://coralproject.net/wp-content/uploads/2018/03/streamdescription.png)
### 3. Decide where you will and won't put comments
@@ -38,19 +38,19 @@ Your plans might include:
* Watching the comments on these articles carefully, and posting a public note using the 'Ask a Question' box stating that you will delete comments, suspend/ban people, or close comments altogether if the conversation devolves (and then following through on that.) Here's where you can add that text from the article page:
![[IMAGE] A screenshot of the Configure menu from the comments view](http://blog.coralproject.net/wp-content/uploads/2018/03/ask2.png)  
![[IMAGE] A screenshot of the Configure menu from the comments view](http://coralproject.net/wp-content/uploads/2018/03/ask2.png)  
* Setting the comments on these articles to pre-moderation, then writing a note in the Ask a Question box stating that all comments have to be approved before publication; The Washington Post does this in some breaking news situations. You can set the comments on a single story to pre-moderation via the Configure tab on the article's comments. 
![[IMAGE] A screenshot of the pre-moderate option in the Configure tab](http://blog.coralproject.net/wp-content/uploads/2018/03/premod.png)
![[IMAGE] A screenshot of the pre-moderate option in the Configure tab](http://coralproject.net/wp-content/uploads/2018/03/premod.png)
* Not allowing comments on these topics at all. Following a series of racist responses, for instance, the CBC in Canada chose not to open comments at all on articles about indigenous Canadians. That's a perfectly valid response if you don't have the resources to ensure a civil discussion if people really want to talk about it, they can go elsewhere.In this situation, we recommend publishing a note at the bottom of all articles where this applies, explaining your decision and directing people to where they can send letters to the editor or continue the discussion off site, so that your community members understand what is happening.
* Not allowing comments on these topics at all. Following a series of racist responses, for instance, the CBC in Canada chose not to open comments at all on articles about indigenous Canadians. That's a perfectly valid response if you don't have the resources to ensure a civil discussion if people really want to talk about it, they can go elsewhere. In this situation, we recommend publishing a note at the bottom of all articles where this applies, explaining your decision and directing people to where they can send letters to the editor or continue the discussion off site, so that your community members understand what is happening.
Other options for contentious topics instead of comments:
* Use a form to request answers on a specific question related to the article, and select only the best answers for display. This is how the Spotlight team at Boston Globe solicited responses to their series on racism in Boston, using our Ask tool. ([Learn more about Ask here.](https://coralproject.net/products/ask.html)) This also allows people to submit their thoughts anonymously.
* Use a form to request answers on a specific question related to the article, and select only the best answers for display. This is how the Spotlight team at Boston Globe solicited responses to their series on racism in Boston, using our Ask tool. ([Learn more about Ask here.](https://coralproject.net/ask)) This also allows people to submit their thoughts anonymously.
* Host a focused, more controlled online discussion about the topic over a fixed period of time, during which you apply more vigilant moderation than usual. You can write a note in the Ask A Question space described above to make that clear, such as "We will host an online conversation about this topic here on Friday between 11am and 3pm EST. Moderators will be present throughout, and our journalists will answer your questions about the topic."This allows community members to approach the topic more calmly than their initial reactions on reading the story, in a space where you can dedicate temporary, more intensive resources to ensuring civility (perhaps with pre-moderation), while still signaling your commitment to engagement around an important topic.
* Host a focused, more controlled online discussion about the topic over a fixed period of time, during which you apply more vigilant moderation than usual. You can write a note in the Ask A Question space described above to make that clear, such as "We will host an online conversation about this topic here on Friday between 11am and 3pm EST. Moderators will be present throughout, and our journalists will answer your questions about the topic. "This allows community members to approach the topic more calmly than their initial reactions on reading the story, in a space where you can dedicate temporary, more intensive resources to ensuring civility (perhaps with pre-moderation), while still signaling your commitment to engagement around an important topic.
### 4. Have clear moderation strategies
@@ -62,26 +62,26 @@ The most important predictors of the success of an online community are:
#### Quickly:
* Create Banned/Suspect word lists specific to your needs, to prevent the worst words being posted, and to auto-report comments you should keep an eye on.![](http://blog.coralproject.net/wp-content/uploads/2018/03/banned-suspect.jpg)
* Create Banned/Suspect word lists specific to your needs, to prevent the worst words being posted, and to auto-report comments you should keep an eye on.![](http://coralproject.net/wp-content/uploads/2018/03/banned-suspect.jpg)
**We have a starter list of more than 1700 words/phrases that most sites choose to ban.** Email support@coralproject.net to request it.
* If you don't have a very high comment volume and use Slack in your newsroom, you could integrate Slack moderation to keep tabs on new/reported comments. [Read more about our free Slack moderation plugin.](https://blog.coralproject.net/slacking-on/)
* If you don't have a very high comment volume and use Slack in your newsroom, you could integrate Slack moderation to keep tabs on new/reported comments. [Read more about our free Slack moderation plugin.](https://coralproject.net/blog/slacking-on/)
* Utilize [our Toxic Comments plugin](https://blog.coralproject.net/toxic-avenging/), developed with Google Jigsaw, to improve commenter behavior and use AI to help identify and prevent the most abusive comments from appearing on your site.
* Utilize [our Toxic Comments plugin](https://coralproject.net/blog/toxic-avenging/), developed with Google Jigsaw, to improve commenter behavior and use AI to help identify and prevent the most abusive comments from appearing on your site.
* Enable our [Akismet plugin](/talk/plugin/talk-plugin-akismet) to keep spam from appearing in your comments.
* Use keyboard shortcuts in the moderation queue to moderate quickly (type '?' in the moderation view to see the list of shortcuts), and if there is a sudden deluge of comments, ask for someone in your newsroom to help you moderate. Talk will notify you in the moderation interface if someone else moderates a comment that's already on your screen.
* Click on a community member's name in the moderation interface to review all their comments, and see if there is a clear pattern of abuse among their Rejected comments. You can also select all their recent comments and delete them in bulk.![A screenshot of the moderation interface with the user drawer on display. Pink arrows indicate the user name, which can be clicked to open the drawer, and the drawer itself.](http://blog.coralproject.net/wp-content/uploads/2018/03/userdrawer.jpg)
* Click on a community member's name in the moderation interface to review all their comments, and see if there is a clear pattern of abuse among their Rejected comments. You can also select all their recent comments and delete them in bulk.![A screenshot of the moderation interface with the user drawer on display. Pink arrows indicate the user name, which can be clicked to open the drawer, and the drawer itself.](http://coralproject.net/wp-content/uploads/2018/03/userdrawer.jpg)
* Give people who need to step away from the conversation a 'time out' by suspending their account via the User drawer (click on the user's name anywhere in the moderation interface), and write a personalized note to them to explain why you took this action. Ban any users whose behavior is clearly offensive and/or abusive to an extreme degree.
![A close up of the User Drawer with a large pink arrow indicating the Actions menu where users can be suspended or banned](http://blog.coralproject.net/wp-content/uploads/2018/03/suspend-ban.jpg)
![A close up of the User Drawer with a large pink arrow indicating the Actions menu where users can be suspended or banned](http://coralproject.net/wp-content/uploads/2018/03/suspend-ban.jpg)
* Reject the worst comments on the article page itself by using the small caret in the corner of each comment.
![A screenshot of the comments stream with a moderation menu popped out and a large pink arrow pointing to the caret where moderators can unfold the moderation menu](http://blog.coralproject.net/wp-content/uploads/2018/03/caret-mod.jpg)
![A screenshot of the comments stream with a moderation menu popped out and a large pink arrow pointing to the caret where moderators can unfold the moderation menu](http://coralproject.net/wp-content/uploads/2018/03/caret-mod.jpg)
* If you find new commenters are causing a lot of trouble with their first comments, consider changing [the User Karma threshold](/talk/trust/) from negative one to zero, forcing every new commenter's first comment into pre-moderation.
@@ -92,9 +92,9 @@ The most important predictors of the success of an online community are:
* Publicly discourage behavior in the comments that doesn't cross the line but suggests that the tone or focus could shift quickly in a direction you don't want. Point to relevant sections of your community guidelines. [Read more about defining and discouraging this kind of behavior here.](https://guides.coralproject.net/manage-a-successful-community/)
* If people are sharing links to conspiracy sites or other unreliable sources, consider setting either the article (via the Configure tab on the article page) or the whole site (via Configure in the Admin view) to Pre-moderate Links, and delete any comments that link to sites that exceed your guidelines.
![An image showing the Pre-moderate Links option in the moderation console](http://blog.coralproject.net/wp-content/uploads/2018/03/premodlinks-site.jpg)
![An image showing the Pre-moderate Links option in the moderation console](http://coralproject.net/wp-content/uploads/2018/03/premodlinks-site.jpg)
![A screenshot of the Configure tab in the comments stream with the Pre-Moderate Links option circled in pink](http://blog.coralproject.net/wp-content/uploads/2018/03/premodllinks-article.jpg)
![A screenshot of the Configure tab in the comments stream with the Pre-Moderate Links option circled in pink](http://coralproject.net/wp-content/uploads/2018/03/premodllinks-article.jpg)
* If the conversation is getting out of hand, set the article to Pre-moderation (via the Configure tab on the comments, see above) and tell your community members you've done it, via Ask a Question (see above). If the conversation doesn't improve, or you feel that it is beyond redemption, consider closing the article to comments altogether via the Configure tab or the Stories tab in the moderation view, and telling the community why you have done so.
@@ -139,4 +139,4 @@ For the first month or so, we recommend including a link to the launch article m
A regular space for conversation about the conversation is always welcome in any successful community, and is a great source of ideas for improvement.  
**_We hope you enjoy using Talk, and that it helps your communities to thrive. If you have any questions or suggestions for this piece, or would like to try Talk on your site, please [contact us.](https://coralproject.net/contact.html)_**  
**_We hope you enjoy using Talk, and that it helps your communities to thrive. If you have any questions or suggestions for this piece, or would like to try Talk on your site, please [contact us.](https://coralproject.net/contact)_**  
+70
View File
@@ -0,0 +1,70 @@
---
title: Migrating from v3.x.x
permalink: /migration/3/
---
## Deprecation Notices
It was previously recommended to use the user service function:
```js
Users.findOrCreateExternalUser(...);
```
If you are developing a social plugin, you should migrate this function to:
```js
Users.upsertSocialUser(...);
```
If you are developing an external auth integration (where the integration
provides) a custom displayName, you should migrate to:
```js
Users.upsertExternalUser(...);
```
## Troubleshooting Username Status
You may be affected by a side-effect of the above mentioned deprecated function
`Users.findOrCreateExternalUser(...);` if the following are true:
1. You have upgraded from Talk `< 3` to `>= 4` and have completed a database
migration
2. You have used a custom auth plugin in the past
3. You have disabled or not included the `talk-plugin-auth` as a `client` plugin
4. You have received reports that some users can not comment, and are instead
given a message `You are not authorized to perform this action.`
If this is the case, you can execute the following one time MongoDB query to
repair the affected users.
```js
db.users.update(
{
"status.username.status": {
$in: ["UNSET", "CHANGED"]
}
},
{
$set: {
"status.username.status": "SET"
},
$push: {
"status.username.history": {
status: "SET",
assigned_by: null,
created_at: ISODate()
}
}
},
{
multi: true
}
);
```
**Note: You must resolve and/or update your custom auth code to resolve the
above mentioned deprecation notices _before_ running the above mentioned MongoDB
query**
+58 -6
View File
@@ -3,15 +3,67 @@ title: FAQ
permalink: /faq/
---
## How can I get help integrating Talk into my newsroom?
## How can I get help, submit a bug, or suggest a feature?
We're here to help with newsrooms of all sizes. Email our Integration Engineer
([jeff@mozillafoundation.org](mailto:jeff@mozillafoundation.org)) to set up a meeting.
There are a few avenues to get in touch with us and others in the community for help.
## How do I request a feature or submit a bug?
To log a bug or request a feature, submit a Support ticket ([support@coralproject.net](mailto:support@coralproject.net)) and someone from our team will get back to you.
The best way is to [submit a Github issue](https://github.com/coralproject/talk/issues). Make sure you give plenty of details, our Core Team can usually respond within a few hours on weekdays.
You can also request help on Github by [submitting an issue](https://github.com/coralproject/talk/issues). This also increases your chances of having someone from the community respond to help.
And you can also search our [Coral Community](https://community.coralproject.net) to see if your issue has been solved, or to get tips from the community.
## How can our dev team contribute to Talk?
We are lucky to work with newsroom dev teams and individual contributors who span the world, and come from newsrooms of all sizes. You can read our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md) to get started, but feel free to reach out to us via Github, or get in touch directly with Jeff via jeff@mozillafoundation.org.
We are lucky to work with newsroom dev teams and individual contributors who span the world, and come from newsrooms of all sizes. You can read our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md) to get started, but feel free to reach out to us via Github too.
## What if we want to add a feature you don't have?
Talk is open source, so you're free to develop additional functionality and [submit a pull request](https://github.com/coralproject.net/talk).
## Do you have GDPR features?
Yes! Please read our [GDPR documentation](/talk/integrating/gdpr/) for more information and instructions to get started.
## Can I import my existing comments?
Yes! We have a community-supported [import framework](https://github.com/coralproject/talk-importer) that you can use to migrate your existing comments.
## What support is available?
Our team is small, so it's difficult for us to provide support packages. However, you can always email us at [support@coralproject.net](mailto:support@coralproject.net), and we can help answer your questions. In some cases, we can provide premium support packages either with our team, or through partners. You can inquire about this via the support email address above.
## Is there a hosted version I can purchase by monthly subscription?
Yes! We are happy to announce that as of July 2018, we provide a SaaS version of Talk, called the Coral Cloud. For a monthly subscription, you get your own hosted Talk instance to embed on your news and blog articles. [Reach out to us](mailto:support@coralproject.net) if you're interested in this option.
## Where is our data when we use Talk?
If you are hosting Talk on your own:
* Your data is stored in a MongoDB database that you provide
* The Coral Team doesnt have any access to your data
If you are using Coral Cloud Hosting for Talk:
* Your data is stored in a dedicated MongDB database that is provisioned for your in the Cloud
* Your data is completely isolated from other customers data
* The Coral Team and its third party database hosting providers use strict access controls and auditing to protected your data from unauthorized access by team members
## Does Talk have any automated moderation features to protect against spam and trolling?
Talk features a couple of plugins that provide advanced moderation:
* The [Toxic Comments plugin](/talk/plugin/talk-plugin-toxic-comments) integrates with the [Perspective API from Google](https://www.perspectiveapi.com/) to detect the likelihood of toxicity of comments in real-time
* The [Akismet plugin](/talk/plugin/talk-plugin-akismet) detects and blocks spam comments
## How much can I customize Talk?
* The CSS of the Talk comment stream can be customized by [adding your own CSS via an external stylesheet](/talk/integrating/styling-css/)
* The functionality of Talk can be [extended through the plugin framework](/talk/plugins/)
## How much does Talk cost?
* The Talk software is freely available under the Apache 2.0 open source license
* Associated costs are those for the infrastructure required to run Talk (i.e. cloud hosting fee or bare-metal server costs)
* The Coral Project offers a SaaS version of Talk. Please [get in touch with us](mailto:support@coralproject.net) to discuss pricing for your custom requirements.
+40
View File
@@ -0,0 +1,40 @@
---
title: Troubleshooting Tips
permalink: /troubleshooting-tips/
---
## How do I find out what version I'm running?
If you visit https://<YOUR TALK INSTANCE>/api/v1, it will return the version you're running and the hash for the latest commit.
## I've installed Talk but I can't see the comment stream appear on my articles
* Make sure you've adding the correct domains to your Permitted Domains in Configure > Tech Settings
* Make sure you've correctly added the embed via your CMS to your article pages
* Check the console for any errors and you can file a bug via [support](mailto:support@coralproject.net) if you can't resolve the issue
## My commenters are reporting they can't see the comment stream, but I'm able to
* If this seems to be isolated to one commenter, it could be related to 3rd party cookies
* Post 4.x Talk doesn't require 3rd party cookies to be enabled. Check the version of Talk you're using and you might consider upgrading.
* A quick fix in the meantime is to ask them to allow 3rd party cookies
* You could also try asking them to clear their browser cache
## My commenters are reporting that they cannot login to Talk
If you're using your own custom auth plugin:
* Review the code and your server logs to ensure your plugin is working correctly. Check [our auth docs](/talk/integrating/authentication/) for more tips.
* Ensure that your JWT token settings, especially expiry, is being set correctly. You can troubleshoot JWT related issues with the [JWT Debugger](https://jwt.io/).
* See if you can isolate if it's a particular group of users that are experiencing this issue, e.g. mods, admins, subscribers? Confirm they have the appropriate permissions to comment.
* Note if this is a new issue that happened after an upgrade - did you read the [migration docs](/talk/migration/3/) and the [release notes](https://github.com/coralproject/talk/releases)? This might help you resolve the issue
* Confirm that users who are affected have the correct `username.status`. If users have status `UNSET`, this is related to a bug with upgrading from 3.x to 4.x that has affected some organizations. Read more here about [upgrading from 3.x to 4.x](/talk/migration/3/).
* If you're still experiencing issues, log a [support ticket](mailto:support@coralproject.net) so we can help diagnose the issue
If you're using `talk-plugin-auth`:
* See if you can isolate if it's a particular group of users that are experiencing this issue, e.g. mods, admins, subscribers? Confirm they have the appropriate permissions to comment.
* Note if this is a new issue that happened after an upgrade - did you read the [migration docs](/talk/migration/3/) and the [release notes](https://github.com/coralproject/talk/releases)? This might help you resolve the issue.
* If you're still experiencing issues, log a [support ticket](mailto:support@coralproject.net) so we can help diagnose the issue
+2 -2
View File
@@ -22,7 +22,7 @@ Feel free to check all the utilities here: `talk/plugin-api`.
#### Stream
* `setSort`
* `showSignInDialog``
* `showSignInDialog`
### Import
```
@@ -264,7 +264,7 @@ Coral UI is a set of components to help you build your UI. This powers our core.
### Import
```js
import {Button} from 'plugin-api/beta/components/ui';
import {Button} from 'plugin-api/beta/client/components/ui';
```
### Components
+1 -1
View File
@@ -319,7 +319,7 @@ module.exports = {
let user;
try {
const { id, provider, displayName } = profile;
user = await UsersService.findOrCreateExternalUser(
user = await UsersService.upsertSocialUser(
req.context,
id,
provider,
+1 -1
View File
@@ -7,7 +7,7 @@ Using plugins and configuration variables, you can modify the way the Admin look
### Creating a Custom Moderation Queue
Talk can support custom pluggable mod queues, meaning you can write a plugin that has some logic and determines which comments should appear there. This works by adding a field modQueues` in the `index.js` of your client side plugin, like so:
Talk can support custom pluggable mod queues, meaning you can write a plugin that has some logic and determines which comments should appear there. This works by adding a field `modQueues` in the `index.js` of your client side plugin, like so:
```
modQueues: {
@@ -10,18 +10,13 @@ Using plugins and configuration variables, you can modify the way the Talk comme
To enable our default rich text editor plugin, you'll need to:
1. Enable `talk-plugin-rich-text` as a server-side plugin
2. Enable `talk-plugin-rich-text-pell` as a client-side plugin
3. If you have `talk-plugin-comment-content` enabled, you will need to disable this (this supports hyperlinks in the comment body)
1. Enable `talk-plugin-rich-text` as a server-side and client-side plugin
2. If you have `talk-plugin-comment-content` enabled, you will need to disable this (this supports hyperlinks in the comment body)
Out of the box, our Talk Editor supports Bold, Italic, and Blockquote.
If you want to support another editor, you can create a plugin and replace the client-side one with the editor of your choice.
For more information on our implementation, see https://github.com/coralproject/talk/pull/1391
For more information on Pell, check out https://jaredreich.com/pell
### Sorting/Filtering the Stream
To enable sorting and filtering plugins, you will first need to enable the viewing options plugin:
+297
View File
@@ -8,3 +8,300 @@ You can add your own stylesheet in Admin > Configure > Tech Settings.
If you would like to change the styling of any elements in Talk, we provide global classnames with the prefix `talk-`. The easiest way to find the classname for the element you're looking for is to use the web inspector, and then update your stylesheet accordingly.
Plugins also have their own stylesheets located in the client directory.
Here is an example stylesheet that we use on our [Coral Blog](https://coralproject.net/blog):
```css
/*
* You can use this stylesheet as a place to get started
* for styling your own version of Talk!
* Author: Sam Hankins, Coral Project, 2018
* License: Apache 2.0
*/
* {
/* font-family: inherit; */
}
html, body {
width:auto;
height:auto;
}
body {
font-family: Helvetica, 'Helvetica Neue', Verdana, sans-serif;
width: 100%;
font-size: 14px;
margin: 0px;
padding: 0px 0px 100px 0px;
height: auto !important;
}
#talk-embed-stream-container {
padding: 4px;
}
.expandForSignin {
min-height: 600px;
}
.coralButton {
margin: 5px 10px 5px 0px;
background: none;
padding: 0px;
border: none;
font-size: inherit;
}
.coralButton:hover {
border-radius: 2px;
color: #767676;
}
.coralButton i {
margin-right: 3px;
}
.coralHr {
border: 0;
height: 0;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
}
.screen-reader-text {
clip: rect(1px, 1px, 1px, 1px);
height: 1px;
width: 1px;
overflow: hidden;
position: absolute !important;
}
/* Notification styles */
#coral-notif {
position: fixed;
border: 0;
background: rgb(105,105,105);
color: white;
border-radius: 2px;
font-weight: bold;
}
/* Info Box Styles */
.talk-plugin-infobox-info {
top: 0;
border: 0;
background: #DEEDFF;
color: #2a2a2a;
width: 100%;
text-align: left;
padding: 10px;
margin-bottom: 10px;
display: block;
box-sizing: border-box;
border-radius: 2px;
}
.talk-plugin-infobox-info em{
font-style: italic;
}
.talk-plugin-infobox-info strong{
font-weight: bold;
}
.talk-plugin-infobox-info blockquote{
border-left: solid 2px #2a2a2a;
padding-left: 10px;
}
.talk-plugin-infobox-info a{
color: #2a2a2a;
}
/* Question Box Styles */
.talk-stream-comments-container {
position: relative;
}
/* Comment styles */
.comment {
margin-bottom: 10px;
position: relative;
}
.talk-plugin-commentcontent-text {
margin-bottom: 7px;
font-size: 16px;
font-weight: 100;
line-height: 1.3;
}
/* Tag Labels */
.talk-plugin-tag-label {
background-color: #4C1066;
color: white;
display: inline-block;
border-radius: 2px;
font-size: 12px;
padding: 5px 6px;
}
/* Comment Action Styles */
.commentActionsRight, .replyActionsRight {
display: flex;
justify-content: flex-end;
}
.commentActionsLeft, .replyActionsLeft {
display: flex;
justify-content: flex-start;
float: left;
}
button.comment__action-button,
.comment__action-button button {
cursor: pointer;
}
button.comment__action-button[disabled],
.comment__action-button[disabled] button {
cursor: inherit;
}
.comment__action-button--nowrap {
white-space: nowrap;
}
.likedButton {
color: rgb(0,134,227);
}
.flaggedIcon {
color: #F00;
}
/* Flag Styles */
.talk-plugin-flags-popup-form {
margin-bottom: 10px;
}
.talk-plugin-flags-popup-header {
font-weight: bold;
font-size: 1rem;
margin-bottom: 10px;
}
.talk-plugin-flags-popup-radio {
margin:5px;
}
.talk-plugin-flags-popup-radio-label {
margin:5px;
font-weight: 400;
font-size: .9rem;
}
.talk-plugin-flags-popup-counter {
float: left;
margin-top: 21px;
color: #999;
}
.talk-plugin-flags-popup-button {
float: right;
margin-top: 10px;
}
.talk-plugin-flags-reason-text {
margin-left: 20px;
margin-top: 5px;
width: 75%;
font-size: 16px;
border: 1px solid #ccc;
max-width: calc(100% - 40px);
}
/* Close comments */
.close-comments-message {
box-sizing: border-box;
width: 100%;
height: 100px;
}
.close-comments-confirm-wrapper {
float: right;
}
.close-comments-alert {
background-color: #d65344;
color: white;
font-size: 1.33rem;
padding: 5px;
}
.close-comments-alert i.material-icons {
font-size: 16px !important;
}
/* Load More */
.talk-load-more {
text-align: center;
}
.talk-load-more button {
width: 100%;
text-align: center;
color: #FFF;
background-color: #2376D8;
border-radius: 2px;
cursor: pointer;
padding: 10px;
border-radius: 2px;
line-height: 1em;
text-transform: capitalize;
display: inline-block;
}
.talk-load-more:hover button {
background-color: #4399FF;
}
.talk-new-comments {
width: 100%;
text-align: center;
margin: 4px 0;
}
.talk-load-more-replies {
width: 100%;
padding-left: 20px;
box-sizing: border-box;
}
.talk-load-more-replies .talk-load-more-button {
background-color: transparent;
color: #979797;
border: #979797 solid 1px;
border-radius: 2px;
}
.talk-load-more-replies .talk-load-more:hover button {
background-color: #979797;
color: white;
}
.hidden {
visibility: hidden;
display: none;
}
```

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