Compare commits

..
311 Commits
Author SHA1 Message Date
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 a638dafd85 Merge pull request #1667 from coralproject/fix-dropdown-display
Fix DropDown display bug
2018-05-30 16:58:10 -04:00
Kim Gardner 93436a2eb5 Merge branch 'master' into fix-dropdown-display 2018-05-30 16:49:47 -04:00
Kim Gardner a16233484c Merge pull request #1666 from coralproject/auth
Fixed double encoding
2018-05-30 16:45:57 -04:00
Chi Vinh Le c98830aa63 Fix DropDown display bug 2018-05-30 22:23:52 +02:00
Wyatt Johnson c24251e8f2 Fixed double encoding 2018-05-30 14:22:13 -06:00
Wyatt Johnson 9ed991936e Merge pull request #1664 from conversario/ban-user-message
remove unused method
2018-05-30 10:08:20 -06:00
Wyatt Johnson ede273a3e0 fix to target handler 2018-05-30 10:00:04 -06:00
Wyatt Johnson c6deabf919 Merge branch 'master' into ban-user-message 2018-05-30 09:49:18 -06:00
Wyatt Johnson 2e40cd53cb Merge pull request #1665 from cristiandean/translation_br
Added missing translation pt_BR
2018-05-30 09:48:10 -06:00
Cristian b9e3a01cdd Added missing translation pt_BR 2018-05-30 09:19:59 -03:00
Leandro 5c7345313e remove unused method 2018-05-30 09:25:10 +02:00
Kim Gardner 8933b43e1a Merge pull request #1661 from coralproject/dropdown-ios
Use <button> for dropdown to fix IOS bug
2018-05-29 15:21:28 -04:00
Kim Gardner 6c1fb2b34d Merge branch 'master' into dropdown-ios 2018-05-29 15:14:35 -04:00
Kim Gardner 0db3b354ca Merge pull request #1662 from coralproject/rte-focus-ios
Workaround RTE Button focus bug on IOS
2018-05-29 15:14:26 -04:00
Kim Gardner a732793cb1 Merge branch 'master' into rte-focus-ios 2018-05-29 15:06:26 -04:00
Wyatt Johnson 03a7ecff0b Merge pull request #1663 from coralproject/e2e-update
E2E improvements
2018-05-29 11:38:22 -06:00
Chi Vinh Le 606f463331 Typo 2018-05-29 16:52:07 +02:00
Chi Vinh Le 576820f1f8 Fix warnings 2018-05-29 16:50:11 +02:00
Chi Vinh Le 3a84da9495 Activate Websocket and add browser console logging 2018-05-29 16:42:04 +02:00
okbel b15a306c43 çhanges 2018-05-28 16:20:20 -03:00
okbel 4299c373ba changes and updates to the e2e 2018-05-28 15:17:31 -03:00
Wyatt Johnson 6f0694d341 Merge pull request #1660 from hjanuschka/patch-1
add missing dependency
2018-05-28 09:53:18 -06:00
Wyatt Johnson da820ad6db updated yarn.lock 2018-05-28 09:42:22 -06:00
Chi Vinh Le ed679096c8 Workaround RTE Button focus bug on IOS 2018-05-28 17:16:44 +02:00
Chi Vinh Le 6577365930 Use standard buttons to fix IOS bug 2018-05-28 16:19:56 +02:00
Helmut Januschka 1fb206daa4 add missing dependency 2018-05-28 10:56:46 +02:00
Wyatt Johnson 2bd4e1182b Merge pull request #1657 from erikreyna/talk-plugin-rich-text-patch_placeholder-fix
move placeholder right behind contentEditable
2018-05-25 16:28:49 -06:00
Kim Gardner 9fe2175da3 Merge branch 'master' into talk-plugin-rich-text-patch_placeholder-fix 2018-05-25 18:19:37 -04:00
Kim Gardner c1692dfb70 Merge pull request #1630 from coralproject/karma
Karma Score
2018-05-25 18:19:25 -04:00
Kim Gardner cb83514365 Merge branch 'master' into talk-plugin-rich-text-patch_placeholder-fix 2018-05-25 18:12:21 -04:00
Erik Reyna ba3af0aeb3 apply @cvle suggestion for fix 2018-05-25 18:07:41 -04:00
Kim Gardner 51cb917d6b Merge branch 'master' into karma 2018-05-25 17:04:30 -04:00
Chi Vinh Le b02fd67742 Merge branch 'karma' of ssh://github.com/coralproject/talk into karma 2018-05-25 22:50:18 +02:00
Chi Vinh Le 90e4682cf6 Take karma into account when doing e2e 2018-05-25 22:49:35 +02:00
Wyatt Johnson 3ff8b8519e Merge pull request #1658 from coralproject/bump-version-4-4-2
Bump version to 4.4.2
2018-05-25 13:58:46 -06:00
Wyatt Johnson 105cff6990 Merge branch 'master' into bump-version-4-4-2 2018-05-25 13:52:44 -06:00
Kim Gardner 99854d8644 Merge pull request #1648 from coralproject/fix-ipad-touch-issue
Fix Touch issues on iOS Safari (iPad)
2018-05-25 15:04:55 -04:00
Kim Gardner cedabf04e3 Bump version to 4.4.2 2018-05-25 15:04:00 -04:00
Erik Reyna 2c6843129a move placeholder right behind contentEditable 2018-05-25 15:02:34 -04:00
Kim Gardner 47761cf8a0 Merge branch 'master' into fix-ipad-touch-issue 2018-05-25 14:49:25 -04:00
Kim Gardner 4442ffc784 Merge pull request #1656 from coralproject/csp
Disable CSP
2018-05-25 14:49:07 -04:00
Wyatt Johnson 016b6d6007 Merge branch 'master' into karma 2018-05-25 12:44:48 -06:00
Wyatt Johnson e94d13e594 Merge branch 'master' into fix-ipad-touch-issue 2018-05-25 12:44:19 -06:00
Kim Gardner 68c7477f3a Merge branch 'master' into csp 2018-05-25 14:40:21 -04:00
Kim Gardner 700cee4e56 Merge pull request #1653 from conversario/missing-translations-local-auth
Add missing translations (en/de) for plugins ignore-user and local-auth.
2018-05-25 14:39:56 -04:00
Kiwi d0740cb323 Merge branch 'master' into csp 2018-05-25 15:36:44 -03:00
Kim Gardner c021c3adc8 Merge branch 'master' into missing-translations-local-auth 2018-05-25 14:22:38 -04:00
Kim Gardner 130fb0d4e9 Merge pull request #1655 from coralproject/translations
Added missing translation
2018-05-25 14:22:23 -04:00
Kiwi 79a0f0321f Merge branch 'master' into karma 2018-05-25 15:12:57 -03:00
Kiwi 5090f9c6b2 Merge branch 'master' into csp 2018-05-25 15:12:20 -03:00
Kim Gardner 5e8c950dc5 Merge branch 'master' into translations 2018-05-25 14:04:03 -04:00
Kim Gardner c7b2641ea7 Merge pull request #1654 from coralproject/docs
Enhanced documentation on JWT's
2018-05-25 14:03:47 -04:00
Wyatt Johnson ba2abab358 added pivotal tracker ref 2018-05-25 11:55:17 -06:00
Wyatt Johnson 5535dde053 Merge branch 'master' into fix-ipad-touch-issue 2018-05-25 11:53:04 -06:00
Wyatt Johnson c1eab3f2c6 moved event handler to bundle 2018-05-25 11:51:43 -06:00
Wyatt Johnson 540df09b5b Update stream.njk 2018-05-25 11:48:55 -06:00
Kim Gardner ed2842717d Merge branch 'master' into docs 2018-05-25 13:45:12 -04:00
Kim Gardner d9621e6801 Merge pull request #1645 from mkonikov/delete-modal-iphone
[iOS CSS Fix] Improve legibility of the verify text
2018-05-25 13:44:59 -04:00
Wyatt Johnson 5f6b1235aa Merge branch 'master' into missing-translations-local-auth 2018-05-25 11:44:07 -06:00
Kim Gardner d923857eda Merge branch 'master' into delete-modal-iphone 2018-05-25 13:36:01 -04:00
Kim Gardner d96702eef5 Merge pull request #1646 from mkonikov/hr-fix
[CSS Fix] HR line on older devices - Flexbox
2018-05-25 13:35:18 -04:00
Wyatt Johnson 0e29a85d72 Merge branch 'master' into missing-translations-local-auth 2018-05-25 11:33:04 -06:00
Wyatt Johnson 7d861236b8 disable CSP until we can work on configuration + webpack issues 2018-05-25 11:21:53 -06:00
Wyatt Johnson 3a79cc684b added missing translation 2018-05-25 11:19:02 -06:00
Wyatt Johnson e1d77daf47 enhanced docs around jwt's 2018-05-25 11:16:24 -06:00
Kiwi 4ec5ccd1a3 Merge branch 'master' into hr-fix 2018-05-25 14:00:12 -03:00
Kiwi 5e6586d228 Merge branch 'master' into delete-modal-iphone 2018-05-25 13:17:57 -03:00
Kim Gardner d414867bb0 Merge branch 'master' into fix-ipad-touch-issue 2018-05-25 12:04:49 -04:00
Kim Gardner cfb162c490 Merge branch 'master' into karma 2018-05-25 12:04:15 -04:00
Fabian Neumann b7d2621cb8 Remove redundant period. It's in the translation strings already. 2018-05-25 17:19:43 +02:00
Wyatt Johnson 3141133e34 Merge pull request #1640 from mkonikov/word-break
Remove wordbreak
2018-05-25 09:12:36 -06:00
Fabian Neumann 325e763fd0 Remove redundancy 2018-05-25 16:34:29 +02:00
Fabian Neumann ab6a193dcb One more missing German translation 2018-05-25 16:31:07 +02:00
Fabian Neumann 56483f3888 Add missing translations (en/de) for plugins ignore-user and local-auth. 2018-05-25 16:09:38 +02:00
Mendel Konikov 4800e32758 Use overflow wrap for better compatibility 2018-05-24 19:07:00 -04:00
Wyatt Johnson 16573b127e text-replace error 2018-05-24 16:19:52 -06:00
Mendel Konikov abafa45be7 Replace break all with break-word 2018-05-24 17:20:54 -04:00
Mendel Konikov ce708bff42 Merge branch 'master' of github.com:mkonikov/talk into word-break 2018-05-24 17:18:29 -04:00
Wyatt Johnson 64c16a1805 support the __webpack_nonce__ parameter 2018-05-24 15:02:08 -06:00
Wyatt Johnson f8cc658c54 csp fixes 2018-05-24 14:34:13 -06:00
Wyatt Johnson e39bcb11ba added comment 2018-05-24 14:02:02 -06:00
Wyatt Johnson a046162f9c karma -> karmaThresholds 2018-05-24 13:57:26 -06:00
Wyatt Johnson 8f51b07388 Merge branch 'master' into karma 2018-05-24 13:28:03 -06:00
Wyatt Johnson 45113f14c7 Merge pull request #1650 from coralproject/whitelisted-langs
Added support for forcing languages
2018-05-24 13:24:00 -06:00
Wyatt Johnson 0c35badd95 name changes 2018-05-24 12:58:55 -06:00
Wyatt Johnson 1c0bfdb89e Merge branch 'master' into whitelisted-langs 2018-05-24 12:38:22 -06:00
Wyatt Johnson 8f4846404a fixed default doc 2018-05-24 12:35:45 -06:00
Wyatt Johnson c642cc811c fix 2018-05-24 12:34:58 -06:00
Wyatt Johnson 945ab9a1b6 Added support for forcing languages 2018-05-24 12:32:06 -06:00
Chi Vinh Le e1298e4fd9 Fix Touch issues on IOS Safari (iPad) 2018-05-24 19:00:07 +02:00
Wyatt Johnson 0c1e4873f8 fixed first time user 2018-05-24 10:11:49 -06:00
okbel f3d89e63d2 Merge branch 'karma' of github.com:coralproject/talk into karma
* 'karma' of github.com:coralproject/talk: (32 commits)
  fixed translations
  "Trust" flag to "Karma" and updated translations
  adjusted karma tooltip
  field renamed
  patches to reliability computation based on settings
  add from to import statement
  Remuve dupe "apply"
  Fix broken link to tokenusernotfound
  Make it so low threshold is included in karma calc
  Fix incorrect statement regarding GDPR
  simplified language negotiation
  Docs typo
  relying on ip
  req not properly passed
  German translations for the email notification plugins.
  Small modification
  doc patch
  fix for custom css
  fixes to csp policy
  disable by default
  ...
2018-05-24 13:09:13 -03:00
Wyatt Johnson 0835f3f321 fixed translations 2018-05-23 17:16:51 -06:00
Wyatt Johnson 9a73c6db59 "Trust" flag to "Karma" and updated translations 2018-05-23 16:18:22 -06:00
Wyatt Johnson 97416d421b adjusted karma tooltip 2018-05-23 16:18:22 -06:00
Wyatt Johnson f36b6e2cb6 field renamed 2018-05-23 16:18:22 -06:00
Wyatt Johnson 975448ec69 patches to reliability computation based on settings 2018-05-23 16:18:22 -06:00
Mendel Konikov 92420ae827 Fix hr line on older devices 2018-05-23 17:42:40 -04:00
Mendel Konikov a6ec021bd1 Explicitly set opacity to 1 for iPhone 2018-05-23 17:25:09 -04:00
Kim Gardner 921b85fba3 Merge branch 'master' into karma 2018-05-23 16:47:52 -04:00
Kim Gardner 9c7ed62cba Merge pull request #1644 from erikreyna/patch-1
add from to import statement
2018-05-23 16:47:02 -04:00
Wyatt Johnson b7a053c011 Merge branch 'master' into patch-1 2018-05-23 13:28:20 -06:00
Kim Gardner 6548df971c Merge branch 'master' into karma 2018-05-23 15:19:32 -04:00
Kim Gardner 27818d966d Merge pull request #1642 from babolivier/patch-1
[Doc] Fix incorrect statement regarding GDPR
2018-05-23 14:43:46 -04:00
Erik Reyna f7f6ed635c add from to import statement
from expected from import statement, thanks eslint
2018-05-23 14:24:11 -04:00
Wyatt Johnson 61c09f5818 Merge branch 'master' into patch-1 2018-05-23 12:17:05 -06:00
Wyatt Johnson def20a3b37 Merge pull request #1643 from smic1909/patch-1
Fix broken link to tokenusernotfound
2018-05-23 12:16:48 -06:00
Wyatt Johnson 4ae3e90422 Merge branch 'master' into patch-1 2018-05-23 12:09:44 -06:00
Wyatt Johnson 9539073eb8 Merge pull request #1633 from coralproject/csp
Content Security Policy + Nunjacks
2018-05-23 12:08:45 -06:00
Wyatt Johnson 422260607e Merge branch 'master' into csp 2018-05-23 11:53:24 -06:00
Wyatt Johnson a16c584880 Merge branch 'master' into patch-1 2018-05-23 11:25:14 -06:00
Wyatt Johnson 3fcc85139d Merge pull request #1641 from coralproject/locales
Locale Negotiation
2018-05-23 10:12:27 -06:00
okbel 17f76e85b0 changes 2018-05-23 13:07:34 -03:00
Brendan Abolivier a95d55b6da Remuve dupe "apply" 2018-05-23 10:05:21 +02:00
Simon 879865a4c1 Fix broken link to tokenusernotfound
Fix broken link to tokenusernotfound
2018-05-23 09:14:32 +02:00
Kim Gardner 8405bdf9bc Merge branch 'karma' of git+ssh://github.com/coralproject/talk into karma 2018-05-22 20:12:40 -04:00
Kim Gardner 8e24d4f5c6 Make it so low threshold is included in karma calc 2018-05-22 20:12:17 -04:00
Kim Gardner 9265d29ac0 Merge branch 'master' into karma 2018-05-22 19:00:12 -04:00
Brendan Abolivier 849fe37b9b Fix incorrect statement regarding GDPR
GDPR's reach doesn't depend on the person or business's location, but on whether this person or this business include european citizen in its target (which can be easy to state for a business, but not that much for an individual).
2018-05-22 23:25:12 +02:00
Wyatt Johnson 1a52ad5d97 Merge branch 'master' into locales 2018-05-22 14:42:43 -06:00
Wyatt Johnson b984af3e2e Merge pull request #1638 from coralproject/akismet-bug
Akismet Bug - Req not properly passed
2018-05-22 14:42:24 -06:00
Wyatt Johnson 10a786f84d simplified language negotiation 2018-05-22 14:21:32 -06:00
Mendel Konikov 4f5f424c77 Remove wordbreak 2018-05-22 15:49:19 -04:00
Kiwi 9b4455f7bd Merge branch 'master' into csp 2018-05-22 16:39:20 -03:00
Kim Gardner 3a12e776c3 Merge branch 'master' into akismet-bug 2018-05-22 15:01:50 -04:00
Kim Gardner 6667bbe01f Docs typo 2018-05-22 13:18:28 -04:00
Kim Gardner 583cb6566b Merge branch 'master' into karma 2018-05-22 12:54:09 -04:00
Kim Gardner f90e17c3e0 Merge pull request #1625 from coralproject/party-fixes
Displaying multiple external provider IDs
2018-05-22 12:52:08 -04:00
Kim Gardner 71b1259db6 Merge branch 'master' into akismet-bug 2018-05-22 12:21:53 -04:00
Kiwi a39b967164 Merge branch 'master' into party-fixes 2018-05-22 13:19:08 -03:00
okbel b411f3e059 relying on ip 2018-05-22 13:08:59 -03:00
okbel 1aed51468f req not properly passed 2018-05-22 12:40:21 -03:00
Kim Gardner fd271d237c Merge branch 'master' into csp 2018-05-22 10:44:08 -04:00
Kim Gardner 04ae6fbb43 Merge pull request #1637 from conversario/notification-translations
German translations for the email notification plugins.
2018-05-22 10:43:50 -04:00
Fabian Neumann 4767354379 German translations for the email notification plugins. 2018-05-22 12:36:25 +02:00
Kim Gardner 55998a67da Merge branch 'master' into csp 2018-05-21 16:18:46 -04:00
Kim Gardner eee1e858bf Merge pull request #1634 from mkonikov/disabled-download-button
Show overflow text on download comments button when disabled
2018-05-21 16:18:30 -04:00
Kiwi 2c9d30bfd2 Small modification
Removed obsolete line and add todo
2018-05-21 17:09:36 -03:00
Kim Gardner 763833783d Merge branch 'master' into disabled-download-button 2018-05-21 15:22:51 -04:00
Kim Gardner 1f5e3d9f3f Merge pull request #1635 from conversario/translations-de-4.4.1
German translations for v4.4.1
2018-05-21 11:19:05 -04:00
Kim Gardner 30f03f5fb8 Merge branch 'master' into disabled-download-button 2018-05-21 11:07:59 -04:00
Kim Gardner 43c7b6f23f Merge branch 'master' into karma 2018-05-21 11:07:34 -04:00
Kim Gardner ced9d2ea4b Merge branch 'master' into translations-de-4.4.1 2018-05-21 11:06:17 -04:00
Wyatt Johnson 5aba578a98 doc patch 2018-05-18 16:47:58 -06:00
Wyatt Johnson dd0ad17038 fix for custom css 2018-05-18 16:44:43 -06:00
Wyatt Johnson 868343a29b fixes to csp policy 2018-05-18 16:42:23 -06:00
Wyatt Johnson 5403872bb7 disable by default 2018-05-18 16:21:53 -06:00
Wyatt Johnson f58e3df439 added config for report only mode 2018-05-18 16:21:53 -06:00
Wyatt Johnson 000d9ae56a Merge branch 'master' into csp 2018-05-18 15:28:26 -06:00
Kim Gardner e2ba08e708 Merge pull request #1632 from mkonikov/extra-p
Only display paragraph if delete requested
2018-05-18 17:18:00 -04:00
Kim Gardner 20860bb773 Merge branch 'master' into extra-p 2018-05-18 16:33:27 -04:00
Kim Gardner b15766bf80 Merge pull request #1629 from coralproject/rte-bugfixes
RTE Bugfixes
2018-05-18 16:33:15 -04:00
Kim Gardner 531cb0793f Merge branch 'master' into rte-bugfixes 2018-05-18 16:24:30 -04:00
Wyatt Johnson 8265c13149 Merge branch 'master' into extra-p 2018-05-18 11:26:37 -06:00
okbel b816053191 Updating the cache 2018-05-18 14:07:10 -03:00
Mendel Konikov ffa6b99d09 Wrap els in div 2018-05-18 12:32:24 -04:00
okbel 0e8065cdf9 Styling 2018-05-18 13:31:50 -03:00
Wyatt Johnson 9484d4c6f5 Merge branch 'master' into csp 2018-05-18 10:11:39 -06:00
Belén Curcio 5ebbc24264 Merge branch 'master' into karma 2018-05-18 13:09:07 -03:00
okbel 43e0f95aa6 Merge branch 'party-fixes' of github.com:coralproject/talk into party-fixes
* 'party-fixes' of github.com:coralproject/talk: (33 commits)
  patched migration bugs
  Don't close popup when there is an error
  Update .nsprc
  fix optimisticResponse typo
  Remove deprecated useMongoClient
  Update lint-staged to fix dependency issue
  copy updates
  Update package.json
  Ready
  Set is_test to false
  Removing
  Added support for new indexes
  Remove unused variable 2
  Remove unused variable
  Remove ModerationIndicator from container
  Show username already exists error
  No indicator on new queue
  Exclude deleted when loading more
  Remove modqueue counts on the frontend
  sepatate state
  ...
2018-05-18 13:07:03 -03:00
okbel 1f40d6b7bb replacing by a one liner :) 2018-05-18 13:05:46 -03:00
Fabian Neumann 8ee90387f6 German translations for v4.4.1 2018-05-18 16:03:30 +02:00
Wyatt Johnson 43170121f3 Merge branch 'master' into party-fixes 2018-05-17 21:03:12 -06:00
Wyatt Johnson 4f001bbdf2 Merge pull request #1628 from conversario/withremovetag-typo
fix optimisticResponse typo
2018-05-17 21:02:37 -06:00
Wyatt Johnson f5362f445a Merge branch 'master' into withremovetag-typo 2018-05-17 20:48:33 -06:00
Wyatt Johnson 1bcf1a23fc Merge branch 'master' into extra-p 2018-05-17 20:34:37 -06:00
Wyatt Johnson b2306066ce Merge pull request #1627 from coralproject/upgrade-mongoose
Upgrade Mongoose
2018-05-17 20:32:40 -06:00
Wyatt Johnson ebcff4d95e patched migration bugs 2018-05-17 18:58:01 -06:00
Wyatt Johnson 2484066922 Merge branch 'master' into upgrade-mongoose 2018-05-17 18:43:24 -06:00
Wyatt Johnson 2822a2dddd Merge branch 'master' into csp 2018-05-17 18:43:12 -06:00
Mendel Konikov 5f410276c6 Show overflow text on download comments button when disabled 2018-05-17 20:41:59 -04:00
Wyatt Johnson c84a9716a1 added support for csp 2018-05-17 18:37:50 -06:00
Mendel Konikov d83c2fd566 Only display paragraph if delete requested 2018-05-17 20:03:12 -04:00
Kim Gardner cd6aacbc76 Merge pull request #1607 from coralproject/admin-auth
External Signin for Admin
2018-05-17 19:08:21 -04:00
Wyatt Johnson 95072be97e switched ejs for nunjucks 2018-05-17 15:59:49 -06:00
Kiwi f0589fba26 Merge branch 'master' into admin-auth 2018-05-17 16:36:32 -03:00
Chi Vinh Le 417befd52a Don't close popup when there is an error 2018-05-17 21:34:37 +02:00
Wyatt Johnson 8d8cd57d79 Merge branch 'master' into upgrade-mongoose 2018-05-17 13:19:09 -06:00
Wyatt Johnson 086c43a003 Update .nsprc 2018-05-17 13:18:57 -06:00
Wyatt Johnson 887f7e17f4 server side 2018-05-17 11:38:00 -06:00
okbel 6444e8c2ab Karma 2018-05-17 13:48:30 -03:00
Leandro 0339e97b37 fix optimisticResponse typo 2018-05-17 16:39:04 +02:00
Kiwi ae88ae3f98 Merge pull request #1614 from conversario/comment-tombstone-slot
Add new slot 'commentTombstone'
2018-05-16 21:00:13 -03:00
Kim Gardner 5241206b03 Remove deprecated useMongoClient 2018-05-16 18:24:34 -04:00
Kim Gardner 663caa1001 Update lint-staged to fix dependency issue 2018-05-16 18:18:36 -04:00
Chi Vinh Le d07c54918d Indent, outdent fixes 2018-05-16 21:37:48 +02:00
Chi Vinh Le 2eac8f48d6 Workaround IOS focus bug when clicking on buttons 2018-05-16 21:37:35 +02:00
Kim Gardner 3a383ff8ba Merge branch 'master' into admin-auth 2018-05-16 12:58:59 -04:00
Kim Gardner 34760653a2 Merge branch 'master' into comment-tombstone-slot 2018-05-16 12:58:51 -04:00
Kim Gardner d176578aed Merge branch 'master' into party-fixes 2018-05-16 12:41:34 -04:00
Kim Gardner e3323ce5ec Merge pull request #1544 from conversario/global-switchoff
New setting: Disable commenting site-wide
2018-05-16 12:14:09 -04:00
Belén Curcio 7fc5c5a6be Merge branch 'master' into comment-tombstone-slot 2018-05-16 12:55:24 -03:00
okbel 2524004e2a Styling 2018-05-16 11:55:05 -03:00
okbel 91060537bb displaying multiple provider ids 2018-05-16 11:51:28 -03:00
Kim Gardner f0b87501c0 Merge branch 'master' into admin-auth 2018-05-15 19:06:27 -04:00
Kim Gardner ea6757cce5 Merge branch 'master' into global-switchoff 2018-05-15 18:55:58 -04:00
Kim Gardner cb9945053f Merge pull request #1624 from coralproject/username-change-copy
Username change copy
2018-05-15 18:48:31 -04:00
Kim Gardner fca6603b38 Merge branch 'master' into admin-auth 2018-05-15 18:13:05 -04:00
Kim Gardner 56ee58efdf Merge branch 'master' into global-switchoff 2018-05-15 17:55:35 -04:00
Wyatt Johnson 0b407d516b copy updates 2018-05-15 15:51:14 -06:00
Kim Gardner 4cca800af0 Merge pull request #1623 from coralproject/wyattjoh-patch-1
Bump to 4.4.1
2018-05-15 13:32:49 -04:00
Wyatt Johnson ee0790dbe8 Update package.json 2018-05-15 11:25:58 -06:00
Kim Gardner 6890d24e47 Merge pull request #1616 from coralproject/gdpr-change-email-bug
GDPR Bug - Confirm Email Dialog inputs shouldn't be connected to the save button in the background
2018-05-15 12:54:39 -04:00
okbel 2392e1ca78 Merge branch 'gdpr-change-email-bug' of github.com:coralproject/talk into gdpr-change-email-bug
* 'gdpr-change-email-bug' of github.com:coralproject/talk:
  Set is_test to false
  Added support for new indexes
2018-05-15 13:34:12 -03:00
okbel 40a431b374 Ready 2018-05-15 13:31:29 -03:00
Kim Gardner 5214705f5b Merge branch 'master' into gdpr-change-email-bug 2018-05-15 12:24:47 -04:00
Kim Gardner 79df9b487f Merge pull request #1622 from icco/patch-1
Set Akismet is_test to false
2018-05-15 12:24:23 -04:00
Nat Welch ef5e5ddbcf Set is_test to false
Akismet is no longer in test mode.
2018-05-15 11:55:02 -04:00
Wyatt Johnson 2e773a4438 Merge branch 'master' into gdpr-change-email-bug 2018-05-15 09:27:09 -06:00
Kim Gardner 2a2eabfae0 Merge pull request #1621 from coralproject/indexes
MongoDB Indexes
2018-05-15 11:07:55 -04:00
okbel bb9be71a7a Merge branch 'gdpr-change-email-bug' of github.com:coralproject/talk into gdpr-change-email-bug
* 'gdpr-change-email-bug' of github.com:coralproject/talk:
  Remove unused variable 2
  Remove unused variable
  Remove ModerationIndicator from container
  Show username already exists error
  No indicator on new queue
  Exclude deleted when loading more
  Remove modqueue counts on the frontend
2018-05-15 12:07:15 -03:00
okbel 50acf9ca50 Removing 2018-05-15 11:47:07 -03:00
Kim Gardner 3ae0136f75 Merge branch 'master' into gdpr-change-email-bug 2018-05-14 17:59:16 -04:00
Wyatt Johnson d3ad486f81 Merge branch 'master' into indexes 2018-05-14 14:52:57 -06:00
Wyatt Johnson 32510db1fa Merge pull request #1618 from coralproject/remove-mod-count-frontend
Remove modqueue counts on the frontend
2018-05-14 14:52:43 -06:00
Wyatt Johnson 8d44525c16 Added support for new indexes 2018-05-14 14:37:27 -06:00
Wyatt Johnson 83fddfdc40 Merge branch 'master' into remove-mod-count-frontend 2018-05-14 14:36:42 -06:00
Kim Gardner ed32a69b68 Merge branch 'master' into gdpr-change-email-bug 2018-05-14 16:32:51 -04:00
Kim Gardner 02b1e03c21 Merge pull request #1620 from coralproject/username-already-exists
Show username already exists error
2018-05-14 16:29:19 -04:00
Chi Vinh Le 08d523a4d6 Remove unused variable 2 2018-05-14 21:38:30 +02:00
Chi Vinh Le b5adea4b2a Remove unused variable 2018-05-14 21:30:09 +02:00
Chi Vinh Le beef4e8b3b Remove ModerationIndicator from container 2018-05-14 21:28:48 +02:00
Chi Vinh Le 5a190839d1 Show username already exists error 2018-05-14 20:44:30 +02:00
Chi Vinh Le ab7e5bc858 No indicator on new queue 2018-05-14 20:05:18 +02:00
Chi Vinh Le 12e848aace Exclude deleted when loading more 2018-05-14 19:27:18 +02:00
Chi Vinh Le 67c3fe3c5b Remove modqueue counts on the frontend 2018-05-14 19:18:34 +02:00
okbel 1f7c4e58a1 sepatate state 2018-05-14 13:31:24 -03:00
Fabian Neumann 8e392ef32d Remove superfluous className 2018-05-14 15:01:51 +02:00
Fabian Neumann 36063b459e Add new slot 'commentTombstone'.
With this slot plugins can override the default tombstone for deleted
comments.
2018-05-14 13:57:46 +02:00
Kim Gardner 400890237c Merge branch 'master' into admin-auth 2018-05-11 18:06:53 -04:00
Wyatt Johnson bde1dff33b changed to seperate slot 2018-05-11 14:45:03 -06:00
Wyatt Johnson f846ef0c06 Merge pull request #1595 from clintabrown/fix-my-profile-title-link
Fix My Profile story title link behavior
2018-05-11 14:37:23 -06:00
Wyatt Johnson fa31541fb7 Merge branch 'master' into fix-my-profile-title-link 2018-05-11 14:13:29 -06:00
Wyatt Johnson 4fcd5dd4f7 Merge pull request #1604 from coralproject/email-attach-fix
Email Attach Fixes
2018-05-11 13:05:45 -06:00
Chi Vinh Le 7d8d5a6115 Use lodash get 2018-05-11 20:46:32 +02:00
Chi Vinh Le f0dcd844a7 Fix excludeIf condition 2018-05-11 20:44:45 +02:00
Wyatt Johnson 9a1b9fb864 Merge branch 'master' into email-attach-fix 2018-05-11 11:39:08 -06:00
Wyatt Johnson 6cf4203c9b Update translations.yml 2018-05-11 11:38:54 -06:00
Kiwi 85e21f8090 Merge pull request #1606 from coralproject/ios-focus-zoom-issues
Disable autofocus on IOS Safari, prevent zoom on narrow screens
2018-05-11 14:31:21 -03:00
Chi Vinh Le 64583fabbe Merge branch 'email-attach-fix' of ssh://github.com/coralproject/talk into email-attach-fix 2018-05-11 18:40:26 +02:00
Kiwi bdaf5c633e Merge branch 'master' into email-attach-fix 2018-05-11 13:40:00 -03:00
Kiwi 945e2670f5 Merge branch 'master' into ios-focus-zoom-issues 2018-05-11 13:39:52 -03:00
Kiwi 4166e36e1e Merge pull request #1599 from coralproject/css-templates
CSS and Template Fixes + Improvements
2018-05-11 13:39:36 -03:00
Chi Vinh Le f018ccf33e Prevent showing add email address dialog when usernam is not set 2018-05-11 18:38:41 +02:00
Belén Curcio e61f325e86 Merge branch 'master' into fix-my-profile-title-link 2018-05-11 13:36:12 -03:00
Wyatt Johnson ef72b7b1d5 Merge branch 'master' into email-attach-fix 2018-05-11 10:33:32 -06:00
okbel a571cf830d Adding target 2018-05-11 13:32:55 -03:00
Kiwi 5dea051427 Merge branch 'master' into css-templates 2018-05-11 13:25:52 -03:00
Kim Gardner 730ef1a5c4 Merge pull request #1608 from mkonikov/copy-fix
Update download page copy
2018-05-11 12:25:39 -04:00
Belén Curcio 622b79b491 Removing unnecessary functions 2018-05-11 13:22:48 -03:00
Wyatt Johnson faa0ee8ceb added global wrap 2018-05-11 10:22:45 -06:00
Kiwi d67c25a791 Merge branch 'master' into email-attach-fix 2018-05-11 13:21:20 -03:00
Belén Curcio 32d615527a Merge branch 'master' into fix-my-profile-title-link 2018-05-11 13:08:13 -03:00
Wyatt Johnson 3a9051bf1b Merge branch 'master' into css-templates 2018-05-11 10:07:59 -06:00
Kim Gardner e8cce02bef Merge branch 'master' into global-switchoff 2018-05-11 11:56:35 -04:00
Kim Gardner 44c2d7847a Merge branch 'master' into copy-fix 2018-05-11 11:55:53 -04:00
Kiwi 93359d8250 Merge pull request #1613 from coralproject/wyattjoh-patch-1
Delete package-lock.json
2018-05-11 12:37:28 -03:00
Wyatt Johnson ba2985e891 Merge branch 'master' into email-attach-fix 2018-05-11 09:33:55 -06:00
Wyatt Johnson 42d4abf842 review updates 2018-05-11 09:33:29 -06:00
Wyatt Johnson 29ffe287cd Delete package-lock.json 2018-05-11 09:21:55 -06:00
Kiwi 05c13126ad Merge branch 'master' into ios-focus-zoom-issues 2018-05-11 12:21:15 -03:00
Kim Gardner 787dd8ec8a Merge branch 'master' into copy-fix 2018-05-11 11:18:22 -04:00
Kim Gardner 47c7125190 Merge pull request #1612 from mkonikov/capitalize
Capitalize "Information"
2018-05-11 11:17:29 -04:00
Chi Vinh Le fc5621a060 Add reference to story 2018-05-11 17:12:28 +02:00
Mendel Konikov f57f8397fe Capitalize "Information" 2018-05-11 11:01:15 -04:00
Fabian Neumann 1236e7a504 Disable/hide the reply buttons when commenting is disabled. 2018-05-11 12:52:44 +02:00
Fabian Neumann bae20c38ec Rename globalSwitchoff to disableCommenting. 2018-05-11 12:21:13 +02:00
Fabian Neumann d731357001 Remove plugin docs for global-switchoff. Not a plugin anymore. 2018-05-11 12:20:26 +02:00
Fabian Neumann f28a3f2f4e Add test case: err if trying to comment while it's disabled 2018-05-11 11:28:58 +02:00
Fabian Neumann 48fc3ab5db Move "global switchoff" code from plugin to core 2018-05-11 11:28:58 +02:00
Fabian Neumann fab15daacd Fix: use the correct keys to get the settings 2018-05-11 11:19:09 +02:00
Fabian Neumann 499030028c Add hooks to RootMutation to save our setting in the database. 2018-05-11 11:19:09 +02:00
Fabian Neumann bbd3050b95 Small refactoring 2018-05-11 11:19:09 +02:00
Fabian Neumann b73620bcb0 WIP 2018-05-11 11:19:09 +02:00
Mendel Konikov 2d1b675234 Update download page copy 2018-05-10 21:01:26 -04:00
Kim Gardner ac7bea209e Merge branch 'master' into email-attach-fix 2018-05-10 18:53:39 -04:00
Kim Gardner dd1b51e09b Merge branch 'master' into fix-my-profile-title-link 2018-05-10 18:52:56 -04:00
Wyatt Johnson 33be00a540 Added support for external signins on Admin 2018-05-10 16:44:19 -06:00
Chi Vinh Le 631206f360 Disable autofocus on IOS Safari, prevent zoom on narrow screens 2018-05-11 00:30:12 +02:00
Wyatt Johnson 4d6ef95416 Merge branch 'master' into css-templates 2018-05-10 14:55:25 -06:00
Wyatt Johnson 462f5e8206 Merge pull request #1603 from coralproject/losowsky-patch-2
Corrections and clarity
2018-05-10 14:55:12 -06:00
Wyatt Johnson 524aa3e863 Merge branch 'master' into losowsky-patch-2 2018-05-10 14:46:09 -06:00
Andrew Losowsky e0cada5bde Update 03-07-product-guide-trust.md 2018-05-10 13:44:03 -07:00
Wyatt Johnson dd0381d22a Merge branch 'master' into email-attach-fix 2018-05-10 14:43:37 -06:00
Wyatt Johnson 2e37f78705 Email Attach Fixes 2018-05-10 13:31:00 -06:00
Kim Gardner 0203a65656 Merge branch 'master' into fix-my-profile-title-link 2018-05-10 15:23:24 -04:00
Kim Gardner 4413e5616c Merge pull request #1596 from coralproject/tombstone-copy
Small tweak to tombstone copy
2018-05-10 15:23:07 -04:00
Wyatt Johnson b9c5024e68 Merge branch 'master' into css-templates 2018-05-10 12:11:48 -06:00
Kim Gardner 38f9c2f458 Merge branch 'master' into tombstone-copy 2018-05-10 13:42:26 -04:00
Kim Gardner fc14c5ab1c Merge pull request #1598 from coralproject/mobile-fix
Fit Delete My Account modals on mobile
2018-05-10 13:42:06 -04:00
Wyatt Johnson 75f9a697cb Merge branch 'master' into tombstone-copy 2018-05-10 11:34:11 -06:00
Kim Gardner 89f87dc9ad Merge branch 'master' into css-templates 2018-05-10 13:33:50 -04:00
Kim Gardner 85f45f390c Merge branch 'master' into mobile-fix 2018-05-10 13:33:15 -04:00
Kim Gardner 10317671ee Merge branch 'master' into losowsky-patch-2 2018-05-10 10:41:01 -04:00
Kim Gardner b16f15e47d Merge pull request #1601 from coralproject/check-correct-email
Allow changing email back and forth
2018-05-10 10:40:34 -04:00
Andrew Losowsky 2554b26110 Corrections and clarity
Fixed the incorrect thresholds and made the description clearer
2018-05-10 10:21:44 -04:00
Chi Vinh Le a092a197cf Check correct email 2018-05-09 23:45:53 +02:00
Kim Gardner e069d823a4 Merge pull request #1600 from coralproject/cli-fix
CLI Fixes
2018-05-09 16:50:59 -04:00
Wyatt Johnson ca3d56def8 asset refresh patched 2018-05-09 14:40:24 -06:00
Wyatt Johnson 9af3772087 Added json output for assets list
- Fixes #1597
2018-05-09 14:28:04 -06:00
Wyatt Johnson 51525de679 fixed set-role description 2018-05-09 14:17:43 -06:00
Wyatt Johnson 969206f905 copy adjustment 2018-05-09 14:14:48 -06:00
Wyatt Johnson 302cab8ca0 added header to graphiql 2018-05-09 14:12:27 -06:00
Wyatt Johnson 55a9c1e6b4 added link to graphiql 2018-05-09 14:05:16 -06:00
Wyatt Johnson 4426be5f39 improved dev experience, fixed css order 2018-05-09 13:56:05 -06:00
okbel 0ded5c72e9 adding media 2018-05-09 14:23:22 -03:00
Kim Gardner 1517611c9e Update tombstone copy 2018-05-09 11:00:02 -04:00
Clint Brown 8be4300ff4 Fix My Profile story title link behavior 2018-05-09 17:41:16 +10:00
221 changed files with 3522 additions and 2037 deletions
+2 -1
View File
@@ -7,6 +7,7 @@
"https://nodesecurity.io/advisories/594",
"https://nodesecurity.io/advisories/603",
"https://nodesecurity.io/advisories/611",
"https://nodesecurity.io/advisories/612"
"https://nodesecurity.io/advisories/612",
"https://nodesecurity.io/advisories/654"
]
}
+2 -1
View File
@@ -7,6 +7,7 @@ ONBUILD ARG TALK_REPLY_COMMENTS_LOAD_DEPTH=3
ONBUILD ARG TALK_THREADING_LEVEL=3
ONBUILD ARG TALK_DEFAULT_STREAM_TAB=all
ONBUILD ARG TALK_DEFAULT_LANG=en
ONBUILD ARG TALK_WHITELISTED_LANGUAGES
ONBUILD ARG TALK_PLUGINS_JSON
ONBUILD ARG TALK_WEBPACK_SOURCE_MAP
@@ -20,4 +21,4 @@ ONBUILD COPY . /usr/src/app
ONBUILD RUN cli plugins reconcile && \
yarn && \
yarn build && \
yarn cache clean
yarn cache clean
+22 -1
View File
@@ -1,4 +1,6 @@
const express = require('express');
const nunjucks = require('nunjucks');
const cons = require('consolidate');
const trace = require('./middleware/trace');
const logging = require('./middleware/logging');
const path = require('path');
@@ -72,7 +74,26 @@ app.use(
// VIEW CONFIGURATION
//==============================================================================
app.set('views', path.join(__dirname, 'views'));
// configure the default views directory.
const views = path.join(__dirname, 'views');
app.set('views', views);
// reconfigure nunjucks.
cons.requires.nunjucks = nunjucks.configure(views, {
autoescape: true,
trimBlocks: true,
lstripBlocks: true,
watch: process.env.NODE_ENV === 'development',
});
// assign the nunjucks engine to .njk files.
app.engine('njk', cons.nunjucks);
// assign the ejs engine to .ejs and .html files.
app.engine('ejs', cons.ejs);
app.engine('html', cons.ejs);
// set .ejs as the default extension.
app.set('view engine', 'ejs');
//==============================================================================
+53 -24
View File
@@ -7,7 +7,7 @@
const util = require('./util');
const program = require('commander');
const parseDuration = require('ms');
const Table = require('cli-table');
const Table = require('cli-table2');
const AssetModel = require('../models/asset');
const CommentModel = require('../models/comment');
const AssetsService = require('../services/assets');
@@ -23,23 +23,33 @@ util.onshutdown([() => mongoose.disconnect()]);
/**
* Lists all the assets registered in the database.
*/
async function listAssets() {
async function listAssets(opts) {
try {
let assets = await AssetModel.find({}).sort({ created_at: 1 });
let table = new Table({
head: ['ID', 'Title', 'URL'],
});
switch (opts.format) {
case 'json': {
console.log(JSON.stringify(assets, null, 2));
break;
}
default: {
let table = new Table({
head: ['ID', 'Title', 'URL'],
});
assets.forEach(asset => {
table.push([
asset.id,
asset.title ? asset.title : '',
asset.url ? asset.url : '',
]);
});
assets.forEach(asset => {
table.push([
asset.id,
asset.title ? asset.title : '',
asset.url ? asset.url : '',
]);
});
console.log(table.toString());
break;
}
}
console.log(table.toString());
util.shutdown();
} catch (e) {
console.error(e);
@@ -49,12 +59,13 @@ async function listAssets() {
async function refreshAssets(ageString) {
try {
const now = new Date().getTime();
const ageMs = parseDuration(ageString);
const age = new Date(now - ageMs);
const query = AssetModel.find({}, { id: 1 });
if (ageString) {
// An age was specified, so filter only those assets.
const ageMs = parseDuration(ageString);
const age = new Date(Date.now() - ageMs);
let assets = await AssetModel.find(
{
query.merge({
$or: [
{
scraped: {
@@ -65,16 +76,28 @@ async function refreshAssets(ageString) {
scraped: null,
},
],
},
{ id: 1 }
);
});
}
// Create a graph context.
const ctx = Context.forSystem();
// Load the assets.
const cursor = query.cursor();
// Queue all the assets for scraping.
await Promise.all(assets.map(({ id }) => scraper.create(ctx, id)));
console.log('Assets were queued to be scraped');
const promises = [];
let asset = await cursor.next();
while (asset) {
promises.push(scraper.create(ctx, asset.id));
asset = await cursor.next();
}
await Promise.all(promises);
console.log(`${promises.length} Assets were queued to be scraped.`);
util.shutdown();
} catch (e) {
console.error(e);
@@ -202,11 +225,17 @@ async function rewrite(search, replace, options) {
program
.command('list')
.option(
'--format <type>',
'Specify the output format [table]',
/^(table|json)$/i,
'table'
)
.description('list all the assets in the database')
.action(listAssets);
program
.command('refresh <age>')
.command('refresh [age]')
.description('queues the assets that exceed the age requested')
.action(refreshAssets);
+1 -1
View File
@@ -8,7 +8,7 @@ const util = require('./util');
const program = require('commander');
const mongoose = require('../services/mongoose');
const TokensService = require('../services/tokens');
const Table = require('cli-table');
const Table = require('cli-table2');
// Register the shutdown criteria.
util.onshutdown([() => mongoose.disconnect()]);
+2 -2
View File
@@ -8,7 +8,7 @@ const util = require('./util');
const program = require('commander');
const inquirer = require('inquirer');
const { stripIndent } = require('common-tags');
const Table = require('cli-table');
const Table = require('cli-table2');
// Make things colorful!
require('colors');
@@ -328,7 +328,7 @@ program
.action(searchUsers);
program
.command('set-role <userID> <role>')
.command('set-role <userID>')
.description('sets the role on a user')
.action(setUserRole);
+11
View File
@@ -0,0 +1,11 @@
:global {
html, body, #root, #root > div {
min-height: 100%;
}
body {
margin: 0;
background-color: #FAFAFA;
font-family: 'Roboto', sans-serif;
}
}
+1
View File
@@ -1,5 +1,6 @@
import React from 'react';
import ToastContainer from './ToastContainer';
import './App.css';
import 'material-design-lite';
import AppRouter from '../AppRouter';
@@ -14,7 +14,8 @@ const ApproveButton = ({ active, minimal, onClick, className, disabled }) => {
className={cn(
styles.root,
{ [styles.minimal]: minimal, [styles.active]: active },
className
className,
'talk-admin-approve-button'
)}
onClick={onClick}
disabled={disabled || active}
@@ -19,7 +19,7 @@ class BanUserDialog extends React.Component {
}
handleMessageChange = e => {
const { value: message } = e;
const { target: { value: message } } = e;
this.setState({ message });
};
@@ -30,6 +30,12 @@ class BanUserDialog extends React.Component {
});
};
handlePerform = () => {
this.props.onPerform({
message: this.state.message,
});
};
renderStep0() {
const { onCancel, username, info } = this.props;
@@ -63,7 +69,7 @@ class BanUserDialog extends React.Component {
}
renderStep1() {
const { onCancel, onPerform } = this.props;
const { onCancel } = this.props;
const { message } = this.state;
return (
@@ -95,7 +101,7 @@ class BanUserDialog extends React.Component {
<Button
className={cn('talk-ban-user-dialog-button-confirm')}
cStyle="black"
onClick={onPerform}
onClick={this.handlePerform}
raised
>
{t('bandialog.send')}
@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import Label from 'coral-ui/components/Label';
import Slot from 'coral-framework/components/Slot';
import { t } from 'coral-framework/services/i18n';
import FlagLabel from 'coral-ui/components/FlagLabel';
import cn from 'classnames';
import styles from './CommentLabels.css';
@@ -63,10 +64,14 @@ const CommentLabels = ({
<FlagLabel iconName="person">{getUserFlaggedType(actions)}</FlagLabel>
)}
{hasSuspectedWords(actions) && (
<FlagLabel iconName="sms_failed">Suspect</FlagLabel>
<FlagLabel iconName="sms_failed">
{t('flags.reasons.comment.suspect_word')}
</FlagLabel>
)}
{hasHistoryFlag(actions) && (
<FlagLabel iconName="sentiment_very_dissatisfied">History</FlagLabel>
<FlagLabel iconName="sentiment_very_dissatisfied">
{t('flags.reasons.comment.trust')}
</FlagLabel>
)}
</div>
<Slot
@@ -0,0 +1,16 @@
.external {
margin-bottom: 20px;
}
.separator h5 {
text-align: center;
font-size: 1.2em;
}
.slot > * {
margin-bottom: 8px;
&:last-child {
margin-bottom: 0px;
}
}
@@ -0,0 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './External.css';
import Slot from 'coral-framework/components/Slot';
import IfSlotIsNotEmpty from 'coral-framework/components/IfSlotIsNotEmpty';
const External = ({ slot }) => (
<IfSlotIsNotEmpty slot={slot}>
<div>
<div className={styles.external}>
<Slot fill={slot} className={styles.slot} />
</div>
<div className={styles.separator}>
<h5>Or</h5>
</div>
</div>
</IfSlotIsNotEmpty>
);
External.propTypes = {
slot: PropTypes.string.isRequired,
};
export default External;
@@ -7,7 +7,6 @@ import styles from './Header.css';
import t from 'coral-framework/services/i18n';
import { Logo } from './Logo';
import { can } from 'coral-framework/services/perms';
import ModerationIndicator from '../routes/Moderation/containers/Indicator';
import CommunityIndicator from '../routes/Community/containers/Indicator';
const CoralHeader = ({
@@ -32,7 +31,6 @@ const CoralHeader = ({
activeClassName={styles.active}
>
{t('configure.moderate')}
<ModerationIndicator root={root} data={data} />
</IndexLink>
)}
<Link
@@ -1,10 +1,8 @@
.indicator {
display: inline-block;
background-color: #E46D59;
border-radius: 10px;
position: absolute;
top: 50%;
width: 7px;
height: 7px;
margin-top: -4px;
margin-left: 7px;
}
@@ -0,0 +1,103 @@
.karmaTooltip {
position: relative;
display: inline-block;
margin: 2px 4px 0;
}
.icon {
font-size: 16px;
color: #0D5B8F;
user-select: none;
-webkit-tap-highlight-color:rgba(0,0,0,0);
> i {
vertical-align: baseline;
}
}
.icon:hover {
cursor: pointer;
}
.menu {
background-color: white;
border: solid 1px #999;
border-radius: 3px;
padding: 10px;
position: absolute;
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2);
z-index: 10;
top: 32px;
left: -100px;
width: 150px;
text-align: left;
color: #616161;
}
.menu::before{
content: '';
border: 10px solid transparent;
border-top-color: #999;
position: absolute;
left: 96px;
top: -20px;
transform: rotate(180deg);
}
.menu::after{
content: '';
border: 10px solid transparent;
border-top-color: white;
position: absolute;
left: 96px;
top: -19px;
transform: rotate(180deg);
}
.menu ul {
list-style: none;
padding: 0;
li {
display: flex;
justify-content: space-between;
margin: 5px 0;
}
}
.label {
padding: 4px 5px;
border-radius: 3px;
color: #fff;
font-weight: 400;
text-align: center;
font-size: .9em;
line-height: normal;
letter-spacing: .4px;
min-width: 25px;
display: block;
/* &.reliable { background-color: #03AB61; } */
/* &.neutral { background-color: #616161; } */
&.unreliable { background-color: #F44336; }
}
.descriptionList {
padding: 0;
margin: 0;
list-style: none;
}
.strongItem {
margin-right: 3px;
}
.descriptionItem {
font-size: 0.9em;
}
.link {
color: #2B7EB5;
text-decoration: underline;
display: block;
}
@@ -0,0 +1,82 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Icon } from 'coral-ui';
import styles from './KarmaTooltip.css';
import ClickOutside from 'coral-framework/components/ClickOutside';
import t from 'coral-framework/services/i18n';
const initialState = { menuVisible: false };
class KarmaTooltip extends React.Component {
static propTypes = {
thresholds: PropTypes.shape({
reliable: PropTypes.number.isRequired,
unreliable: PropTypes.number.isRequired,
}).isRequired,
};
state = initialState;
toogleMenu = () => {
this.setState({ menuVisible: !this.state.menuVisible });
};
hideMenu = () => {
this.setState({ menuVisible: false });
};
render() {
const { thresholds: { unreliable } } = this.props;
const { menuVisible } = this.state;
return (
<ClickOutside onClickOutside={this.hideMenu}>
<div className={cn(styles.karmaTooltip, 'talk-admin-karma-tooltip')}>
<span
onClick={this.toogleMenu}
className={cn(styles.icon, 'talk-admin-karma-tooltip-icon')}
>
<Icon name="info" />
</span>
{menuVisible && (
<div className={cn(styles.menu, 'talk-admin-karma-tooltip-menu')}>
<strong>{t('user_detail.user_karma_score')}</strong>
<ul>
{/* NOTE: we may display this data in the future, keeping around for that eventuality */}
{/* <li>
<span>Reliable</span>{' '}
<span className={cn(styles.label, styles.reliable)}>
&ge; {reliable}
</span>
</li>
<li>
<span>Neutral</span>{' '}
<span className={cn(styles.label, styles.neutral)}>
&lt; {reliable}, &gt; {unreliable}
</span>
</li> */}
<li>
<span>{t('user_detail.unreliable')}</span>{' '}
<span className={cn(styles.label, styles.unreliable)}>
&le; {unreliable}
</span>
</li>
</ul>
<a
className={styles.link}
href={t('user_detail.karma_docs_link')}
target="_blank"
>
{t('user_detail.learn_more')}
</a>
</div>
)}
</div>
</ClickOutside>
);
}
}
export default KarmaTooltip;
@@ -14,7 +14,8 @@ const RejectButton = ({ active, minimal, onClick, className, disabled }) => {
className={cn(
styles.root,
{ [styles.minimal]: minimal, [styles.active]: active },
className
className,
'talk-admin-reject-button'
)}
onClick={onClick}
disabled={disabled || active}
+49 -41
View File
@@ -4,6 +4,7 @@ import styles from './SignIn.css';
import { Button, TextField, Alert } from 'coral-ui';
import cn from 'classnames';
import Recaptcha from 'coral-framework/components/Recaptcha';
import External from './External';
class SignIn extends React.Component {
recaptcha = null;
@@ -33,48 +34,55 @@ class SignIn extends React.Component {
render() {
const { email, password, errorMessage, requireRecaptcha } = this.props;
return (
<form className="talk-admin-login-sign-in" onSubmit={this.handleSubmit}>
{errorMessage && <Alert>{errorMessage}</Alert>}
<TextField
id="email"
label="Email Address"
value={email}
onChange={this.handleEmailChange}
/>
<TextField
id="password"
label="Password"
value={password}
onChange={this.handlePasswordChange}
type="password"
/>
{requireRecaptcha && (
<div className={styles.recaptcha}>
<Recaptcha
ref={this.handleRecaptchaRef}
onVerify={this.props.onRecaptchaVerify}
/>
</div>
)}
<Button
className={cn(styles.signInButton, 'talk-admin-login-sign-in-button')}
type="submit"
cStyle="black"
full
>
Sign In
</Button>
<p className={styles.forgotPasswordCTA}>
Forgot your password?{' '}
<a
href="#"
className={styles.forgotPasswordLink}
onClick={this.handleForgotPasswordLink}
<div className="talk-admin-login-sign-in">
<External slot="authExternalAdminSignIn" />
<form onSubmit={this.handleSubmit}>
{errorMessage && <Alert>{errorMessage}</Alert>}
<TextField
id="email"
label="Email Address"
value={email}
onChange={this.handleEmailChange}
/>
<TextField
id="password"
label="Password"
value={password}
onChange={this.handlePasswordChange}
type="password"
/>
{requireRecaptcha && (
<div className={styles.recaptcha}>
<Recaptcha
ref={this.handleRecaptchaRef}
onVerify={this.props.onRecaptchaVerify}
/>
</div>
)}
<Button
className={cn(
styles.signInButton,
'talk-admin-login-sign-in-button'
)}
type="submit"
cStyle="black"
full
>
Request a new one.
</a>
</p>
</form>
Sign In
</Button>
<p className={styles.forgotPasswordCTA}>
{/* TODO: translate */}
Forgot your password?{' '}
<a
href="#"
className={styles.forgotPasswordLink}
onClick={this.handleForgotPasswordLink}
>
Request a new one.
</a>
</p>
</form>
</div>
);
}
}
@@ -35,44 +35,49 @@
margin-right: 20px;
}
.karmaStat {
display: flex;
}
.stat:last-child {
margin-right: 0px;
}
.statItem,
.statReportResult {
.statItem, .statReportResult, .statKarmaResult {
padding: 3px 5px;
background-color: #D8D8D8;
border-radius: 3px;
font-weight: 500;
display: block;
font-size: 0.9em;
line-height: normal;
letter-spacing: 0.4px;
min-width: 60px;
min-width: 25px;
display: block;
}
.statResult {
font-size: 1.5em;
padding: 5px 0;
display: inline-block;
text-align: center;
}
.statReportResult {
.statReportResult, .statKarmaResult {
color: white;
margin: 5px 0;
font-weight: 400;
text-align: center;
}
.statReportResult.reliable {
background-color: #749C48;
.statReportResult.reliable, .statKarmaResult.good {
background-color: #03AB61;
}
.statReportResult.neutral {
.statReportResult.neutral, .statKarmaResult.neutral {
background-color: #616161;
}
.statReportResult.unreliable {
.statReportResult.unreliable, .statKarmaResult.bad {
background-color: #F44336;
}
@@ -143,3 +148,7 @@
border-color: #E45241;
color: white;
}
.userDetailItem {
padding: 2px 0;
}
+42 -23
View File
@@ -6,11 +6,7 @@ import styles from './UserDetail.css';
import UserHistory from './UserHistory';
import { Slot } from 'coral-framework/components';
import UserDetailCommentList from '../components/UserDetailCommentList';
import {
getReliability,
isSuspended,
isBanned,
} from 'coral-framework/utils/user';
import { isSuspended, isBanned, getKarma } from 'coral-framework/utils/user';
import ButtonCopyToClipboard from './ButtonCopyToClipboard';
import ClickOutside from 'coral-framework/components/ClickOutside';
import {
@@ -25,6 +21,7 @@ import {
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
import UserInfoTooltip from './UserInfoTooltip';
import KarmaTooltip from './KarmaTooltip';
import t from 'coral-framework/services/i18n';
class UserDetail extends React.Component {
@@ -79,7 +76,13 @@ class UserDetail extends React.Component {
renderLoaded() {
const {
root,
root: { me, user, totalComments, rejectedComments },
root: {
me,
user,
totalComments,
rejectedComments,
settings: { karmaThresholds },
},
activeTab,
selectedCommentIds,
toggleSelect,
@@ -177,7 +180,7 @@ class UserDetail extends React.Component {
<div>
<ul className={styles.userDetailList}>
<li>
<li className={styles.userDetailItem}>
<Icon name="assignment_ind" />
<span className={styles.userDetailItem}>
{t('user_detail.member_since')}:
@@ -185,11 +188,24 @@ class UserDetail extends React.Component {
{new Date(user.created_at).toLocaleString()}
</li>
{user.profiles.map(({ id }) => (
<li key={id}>
<Icon name="email" />
<li className={styles.userDetailItem}>
<Icon name="email" />
<span className={styles.userDetailItem}>
{t('user_detail.email')}:
</span>
{user.email}{' '}
<ButtonCopyToClipboard
className={styles.copyButton}
icon="content_copy"
copyText={user.email}
/>
</li>
{user.profiles.map(({ provider, id }) => (
<li key={id} className={styles.userDetailItem}>
<Icon name="device_hub" />
<span className={styles.userDetailItem}>
{t('user_detail.email')}:
{capitalize(provider)} {t('user_detail.id')}:
</span>
{id}{' '}
<ButtonCopyToClipboard
@@ -216,18 +232,21 @@ class UserDetail extends React.Component {
{rejectedPercent.toFixed(1)}%
</span>
</li>
<li className={styles.stat}>
<span className={styles.statItem}>
{t('user_detail.reports')}
</span>
<span
className={cn(
styles.statReportResult,
styles[getReliability(user.reliable.flagger)]
)}
>
{capitalize(getReliability(user.reliable.flagger))}
</span>
<li className={cn(styles.stat, styles.karmaStat)}>
<div>
<span className={styles.statItem}>
{t('user_detail.karma')}
</span>
<span
className={cn(
styles.statKarmaResult,
styles[getKarma(user.reliable.commenter)]
)}
>
{user.reliable.commenterKarma}
</span>
</div>
<KarmaTooltip thresholds={karmaThresholds.comment} />
</li>
</ul>
</div>
@@ -12,7 +12,7 @@ import { compose } from 'react-apollo';
import t from 'coral-framework/services/i18n';
class BanUserDialogContainer extends Component {
banUser = async () => {
banUser = async ({ message }) => {
const {
userId,
commentId,
@@ -21,7 +21,7 @@ class BanUserDialogContainer extends Component {
setCommentStatus,
hideBanUserDialog,
} = this.props;
await banUser({ id: userId, message: '' });
await banUser({ id: userId, message });
hideBanUserDialog();
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
await setCommentStatus({ commentId, status: 'REJECTED' });
+4 -5
View File
@@ -2,21 +2,20 @@ import { gql } from 'react-apollo';
import withQuery from 'coral-framework/hocs/withQuery';
import Header from '../components/Header';
import CommunityIndicator from '../routes/Community/containers/Indicator';
import ModerationIndicator from '../routes/Moderation/containers/Indicator';
// TODO: eventually we will readd modqueue counts
// import ModerationIndicator from '../routes/Moderation/containers/Indicator';
import { getDefinitionName } from 'coral-framework/utils';
export default withQuery(
gql`
query TalkAdmin_Header($nullID: ID) {
...${getDefinitionName(ModerationIndicator.fragments.root)}
query TalkAdmin_Header {
...${getDefinitionName(CommunityIndicator.fragments.root)}
}
${ModerationIndicator.fragments.root}
${CommunityIndicator.fragments.root}
`,
{
options: {
variables: { nullID: null },
// variables: { nullID: null },
},
}
)(Header);
+2 -2
View File
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withSignIn } from 'coral-framework/hocs';
import { withSignIn, withPopupAuthHandler } from 'coral-framework/hocs';
import { compose } from 'recompose';
import SignIn from '../components/SignIn';
@@ -55,4 +55,4 @@ SignInContainer.propTypes = {
requireRecaptcha: PropTypes.bool.isRequired,
};
export default compose(withSignIn)(SignInContainer);
export default compose(withSignIn, withPopupAuthHandler)(SignInContainer);
@@ -179,12 +179,14 @@ export const withUserDetailQuery = withQuery(
id
username
created_at
email
profiles {
id
provider
}
reliable {
flagger
commenter
commenterKarma
}
state {
status {
@@ -225,6 +227,14 @@ export const withUserDetailQuery = withQuery(
}
${getSlotFragmentSpreads(slots, 'user')}
}
settings {
karmaThresholds {
comment {
reliable
unreliable
}
}
}
me {
id
}
+51 -2
View File
@@ -24,6 +24,25 @@ const userRoleFragment = gql`
}
`;
/**
* calculateReliability will determine the reliability of a karma score based on
* the settings for the karma type.
*
* @param {Number} karma - the current karma value/score for the given user
* @param {Object} thresholds - the karma thresholds to base the karma computation on
*/
const calculateReliability = (karma, { reliable, unreliable }) => {
if (karma >= reliable) {
return true;
}
if (karma <= unreliable) {
return false;
}
return null;
};
export default {
mutations: {
SetUserRole: ({ variables: { id, role } }) => ({
@@ -156,7 +175,9 @@ export default {
}
const updated = update(prev, {
users: {
nodes: { $apply: nodes => nodes.filter(node => node.id !== id) },
nodes: {
$apply: nodes => nodes.filter(node => node.id !== id),
},
},
});
return updated;
@@ -185,7 +206,9 @@ export default {
const updated = update(prev, {
...decrement,
flaggedUsers: {
nodes: { $apply: nodes => nodes.filter(node => node.id !== id) },
nodes: {
$apply: nodes => nodes.filter(node => node.id !== id),
},
},
});
return updated;
@@ -295,12 +318,38 @@ export default {
updateQueries: {
CoralAdmin_UserDetail: prev => {
const increment = {
user: {
reliable: {
commenter: {
$set: calculateReliability(
prev.user.reliable.commenterKarma - 1,
prev.settings.karmaThresholds.comment
),
},
commenterKarma: {
$apply: count => count - 1,
},
},
},
rejectedComments: {
$apply: count => (count < prev.totalComments ? count + 1 : count),
},
};
const decrement = {
user: {
reliable: {
commenter: {
$set: calculateReliability(
prev.user.reliable.commenterKarma + 1,
prev.settings.karmaThresholds.comment
),
},
commenterKarma: {
$apply: count => count + 1,
},
},
},
rejectedComments: {
$apply: count => (count > 0 ? count - 1 : 0),
},
@@ -133,6 +133,7 @@ th.header:nth-child(2), th.header:nth-child(3) {
.roleDropdown {
width: 150px;
text-align: left;
}
.roleOption {
@@ -130,7 +130,9 @@ class People extends React.Component {
{user.username}
</button>
<span className={styles.email}>
{user.profiles.map(({ id }) => id)}
{user.email
? user.email
: user.profiles.map(p => p.id).join(', ')}
</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
@@ -200,7 +202,7 @@ class People extends React.Component {
</td>
<td className="mdl-data-table__cell--non-numeric">
<Dropdown
className={cn(
toggleClassName={cn(
'talk-admin-community-people-dd-role',
styles.roleDropdown
)}
@@ -82,6 +82,20 @@ class StreamSettings extends React.Component {
this.props.updatePending({ updater });
};
updateDisableCommenting = () => {
const updater = {
disableCommenting: {
$set: !this.props.settings.disableCommenting,
},
};
this.props.updatePending({ updater });
};
updateDisableCommentingMessage = value => {
const updater = { disableCommentingMessage: { $set: value } };
this.props.updatePending({ updater });
};
updateAutoClose = () => {
const updater = {
autoCloseStream: { $set: !this.props.settings.autoCloseStream },
@@ -192,6 +206,25 @@ class StreamSettings extends React.Component {
&nbsp;
{t('configure.edit_comment_timeframe_text_post')}
</ConfigureCard>
<ConfigureCard
checked={settings.disableCommenting}
onCheckbox={this.updateDisableCommenting}
title={t('configure.disable_commenting_title')}
>
<p>{t('configure.disable_commenting_desc')}</p>
<div
className={cn(
styles.configSettingDisableCommenting,
settings.disableCommenting ? null : styles.hidden
)}
>
<MarkdownEditor
className={styles.descriptionBox}
onChange={this.updateDisableCommentingMessage}
value={settings.disableCommentingMessage}
/>
</div>
</ConfigureCard>
<ConfigureCard
checked={settings.autoCloseStream}
onCheckbox={this.updateAutoClose}
@@ -39,6 +39,8 @@ export default compose(
autoCloseStream
closedTimeout
closedMessage
disableCommenting
disableCommentingMessage
${getSlotFragmentSpreads(slots, 'settings')}
}
`,
@@ -1,5 +1,4 @@
@custom-media --big-viewport (min-width: 780px);
.root {
border-bottom: 1px solid #e0e0e0;
font-size: 18px;
@@ -13,7 +12,6 @@
margin-top: 13px;
min-height: 0;
outline: 0;
/*
Fix rendering issues in Safari by promoting this
into its own layer.
@@ -21,7 +19,6 @@
https://www.pivotaltracker.com/story/show/151142211
*/
transform: translateZ(0);
&:last-child {
border-bottom: none;
}
@@ -39,7 +36,6 @@
display: flex;
align-items: center;
justify-content: space-between;
}
.author {
@@ -74,7 +70,7 @@
max-width: 500px;
font-weight: 300;
font-size: 16px;
word-break: break-all;
overflow-wrap: break-word;
}
.created {
@@ -95,7 +91,6 @@
font-weight: 500;
line-height: 1.2;
max-width: 500px;
a {
display: inline-block;
color: #063b9a;
@@ -103,17 +98,15 @@
font-weight: 500;
letter-spacing: .5px;
margin-left: 10px;
font-size: 13px;
margin-left: 5px;
padding-bottom: 0px;
border-bottom: solid 1px;
line-height: 16px;
&:hover {
opacity: .9;
cursor: pointer;
}
}
}
}
@@ -138,12 +131,10 @@
cursor: pointer;
font-weight: normal;
white-space: nowrap;
&:hover {
text-decoration: underline;
opacity: .9;
}
i {
font-size: 12px;
position: relative;
@@ -172,7 +163,6 @@
align-items: center;
justify-content: flex-end;
margin-right: 14px;
i {
margin-right: 5px;
}
@@ -181,7 +171,6 @@
@media (--big-viewport) {
.root {
margin-bottom: 30px;
&:last-child {
border-bottom: 1px solid #e0e0e0;
}
@@ -190,7 +179,6 @@
.commentContent {
display: flex;
blockquote {
font-size: inherit;
line-height: inherit;
@@ -63,10 +63,6 @@ class Moderation extends Component {
this.props.toggleStorySearch(true);
};
getActiveTabCount = (props = this.props) => {
return props.root[`${props.activeTab}Count`];
};
moderate = accept => {
const {
acceptComment,
@@ -139,12 +135,14 @@ class Moderation extends Component {
const comments = root[activeTab];
const activeTabCount = this.getActiveTabCount();
const menuItems = Object.keys(queueConfig).map(queue => ({
key: queue,
name: queueConfig[queue].name,
icon: queueConfig[queue].icon,
count: root[`${queue}Count`],
indicator:
['premod', 'reported'].includes(queue) && root[queue].nodes.length > 0,
// TODO: Eventually we'll reintroduce counting
// count: root[`${props.queue}Count`]
}));
const slotPassthrough = {
@@ -189,7 +187,6 @@ class Moderation extends Component {
loadMore={this.loadMore}
commentBelongToQueue={this.props.commentBelongToQueue}
isLoadingMore={this.state.isLoadingMore}
commentCount={activeTabCount}
currentUserId={this.props.currentUser.id}
viewUserDetail={viewUserDetail}
selectCommentId={props.selectCommentId}
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import CountBadge from '../../../components/CountBadge';
import Indicator from '../../../components/Indicator';
import styles from './ModerationMenu.css';
import { Icon } from 'coral-ui';
import { Link } from 'react-router';
@@ -24,6 +24,7 @@ const ModerationMenu = ({ asset = {}, items, getModPath, activeTab }) => {
>
{items.map(queue => (
<Link
id={`talk-admin-moderate-tab-${queue.key}`}
key={queue.key}
to={getModPath(queue.key, asset.id)}
className={cn('mdl-tabs__tab', styles.tab, {
@@ -32,7 +33,7 @@ const ModerationMenu = ({ asset = {}, items, getModPath, activeTab }) => {
activeClassName={styles.active}
>
<Icon name={queue.icon} className={styles.tabIcon} /> {queue.name}{' '}
<CountBadge count={queue.count} />
{queue.indicator && <Indicator />}
</Link>
))}
</div>
@@ -15,6 +15,7 @@ import {
} from 'react-virtualized';
import throttle from 'lodash/throttle';
import key from 'keymaster';
import cn from 'classnames';
const hasComment = (nodes, id) => nodes.some(node => node.id === id);
@@ -204,7 +205,7 @@ class ModerationQueue extends React.Component {
}
componentDidUpdate(prev) {
const { commentCount, selectedCommentId } = this.props;
const { selectedCommentId, hasNextPage } = this.props;
const switchedToMultiMode = prev.singleView && !this.props.singleView;
const switchedMode = prev.singleView !== this.props.singleView;
@@ -212,7 +213,6 @@ class ModerationQueue extends React.Component {
prev.selectedCommentId !== selectedCommentId && selectedCommentId;
const moderatedLastComment =
prev.comments.length > 0 && this.getCommentCountWithoutDagling() === 0;
const hasMoreComment = commentCount > 0;
if (switchedToMultiMode) {
// Reflow virtual list.
@@ -223,7 +223,7 @@ class ModerationQueue extends React.Component {
this.scrollToSelectedComment();
}
if (moderatedLastComment && hasMoreComment) {
if (moderatedLastComment && hasNextPage) {
this.props.loadMore();
}
}
@@ -240,10 +240,7 @@ class ModerationQueue extends React.Component {
const index = view.findIndex(
({ id }) => id === this.props.selectedCommentId
);
if (
index === view.length - 1 &&
this.getCommentCountWithoutDagling() !== this.props.commentCount
) {
if (index === view.length - 1 && this.props.hasNextPage) {
await this.props.loadMore();
this.selectDown();
return;
@@ -384,6 +381,11 @@ class ModerationQueue extends React.Component {
...props
} = this.props;
const rootClassName = cn(
styles.root,
`talk-admin-moderate-queue-${this.props.activeTab}`
);
if (comments.length === 0) {
return (
<div className={styles.root}>
@@ -405,7 +407,7 @@ class ModerationQueue extends React.Component {
const comment = comments[index];
return (
<div className={styles.root}>
<div className={rootClassName}>
<Comment
root={this.props.root}
key={comment.id}
@@ -427,7 +429,7 @@ class ModerationQueue extends React.Component {
const view = this.state.view;
return (
<div className={styles.root}>
<div className={rootClassName}>
<ViewMore
viewMore={() => this.viewNewComments()}
count={comments.length - view.length}
@@ -467,7 +469,6 @@ ModerationQueue.propTypes = {
acceptComment: PropTypes.func.isRequired,
commentBelongToQueue: PropTypes.func.isRequired,
cleanUpQueue: PropTypes.func.isRequired,
commentCount: PropTypes.number.isRequired,
loadMore: PropTypes.func.isRequired,
singleView: PropTypes.bool,
isLoadingMore: PropTypes.bool,
@@ -314,11 +314,11 @@ class ModerationContainer extends Component {
const currentQueueConfig = Object.assign({}, this.props.queueConfig);
if (premodEnabled && root.newCount === 0) {
if (premodEnabled && root.new.nodes.length === 0) {
delete currentQueueConfig.new;
}
if (!premodEnabled && root.premodCount === 0) {
if (!premodEnabled && root.premod.nodes.length === 0) {
delete currentQueueConfig.premod;
}
@@ -402,7 +402,7 @@ const COMMENT_RESET_SUBSCRIPTION = gql`
const LOAD_MORE_QUERY = gql`
query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Cursor, $sortOrder: SORT_ORDER, $asset_id: ID, $tags:[String!], $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) {
comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sortOrder: $sortOrder, action_type: $action_type, tags: $tags}) {
comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sortOrder: $sortOrder, action_type: $action_type, tags: $tags, excludeDeleted: true}) {
nodes {
...${getDefinitionName(Comment.fragments.comment)}
}
@@ -456,7 +456,11 @@ const withModQueueQuery = withQuery(
}
`
)}
${Object.keys(queueConfig).map(
${
''
/*
TODO: eventually we'll reintroduce counting..
Object.keys(queueConfig).map(
queue => `
${queue}Count: commentCount(query: {
excludeDeleted: true,
@@ -478,7 +482,8 @@ const withModQueueQuery = withQuery(
asset_id: $asset_id,
})
`
)}
)*/
}
asset(id: $asset_id) @skip(if: $allAssets) {
id
title
@@ -92,9 +92,6 @@
.statusDropdown {
width: 150px;
}
.statusDropdownOption {
min-width: 100px;
text-align: left;
}
@@ -22,20 +22,12 @@ class Stories extends Component {
const closed = !!(closedAt && new Date(closedAt).getTime() < Date.now());
return (
<Dropdown
className={styles.statusDropdown}
toggleClassName={styles.statusDropdown}
value={closed}
onChange={value => this.props.onStatusChange(value, id)}
>
<Option
value={false}
label={t('streams.open')}
className={styles.statusDropdownOption}
/>
<Option
value={true}
label={t('streams.closed')}
className={styles.statusDropdownOption}
/>
<Option value={false} label={t('streams.open')} />
<Option value={true} label={t('streams.closed')} />
</Dropdown>
);
};
+27 -23
View File
@@ -3,32 +3,36 @@ import { getStaticConfiguration } from 'coral-framework/services/staticConfigura
import { createPostMessage } from 'coral-framework/services/postMessage';
document.addEventListener('DOMContentLoaded', () => {
try {
const staticConfig = getStaticConfiguration();
const { STATIC_ORIGIN: origin } = staticConfig;
const postMessage = createPostMessage(origin);
const staticConfig = getStaticConfiguration();
const { STATIC_ORIGIN: origin } = staticConfig;
const postMessage = createPostMessage(origin);
// Get the auth element and parse it as JSON by decoding it.
const auth = document.getElementById('auth');
const doc = document.implementation.createHTMLDocument('');
doc.body.innerHTML = auth.innerText;
// Get the auth element and parse it as JSON by decoding it.
const auth = document.getElementById('auth');
const doc = document.implementation.createHTMLDocument('');
doc.body.innerHTML = auth.innerText;
// Auth state is contained within the node.
const { err, data } = JSON.parse(doc.body.textContent);
if (err) {
// TODO: send back the error message.
console.error(err);
// Auth state is contained within the node.
const { err, data } = JSON.parse(doc.body.textContent);
if (err) {
const errDiv = document.createElement('div');
if (err.message) {
errDiv.innerText = `${err.name}: ${err.message}`;
} else {
// The data will contain a user and a token.
const { user, token } = data;
// Send the state back.
postMessage.post(HANDLE_SUCCESSFUL_LOGIN, { user, token });
errDiv.innerText = JSON.stringify(err);
}
} finally {
// Always close the window.
setTimeout(() => {
window.close();
}, 50);
document.body.appendChild(errDiv);
throw err;
}
// The data will contain a user and a token.
const { user, token } = data;
// Send the state back.
postMessage.post(HANDLE_SUCCESSFUL_LOGIN, { user, token });
// Close the window when all went well.
setTimeout(() => {
window.close();
}, 50);
});
+8
View File
@@ -8,6 +8,14 @@ import reducers from './reducers';
import TalkProvider from 'coral-framework/components/TalkProvider';
import pluginsConfig from 'pluginsConfig';
// Resolves touch handling issues encountered on IOS Safari under certain
// circumstances. It may be related to issues reported here:
//
// https://stackoverflow.com/questions/12363742/touchstart-event-is-not-firing-inside-iframe-ios-6
//
// Further details: https://www.pivotaltracker.com/story/show/157794038
document.body.addEventListener('touchstart', () => {});
async function main() {
const context = await createContext({
reducers,
@@ -31,6 +31,7 @@
font-weight: bold;
font-size: 12px;
color: #757575;
cursor: pointer;
}
.commentSummary {
@@ -11,16 +11,6 @@ import { getTotalReactionsCount } from 'coral-framework/utils';
import t from 'coral-framework/services/i18n';
class Comment extends React.Component {
goToStory = () => {
this.props.navigate(this.props.comment.asset.url);
};
goToConversation = () => {
this.props.navigate(
`${this.props.comment.asset.url}?commentId=${this.props.comment.id}`
);
};
render() {
const { comment, root } = this.props;
const reactionCount = getTotalReactionsCount(comment.action_summaries);
@@ -76,8 +66,8 @@ class Comment extends React.Component {
<div className="my-comment-asset">
<a
className={cn(styles.assetURL, 'my-comment-anchor')}
href="#"
onClick={this.goToStory}
href={this.props.comment.asset.url}
target="_parent"
>
{t('common.story')}:{' '}
{comment.asset.title ? comment.asset.title : comment.asset.url}
@@ -87,7 +77,13 @@ class Comment extends React.Component {
<div className={styles.sidebar}>
<ul>
<li>
<a onClick={this.goToConversation} className={styles.viewLink}>
<a
className={styles.viewLink}
href={`${this.props.comment.asset.url}?commentId=${
this.props.comment.id
}`}
target="_parent"
>
<Icon name="open_in_new" className={styles.iconView} />
{t('view_conversation')}
</a>
@@ -745,10 +745,21 @@ export default class Comment extends React.Component {
const id = `c_${comment.id}`;
// props that are passed down the slots.
const slotPassthrough = {
action: 'deleted',
comment,
};
return (
<div className={rootClassName} id={id}>
{isCommentDeleted(comment) ? (
<CommentTombstone action="deleted" />
<Slot
fill="commentTombstone"
defaultComponent={CommentTombstone}
size={1}
passthrough={slotPassthrough}
/>
) : (
<div>
{this.renderComment()}
@@ -39,6 +39,7 @@ class CommentTombstone extends React.Component {
CommentTombstone.propTypes = {
action: PropTypes.string,
comment: PropTypes.object,
onUndo: PropTypes.func,
};
@@ -4,6 +4,7 @@ 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';
@@ -181,7 +182,9 @@ class Stream extends React.Component {
setActiveReplyBox={setActiveReplyBox}
activeReplyBox={activeReplyBox}
notify={notify}
disableReply={asset.isClosed}
disableReply={
asset.isClosed || asset.settings.disableCommenting
}
postComment={postComment}
currentUser={currentUser}
postFlag={postFlag}
@@ -215,7 +218,7 @@ class Stream extends React.Component {
currentUser,
} = this.props;
const { keepCommentBox } = this.state;
const open = !asset.isClosed;
const open = !(asset.isClosed || asset.settings.disableCommenting);
const banned = get(currentUser, 'status.banned.status');
const suspensionUntil = get(currentUser, 'status.suspension.until');
@@ -293,7 +296,13 @@ class Stream extends React.Component {
)}
</div>
) : (
<p>{asset.settings.closedMessage}</p>
<div>
{asset.isClosed ? (
<p>{asset.settings.closedMessage}</p>
) : (
<Markdown content={asset.settings.disableCommentingMessage} />
)}
</div>
)}
<Slot fill="stream" passthrough={slotPassthrough} />
@@ -24,6 +24,7 @@ const slots = [
'commentAuthorName',
'commentAuthorTags',
'commentTimestamp',
'commentTombstone',
'commentContent',
];
@@ -434,6 +434,8 @@ const fragments = {
questionBoxIcon
closedTimeout
closedMessage
disableCommenting
disableCommentingMessage
charCountEnable
charCount
requireEmailConfirmation
+11 -5
View File
@@ -37,7 +37,8 @@ export default class Popup extends Component {
this.onBlur();
};
// Use `onunload` instead of `onbeforeunload` which is not supported in IOS Safari.
// Use `onunload` instead of `onbeforeunload` which is not supported in iOS
// Safari.
this.ref.onunload = () => {
this.onUnload();
@@ -46,10 +47,15 @@ export default class Popup extends Component {
}
this.resetCallbackInterval = setInterval(() => {
if (this.ref && this.ref.onload === null) {
clearInterval(this.resetCallbackInterval);
this.resetCallbackInterval = null;
this.setCallbacks();
try {
if (this.ref && this.ref.onload === null) {
clearInterval(this.resetCallbackInterval);
this.resetCallbackInterval = null;
this.setCallbacks();
}
} catch (err) {
// We could be getting a security exception here if the login page
// gets redirected to another domain to authenticate.
}
}, 50);
@@ -1,5 +1,5 @@
import React from 'react';
const PropTypes = require('prop-types');
import PropTypes from 'prop-types';
import { ApolloProvider } from 'react-apollo';
class TalkProvider extends React.Component {
+1 -1
View File
@@ -116,7 +116,7 @@ export const withRemoveTag = withMutation(
asset_id: assetId,
item_type: itemType,
},
o3timisticResponse: {
optimisticResponse: {
removeTag: {
__typename: 'ModifyTagResponse',
errors: null,
@@ -1,9 +1,9 @@
/* global __webpack_public_path__ */ // eslint-disable-line no-unused-vars
/* global __webpack_public_path__, __webpack_nonce__ */ // eslint-disable-line no-unused-vars
import { getStaticConfiguration } from 'coral-framework/services/staticConfiguration';
// Load the static url from the static configuration.
const { STATIC_URL } = getStaticConfiguration();
const { STATIC_URL, SCRIPT_NONCE } = getStaticConfiguration();
// Update the static url for the imported public path so dynamically imported
// chunks will use the correct path as defined by the process.env.STATIC_URL
@@ -14,3 +14,13 @@ const { STATIC_URL } = getStaticConfiguration();
// https://webpack.js.org/configuration/output/#output-publicpath
//
__webpack_public_path__ = STATIC_URL + 'static/';
// All dynamically included scripts that support nonce's will add this to their
// script tags.
//
// The __webpack_nonce__ can be referenced: https://webpack.js.org/guides/csp/
//
// Pending issues:
// - https://github.com/webpack-contrib/style-loader/pull/319
//
__webpack_nonce__ = SCRIPT_NONCE;
@@ -59,6 +59,7 @@ const withSetUsername = hoistStatics(WrappedComponent => {
}
const changeSet = { success: false, loading: false, error };
this.setState(changeSet);
throw error;
}
};
+74 -74
View File
@@ -1,7 +1,10 @@
import ta from 'timeago.js';
import { negotiateLanguages } from 'fluent-langneg/compat';
import has from 'lodash/has';
import get from 'lodash/get';
import merge from 'lodash/merge';
import first from 'lodash/first';
import isUndefined from 'lodash/isUndefined';
import moment from 'moment';
import 'moment/locale/ar';
@@ -12,8 +15,8 @@ import 'moment/locale/fr';
import 'moment/locale/nl';
import 'moment/locale/pt-br';
import { createStorage } from 'coral-framework/services/storage';
// timeago
import ta from 'timeago.js';
import arTA from 'timeago.js/locales/ar';
import daTA from 'timeago.js/locales/da';
import deTA from 'timeago.js/locales/de';
@@ -24,6 +27,7 @@ import pt_BRTA from 'timeago.js/locales/pt_BR';
import zh_CNTA from 'timeago.js/locales/zh_CN';
import zh_TWTA from 'timeago.js/locales/zh_TW';
// locales
import ar from '../../../locales/ar.yml';
import en from '../../../locales/en.yml';
import da from '../../../locales/da.yml';
@@ -35,8 +39,22 @@ import pt_BR from '../../../locales/pt_BR.yml';
import zh_CN from '../../../locales/zh_CN.yml';
import zh_TW from '../../../locales/zh_TW.yml';
const defaultLanguage = process.env.TALK_DEFAULT_LANG;
const translations = {
// the list of languages that are whitelisted. If false, all languages that are
// supported by Talk will be enabled.
const whitelistedLanguages =
process.env.TALK_WHITELISTED_LANGUAGES &&
process.env.TALK_WHITELISTED_LANGUAGES.split(',').map(l => l.trim());
// The default language. If the whitelisted languages is specified and the
// default language is not in that list, then the first language in the
// whitelisted list will be used as the default.
export const defaultLocale = whitelistedLanguages
? !whitelistedLanguages.includes(process.env.TALK_DEFAULT_LANG)
? whitelistedLanguages[0]
: process.env.TALK_DEFAULT_LANG
: process.env.TALK_DEFAULT_LANG;
export const translations = {
...ar,
...en,
...da,
@@ -49,84 +67,66 @@ const translations = {
...zh_TW,
};
let lang;
let timeagoInstance;
export const supportedLocales = Object.keys(translations);
function setLocale(storage, locale) {
storage.setItem('locale', locale);
}
let LOCALE;
let TIMEAGO_INSTANCE;
// detectLanguage will try to get the locale from storage if available,
// otherwise will try to get it from the navigator, otherwise, it will fallback
// to the default language.
function detectLanguage(storage) {
try {
const lang = storage.getItem('locale') || navigator.language;
if (lang) {
return lang;
}
} catch (err) {
console.warn(
'Error while trying to detect language, will fallback to',
err
);
}
console.warn('Could not detect language, will fallback to', defaultLanguage);
return defaultLanguage;
}
// getLocale will get the users locale from the local detector and parse it to a
// format we can work with.
function getLocale(storage) {
// Get the language from the local detector.
const lang = detectLanguage(storage);
// Some language strings come with additional subtags as defined in:
//
// https://www.ietf.org/rfc/bcp/bcp47.txt
//
// So we should strip that off if we find it.
return lang.split('-')[0];
}
const detectLanguage = () =>
first(
negotiateLanguages(
navigator.languages,
whitelistedLanguages || supportedLocales,
{
defaultLocale,
strategy: 'lookup',
}
)
);
export function setupTranslations() {
// Setup the translation framework with the storage.
const storage = createStorage('localStorage');
// locale
LOCALE = detectLanguage();
const locale = getLocale(storage);
setLocale(storage, locale);
// Setting moment
moment.locale(locale);
// Extract language key.
lang = locale.split('-')[0];
// Check if we have a translation in this language.
if (!(lang in translations)) {
lang = defaultLanguage;
}
// moment
moment.locale(LOCALE);
// timeago
ta.register('ar', arTA);
ta.register('es', esTA);
ta.register('da', daTA);
ta.register('de', deTA);
ta.register('fr', frTA);
ta.register('nl_NL', nlTA);
ta.register('pt_BR', pt_BRTA);
ta.register('zh_CN', zh_CNTA);
ta.register('zh_TW', zh_TWTA);
timeagoInstance = ta();
ta.register('nl-NL', nlTA);
ta.register('pt-BR', pt_BRTA);
ta.register('zh-CN', zh_CNTA);
ta.register('zh-TW', zh_TWTA);
TIMEAGO_INSTANCE = ta();
}
/**
* loadTranslations will load the new language pack into the existing ones.
*
* @param {Object} newTranslations translation object to merge into the existing
* languages.
*/
export function loadTranslations(newTranslations) {
// Merge the new translations into the existing translations.
merge(translations, newTranslations);
// Push new languages into the supportedLocales array.
Object.keys(newTranslations).forEach(language => {
if (!supportedLocales.includes(language)) {
supportedLocales.push(language);
}
});
}
export function timeago(time) {
return timeagoInstance.format(new Date(time), lang);
return TIMEAGO_INSTANCE.format(new Date(time), LOCALE);
}
/**
@@ -140,24 +140,24 @@ export function timeago(time) {
*/
export function t(key, ...replacements) {
let translation;
if (has(translations[lang], key)) {
translation = get(translations[lang], key);
if (has(translations[LOCALE], key)) {
translation = get(translations[LOCALE], key);
} else if (has(translations['en'], key)) {
translation = get(translations['en'], key);
console.warn(`${lang}.${key} language key not set`);
console.warn(`${LOCALE}.${key} language key not set`);
}
if (translation) {
// replace any {n} with the arguments passed to this method
replacements.forEach((str, i) => {
translation = translation.replace(new RegExp(`\\{${i}\\}`, 'g'), str);
});
return translation;
} else {
console.warn(`${lang}.${key} and en.${key} language key not set`);
if (!translation) {
console.warn(`${LOCALE}.${key} and en.${key} language key not set`);
return key;
}
// Handle replacements in the translation string.
return translation.replace(
/{(\d+)}/g,
(match, number) =>
!isUndefined(replacements[number]) ? replacements[number] : match
);
}
export default t;
@@ -56,10 +56,10 @@ export function createPostMessage(origin, scope = 'client') {
// Send the message.
target.postMessage(msg, origin);
},
subscribe: (handler, target = window) => {
subscribe(handler, target = window) {
// If this handler is already attached to the target, detach it.
if (has(listeners, [target, handler])) {
this.unsubscribeFromMessages(handler, target);
this.unsubscribe(handler, target);
}
// Wrap the listener with a origin check.
@@ -71,7 +71,7 @@ export function createPostMessage(origin, scope = 'client') {
// Attach the listener to the target.
target.addEventListener('message', listener);
},
unsubscribe: (handler, target = window) => {
unsubscribe(handler, target = window) {
if (!has(listeners, [target, handler])) {
return;
}
+1
View File
@@ -6,6 +6,7 @@
border: none;
touch-action: manipulation;
padding: 0;
margin: 0;
overflow: hidden;
+20
View File
@@ -273,3 +273,23 @@ export function translateError(error) {
}
return error.toString();
}
/**
* handlePopupAuth will optionally open a popup with the requested uri if the
* window is not already a popup.
*
* @param {String} uri the url to open the window? to
* @param {String} title the title of the new window? to open
* @param {String} features the features to use when opening a window?
*/
export function handlePopupAuth(
uri,
title = 'Login', // TODO: translate
features = 'menubar=0,resizable=0,width=500,height=550,top=200,left=500'
) {
if (window.opener) {
window.location = uri;
} else {
window.open(uri, title, features);
}
}
+15
View File
@@ -49,3 +49,18 @@ export const canUsernameBeUpdated = status => {
moment(created_at).isAfter(oldestEditTime)
);
};
/**
* getKarma
* retrieves karma value as string
*/
export const getKarma = reliability => {
if (reliability === null) {
return 'neutral';
} else if (reliability) {
return 'good';
} else {
return 'bad';
}
};
+17 -8
View File
@@ -7,17 +7,26 @@ import cn from 'classnames';
* BareButton is a button whose styling is stripped off to a minimum.
* Can pass anchor=true to use `a` instead of `button`
*/
const BareButton = ({ anchor, className, ...props }) => {
let Element = 'button';
if (anchor) {
Element = 'a';
export default class BareButton extends React.Component {
ref = null;
handleRef = ref => (this.ref = ref);
focus = () => this.ref.focus();
render() {
const { anchor, className, ...props } = this.props;
const Element = anchor ? 'a' : 'button';
return (
<Element
{...props}
className={cn(styles.bare, className)}
ref={this.handleRef}
/>
);
}
return <Element {...props} className={cn(styles.bare, className)} />;
};
}
BareButton.propTypes = {
className: PropTypes.string,
anchor: PropTypes.bool,
};
export default BareButton;
+13 -18
View File
@@ -1,32 +1,27 @@
.dropdown {
display: inline-block;
position: relative;
height: 34px;
background: #2c2c2c;
box-sizing: border-box;
color: white;
border-radius: 3px;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
line-height: 20px;
font-size: 0.98em;
border-radius: 3px;
cursor: pointer;
&.disabled {
color: #e5e5e5;
background: #888;
cursor: default;
pointer-events: none;
}
}
.toggle {
padding: 8px 45px 8px 15px;
outline: none;
color: white;
background: #2c2c2c;
border-radius: 3px;
height: 34px;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
line-height: 20px;
font-size: 0.98em;
&:focus {
background: #888;
}
&:disabled {
color: #e5e5e5;
background: #888;
}
}
.toggleOpen {
+5 -19
View File
@@ -4,6 +4,7 @@ import styles from './Dropdown.css';
import Icon from './Icon';
import cn from 'classnames';
import ClickOutside from 'coral-framework/components/ClickOutside';
import { BareButton } from 'coral-ui';
class Dropdown extends React.Component {
toggleRef = null;
@@ -88,16 +89,6 @@ class Dropdown extends React.Component {
this.toggle();
};
handleKeyDown = e => {
const code = e.which;
// 13 = Return, 32 = Space
if (code === 13 || code === 32) {
e.preventDefault();
this.toggle();
}
};
hideMenu = () => {
this.setState({
isOpen: false,
@@ -155,23 +146,18 @@ class Dropdown extends React.Component {
styles.dropdown,
className,
containerClassName,
'dd dd-container',
{
[styles.disabled]: disabled,
}
'dd dd-container'
)}
>
<div
<BareButton
className={cn(styles.toggle, toggleClassName, {
[cn(this.state.isOpen, toggleOpenClassName)]: this.state.isOpen,
})}
onClick={this.handleClick}
onKeyDown={this.handleKeyDown}
role="button"
aria-pressed={this.state.isOpen}
aria-haspopup="true"
tabIndex={disabled ? '-1' : '0'}
ref={this.handleToggleRef}
disabled={disabled}
>
{this.props.icon && (
<Icon
@@ -194,7 +180,7 @@ class Dropdown extends React.Component {
aria-hidden="true"
/>
)}
</div>
</BareButton>
{this.state.isOpen && (
<div>
<div tabIndex="0" onFocus={this.trapFocusBegin} />
+3
View File
@@ -1,7 +1,10 @@
.option {
min-width: 100px;
width: 100%;
padding: 10px;
outline: none;
white-space: nowrap;
text-align: left;
&:focus, &:hover {
background-color: #ccc;
+14 -11
View File
@@ -1,13 +1,15 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import styles from './Option.css';
import cn from 'classnames';
import { BareButton } from 'coral-ui';
class Option extends React.Component {
ref = null;
handleRef = ref => {
this.ref = ref;
this.ref = findDOMNode(ref);
};
focus = () => {
@@ -19,16 +21,17 @@ class Option extends React.Component {
const { className, label = '', onClick, onKeyDown } = this.props;
const id = this.props.id ? this.props.id : this.props.value;
return (
<li
className={cn(styles.option, className, 'dd-option')}
onClick={onClick}
onKeyDown={onKeyDown}
role="option"
tabIndex="0"
ref={this.handleRef}
id={id}
>
{label}
<li>
<BareButton
className={cn(styles.option, className, 'dd-option')}
onClick={onClick}
onKeyDown={onKeyDown}
role="option"
ref={this.handleRef}
id={id}
>
{label}
</BareButton>
</li>
);
}
+22
View File
@@ -36,6 +36,13 @@ const CONFIG = {
// rendered text.
DEFAULT_LANG: process.env.TALK_DEFAULT_LANG || 'en',
// WHITELISTED_LANGUAGES is a comma separated list of language/locales that
// should be supported. If the default language is not included in the
// whitelist list, the first entry will be used as the default.
WHITELISTED_LANGUAGES:
process.env.TALK_WHITELISTED_LANGUAGES &&
process.env.TALK_WHITELISTED_LANGUAGES.split(',').map(l => l.trim()),
// When TRUE, it ensures that database indexes created in core will not add
// indexes.
CREATE_MONGO_INDEXES: process.env.DISABLE_CREATE_MONGO_INDEXES !== 'TRUE',
@@ -48,6 +55,10 @@ const CONFIG = {
// request all of the records. Otherwise, minimum limits of 0 are enforced.
ALLOW_NO_LIMIT_QUERIES: process.env.TALK_ALLOW_NO_LIMIT_QUERIES === 'TRUE',
// ENABLE_STRICT_CSP enables strict CSP enforcement, and will enforce as well
// as report CSP violations.
ENABLE_STRICT_CSP: process.env.TALK_ENABLE_STRICT_CSP === 'TRUE',
// LOGGING_LEVEL specifies the logging level used by the bunyan logger.
LOGGING_LEVEL: ['fatal', 'error', 'warn', 'info', 'debug', 'trace'].includes(
process.env.TALK_LOGGING_LEVEL
@@ -313,6 +324,17 @@ CONFIG.JWT_COOKIE_NAMES = uniq(
])
);
//------------------------------------------------------------------------------
// Locale validation
//------------------------------------------------------------------------------
if (
CONFIG.WHITELISTED_LANGUAGES &&
!CONFIG.WHITELISTED_LANGUAGES.includes(CONFIG.DEFAULT_LANG)
) {
CONFIG.DEFAULT_LANG = CONFIG.WHITELISTED_LANGUAGES[0];
}
//------------------------------------------------------------------------------
// External database url's
//------------------------------------------------------------------------------
+1 -1
View File
@@ -81,4 +81,4 @@ TALK_JWT_SECRET=jX9y8G2ApcVLwyL{$6s3
Be default, we sign our tokens with HMAC using a SHA-256 hash algorithm. If you
want to change the signing algorithm, or use multiple signing/verifying keys,
refer to our [Advanced Configuration](/talk/advanced-configuration/) documentation.
refer to our [Advanced Configuration](/talk/advanced-configuration/#talk-jwt-secret) documentation.
@@ -31,6 +31,21 @@ image you can specify it with `--build-arg TALK_DEFAULT_LANG=en`.
Specify the default translation language. (Default `en`)
## TALK_WHITELISTED_LANGUAGES
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_WHITELISTED_LANGUAGES=en`.
Specify the comma separated whitelisted languages that you want the Talk
application to serve. This will override the available set of languages that
Talk will allow to be served.
If the [TALK_DEFAULT_LANG](#talk-default-lang) is not included in this list of
whitelisted languages, then the first whitelisted language will become the
default language. If this parameter is empty, then all languages supported by
Talk will be whitelisted. (Default '')
## TALK_DEFAULT_STREAM_TAB
This is a **Build Variable** and must be consumed during build. If using the
@@ -497,6 +512,15 @@ tracing of GraphQL requests.
**Note: Apollo Engine is a premium service, charges may apply.**
<!-- TODO: re-add CSP once we've resolved issues with dynamic webpack loading. -->
<!-- ## TALK_ENABLE_STRICT_CSP
Setting this to `TRUE` will enforce the [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
(or CSP). By default, this configuration is set to
[report only](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP#Testing_your_policy)
where the policy is not enforced, but any violations are reported to a provided
URI. (Default `FALSE`) -->
## ALLOW_NO_LIMIT_QUERIES
Setting this to `TRUE` will allow queries to execute without a limit (returns
+25 -19
View File
@@ -3,47 +3,53 @@ title: Trust
permalink: /trust/
---
Trust is a set of components within Talk that incorporate automated moderation
features based on a user's previous behavior.
Trust is a set of components within Talk that incorporate basic automated moderation features based on a user's previous behavior.
## User Karma Score
Using Trusts calculations, Talk will automatically pre-moderate comments of
users who have a negative karma score. All users start out with a `0` neutral
karma score. If they have a comment approved by a moderator, their score
increases by `1`; if they have a comment rejected by a moderator, it decreases
by `1`. When a commenter is labeled as Unreliable, their comments must be
moderated before they are posted.
Using Trusts calculations, Talk will automatically hold back, move to the Reported queue, and tag with a 'History' marker, any comments by users who have an Unreliable karma score. (This is for sites who practice post-moderation. If you set pre-moderation of all comments sitewide, this feature has limited use.)
When a commenter has one comment rejected, their next comment must be moderated
once in order to post freely again. If they instead get rejected again, then
they must have two of their comments approved in order to get added back to the
queue.
All users start out with a Neutral karma score (`0`). If they have a comment approved by a moderator, their score increases by `1`; if they have a comment rejected by a moderator, it decreases by `1`. When a commenter's score is labeled as Unreliable, their comments must be approved from the Reported queue before they are posted. Commenters are shown a message stating that a moderator will review their comment shortly.
Here are the default thresholds:
```text
-2 and lower: Unreliable
-1 to +2: Neutral
+3 and higher: Reliable
-1 and lower: Unreliable
0 to +1: Neutral
+2 and higher: Reliable (we don't do anything with this label right now)
```
You can configure your own Trust thresholds by using [TRUST_THRESHOLD](/talk/advanced-configuration/#trust-thresholds) in your
configuration.
So in this case, when a new commenter has their first comment rejected, their user karma score becomes `-1`, which triggers the Unreliable threshhold, and they must then have a comment approved by a moderator in order to post freely again. Until that occurs, all of their comments will be held back temporarily in the Reported queue, marked with a `History` tag.
If their next comment is also rejected, their user karma score is now `-2`, and they must have two comments approved in order to reach a Neutral score, and post without pre-approval.
We strongly recommend not telling your community how this system works, or where the threshholds lie. Firstly, they might try to game the system to meet approval, and secondly, it makes it harder for you to change the threshhold in the future. We suggest using language such as "We hold back comments for approval for a variety of reasons, including content, account history, and more."
If you see that a high proportion of first-time commenters on your site are abusive, you might want to change the threshhold to `0`, at least temporarily. You can configure your own Trust thresholds by using [TRUST_THRESHOLDS](/talk/advanced-configuration/#trust-thresholds) in your site configuration.
## Reliable and Unreliable Flaggers
Trust also calculates how reliable users are in terms of the comments they
report. This information is displayed to moderators in the User History drawer,
which is accessed by clicking on a users name in the Admin.
which is accessed by clicking on a users name in the Admin. Currently, no other action is taken based on this score.
If a user's reports mostly match what moderators reject, their Report status
will display to moderators as Reliable in the user information drawer. If a
user's reports mostly differ from what moderators reject, their Report status
will show as Unreliable.
If we don't have enough reports to make a call, or the reports even out, their
If Talk doesn't have enough reports to make a call, or the reports even out, their
status is Neutral.
Here are the default thresholds:
```text
-1 and lower: Unreliable
0 to +1: Neutral
+2 and higher: Reliable
```
You can configure your own Trust thresholds by using [TRUST_THRESHOLDS](/talk/advanced-configuration/#trust-thresholds) in your
configuration.
Note: Report Karma doesn't include reports of "I don't agree with this comment".
+3 -3
View File
@@ -11,9 +11,9 @@ can enable the following plugins:
- [talk-plugin-local-auth](/talk/plugin/talk-plugin-local-auth) - to facilitate email changes and email association
- [talk-plugin-profile-data](/talk/plugin/talk-plugin-profile-data) - to facilitate account download and deletion
Even if you don't reside in a location where GDPR will apply, it is recommended
to enable these features as a best practice to provide your users with control over their
own data.
Even if GDPR will not apply to you, it is recommended to enable these
features as a best practice to provide your users with control over their own
data.
## GPDR Feature Overview
+1 -1
View File
@@ -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} 'plugin-api/beta/components/ui';
import {Button} from 'plugin-api/beta/components/ui';
```
### Components
+1
View File
@@ -99,6 +99,7 @@ You won't have to use this to build plugins, but it's helpful to find where to e
* `commentReactions`
* `commentActions`
* `commentInputArea`
* `commentTombstone`
* `draftArea`
* `streamSettings`
+24 -9
View File
@@ -25,9 +25,10 @@ state (you don't use the auth anywhere else now). A great example of this is our
You can integrate Talk with any authentication service to enable single sign-on
for users. The steps to do that are:
1. Create a service that generates [JWT tokens](https://jwt.io).
1. Create a service that generates [JWT tokens](https://jwt.io/introduction/).
2. Push the token into the embed.
3. Implement the `tokenUserNotFound` hook to process the token.
3. Implement the [`tokenUserNotFound`](#implement-tokenusernotfound) hook to
process the token.
### Create JWT Token
@@ -39,7 +40,20 @@ Using that demo application, you'll see how you can:
1. Create a node application that can issue JWT's that are compatible with Talk.
2. Provide a validation endpoint that can be used by Talk to validate the token
and get the user via the `tokenUserNotFound` hook.
and get the user via the [`tokenUserNotFound`](#implement-tokenusernotfound)
hook.
It's also important to note a few requirements for proper integration with Talk.
The generated JWT must contain the following claims:
- [`jti`](https://tools.ietf.org/html/rfc7519#section-4.1.7): a unique identifier for the token (like a uuid/v4)
- [`exp`](https://tools.ietf.org/html/rfc7519#section-4.1.4): the expiry date of the token as a unix timestamp
- [`sub`](https://tools.ietf.org/html/rfc7519#section-4.1.2): the user identifier that can be used to lookup the user in the mongo
database
- The user may not yet exist in the database, but that's the responsibility
of the [`tokenUserNotFound`](#implement-tokenusernotfound) hook.
- [`iss`](https://tools.ietf.org/html/rfc7519#section-4.1.1): the issuer for the token must match the value of `TALK_JWT_ISSUER`
- [`aud`](https://tools.ietf.org/html/rfc7519#section-4.1.3): the audience for the token must match the value of `TALK_JWT_AUDIENCE`
### Push token into embed
@@ -47,7 +61,8 @@ We're assuming that your CMS is capable of authenticating a user account, or
at least having the user's details available to send off to the token creation
service we created/used in the previous step.
Using the token that was created for the user, you simply have to ammend the template where Talk is rendering to read as the following:
Using the token that was created for the user, you simply have to amend the
template where Talk is rendering to read as the following:
```js
Coral.Talk.render(document.getElementById('coralStreamEmbed'), {
@@ -72,14 +87,14 @@ example issuer and Talk must match:
| Talk | Token Issuer Example |
|------|----------------------|
|`JWT_ISSUER`|`JWT_ISSUER`|
|`JWT_AUDIENCE`|`JWT_AUDIENCE`|
|`SECRET`|`JWT_SECRET`*|
|[`TALK_JWT_ISSUER`](/talk/advanced-configuration/#talk-jwt-issuer)|`JWT_ISSUER`|
|[`TALK_JWT_AUDIENCE`](/talk/advanced-configuration/#talk-jwt-audience)|`JWT_AUDIENCE`|
|[`TALK_JWT_SECRET`](/talk/advanced-configuration/#talk-jwt-secret)|`JWT_SECRET`*|
\* Note that secrets is a pretty complex topic, refer to the
[TALK-JWT-SECRET](/talk/advanced-configuration/#TALK-JWT-SECRET) configuration
[TALK_JWT_SECRET](/talk/advanced-configuration/#talk-jwt-secret) configuration
reference, the basic takeaway is that the secret used to sign the tokens issued
by the issuer must be able to be verified by Talk.
For an example of implementing the plugin, refer to [`tokenUserNotFound`](/talk/reference/server/#tokenUserNotFound)
For an example of implementing the plugin, refer to [`tokenUserNotFound`](/talk/api/server/#tokenusernotfound)
reference.
+2 -3
View File
@@ -291,11 +291,10 @@ pre {
.content {
article {
p a:not(.plain-link) {
@extend .coral-link;
}
p a:not(.plain-link),
ul:not(.toc__menu) li a,
ol li a,
td a,
dd > a {
@extend .coral-link;
}
+19
View File
@@ -161,6 +161,24 @@ class ErrAssetCommentingClosed extends TalkError {
}
}
// ErrCommentingDisabled is returned when a comment or action is attempted while
// commenting has been disabled site-wide.
class ErrCommentingDisabled extends TalkError {
constructor(message = null) {
super(
'asset commenting is closed',
{
status: 400,
translation_key: 'COMMENTING_DISABLED',
},
{
// Include the closedMessage in the metadata piece of the error.
message,
}
);
}
}
/**
* ErrAuthentication is returned when there is an error authenticating and the
* message is provided.
@@ -387,6 +405,7 @@ module.exports = {
ErrAuthentication,
ErrCannotIgnoreStaff,
ErrCommentTooShort,
ErrCommentingDisabled,
ErrContainsProfanity,
ErrEditWindowHasEnded,
ErrEmailAlreadyVerified,
+12
View File
@@ -55,6 +55,18 @@ class SettingsLoader {
// assembled Settings object.
return zipObject(fields, values);
}
/**
* get, like select, will retrieve the settings, but get will only return a
* single setting.
*
* @param {String} field the field to get
*/
async get(field) {
const value = await this._loader.load(field);
return value;
}
}
module.exports = () => ({ Settings: new SettingsLoader() });
+2 -2
View File
@@ -57,8 +57,8 @@ const Comment = {
asset({ asset_id }, _, { loaders: { Assets } }) {
return Assets.getByID.load(asset_id);
},
async editing(comment, _, { loaders: { Settings } }) {
const { editCommentWindowLength } = await Settings.select(
editing: async (comment, _, { loaders: { Settings } }) => {
const editCommentWindowLength = await Settings.get(
'editCommentWindowLength'
);
+2
View File
@@ -15,6 +15,7 @@ const DontAgreeActionSummary = require('./dont_agree_action_summary');
const FlagAction = require('./flag_action');
const FlagActionSummary = require('./flag_action_summary');
const GenericUserError = require('./generic_user_error');
const KarmaThreshold = require('./karma_threshold');
const LocalUserProfile = require('./local_user_profile');
const RootMutation = require('./root_mutation');
const RootQuery = require('./root_query');
@@ -48,6 +49,7 @@ let resolvers = {
FlagAction,
FlagActionSummary,
GenericUserError,
KarmaThreshold,
LocalUserProfile,
RootMutation,
RootQuery,
+6
View File
@@ -0,0 +1,6 @@
const { property } = require('lodash');
module.exports = {
reliable: property('RELIABLE'),
unreliable: property('UNRELIABLE'),
};
+8 -2
View File
@@ -1,8 +1,13 @@
const { VIEW_PROTECTED_SETTINGS } = require('../../perms/constants');
const { decorateWithPermissionCheck } = require('./util');
const Settings = {};
const Settings = {
karmaThresholds: (
settings,
args,
{ connectors: { services: { Karma: { THRESHOLDS } } } }
) => THRESHOLDS,
};
// PROTECTED_SETTINGS are the settings keys that must be protected for only some
// eyes.
@@ -11,6 +16,7 @@ const PROTECTED_SETTINGS = {
autoCloseStream: [VIEW_PROTECTED_SETTINGS],
wordlist: [VIEW_PROTECTED_SETTINGS],
domains: [VIEW_PROTECTED_SETTINGS],
karmaThresholds: [VIEW_PROTECTED_SETTINGS],
};
// decorate the fields on the settings resolver with a permission check.
+49
View File
@@ -20,9 +20,17 @@ type Reliability {
# `null` if the reliability cannot be determined.
flagger: Boolean
# flaggerKarma will contains the number of agreed flags vs disagred flag
# count.
flaggerKarma: Int!
# Commenter will be `true` when the commenter is reliable, `false` if not, or
# `null` if the reliability cannot be determined.
commenter: Boolean
# commenterKarma the number of approved comments (not untouched) subtracted by
# the number of rejected comments.
commenterKarma: Int!
}
################################################################################
@@ -793,6 +801,29 @@ type Domains {
whitelist: [String!]!
}
# KarmaThreshold defines the bounds for which a User will become unreliable or
# reliable based on their karma score. If the score is equal or less than the
# unreliable value, they are unreliable. If the score is equal or more than the
# reliable value, they are reliable. If they are neither reliable or unreliable
# then they are neutral.
type KarmaThreshold {
reliable: Int!
unreliable: Int!
}
# KarmaThresholds contains the currently set thresholds for triggering Trust
# beheviour.
type KarmaThresholds {
# flag represents karma settings in relation to how well a User's flagging
# ability aligns with the moderation decicions made by moderators.
flag: KarmaThreshold!
# comment represents the karma setting in relation to how well a User's
# comments are moderated.
comment: KarmaThreshold!
}
# Settings stores the global settings for a given installation.
type Settings {
@@ -837,6 +868,13 @@ type Settings {
# closed.
closedMessage: String
# disableCommenting will disable commenting site-wide.
disableCommenting: Boolean
# disableCommentingMessage will be shown above the comment stream while
# commenting is disabled site-wide.
disableCommentingMessage: String
# editCommentWindowLength is the length of time (in milliseconds) after a
# comment is posted that it can still be edited by the author.
editCommentWindowLength: Int
@@ -858,6 +896,10 @@ type Settings {
# domains will return a given list of domains.
domains: Domains
# karmaThresholds contains the currently set thresholds for triggering Trust
# beheviour.
karmaThresholds: KarmaThresholds
}
################################################################################
@@ -1300,6 +1342,13 @@ input UpdateSettingsInput {
# closed.
closedMessage: String
# disableCommenting will disable commenting site-wide.
disableCommenting: Boolean
# disableCommentingMessage will be shown above the comment stream while
# commenting is disabled site-wide.
disableCommentingMessage: String
# charCountEnable is true when the character count restriction is enabled.
charCountEnable: Boolean
+1 -148
View File
@@ -292,55 +292,7 @@ ar:
suspect_word: "كلمة مشتبهة"
banned_word: "كلمة محظورة"
body_count: "يتجاوز النص الحد الأقصى للطول المسموح"
trust: "ثقة"
links: "رابط"
modqueue:
account: "account flags"
actions: Actions
all: all
all_streams: "All Streams"
notify_edited: '{0} edited comment "{1}"'
notify_accepted: '{0} accepted comment "{1}"'
notify_rejected: '{0} rejected comment "{1}"'
notify_flagged: '{0} flagged comment "{1}"'
notify_reset: '{0} reset status of comment "{1}"'
approve: "Approve"
approved: "Approved"
ban_user: "Ban"
billion: B
close: Close
empty_queue: "No more comments to moderate! You're all caught up. Go have some ☕️"
flagged: flagged
reported: reported
less_detail: "Less detail"
likes: likes
million: M
mod_faster: "Moderate faster with keyboard shortcuts"
moderate: "Moderate →"
more_detail: "More detail"
new: New
newest_first: "Newest First"
navigation: Navigation
next_comment: "Go to the next comment"
toggle_search: "Open search"
next_queue: "Switch queues"
oldest_first: "Oldest First"
premod: pre-mod
prev_comment: "Go to the previous comment"
reject: "Reject"
rejected: "Rejected"
reply: "Reply"
select_stream: "Select Stream"
shift_key: "⇧"
shortcuts: "Shortcuts"
sort: "Sort"
show_shortcuts: "Show Shortcuts"
singleview: "Zen mode"
thismenu: "Open this menu"
jump_to_queue: "Jump to specific queue"
thousand: k
try_these: "Try these"
view_more_shortcuts: "View more shortcuts"
my_comment_history: "سجل التعليقات"
name: اسم
no_agree_comment: "لا أوافق على هذا التعليق"
@@ -358,9 +310,6 @@ ar:
report_notif: "شكرا على الإبلاغ عن هذا التعليق. تم إبلاغ فريق الإشراف لدينا وسيراجعه قريبًا."
report_notif_remove: "لقد تمت إزالة بلاغك."
reported: بلغ عنه
comment_history_blank:
title: You have not written any comments
info: A history of your comments will appear here
settings:
from_settings_page: "من صفحة الملف الشخصي يمكنك مشاهدة سجل التعليقات."
my_comment_history: "سجل التعليقات"
@@ -378,104 +327,8 @@ ar:
step_1_header: "بلغ عن مشكلة"
step_2_header: "ساعدنا على الفهم"
step_3_header: "شكرا لك على المساهمة الخاصة بك"
streams:
all: All
article: Story
closed: Closed
empty_result: "No assets match this search. Maybe try widening your search?"
filter_streams: "Filter Streams"
newest: Newest
oldest: Oldest
open: Open
pubdate: "Publication Date"
search: Search
sort_by: "Sort By"
status: "Stream Status"
stream_status: "Stream Status"
suspenduser:
title_suspend: "Suspend User"
description_suspend: "You are suspending {0}. This comment will go to the Rejected queue, and {0} will not be allowed to like, report, reply or post until the suspension time is complete."
select_duration: "Select suspension duration"
one_hour: "1 hour"
hours: "{0} hours"
days: "{0} days"
hour: "{0} hours"
day: "{0} days"
cancel: "Cancel"
suspend_user: "Suspend User"
email_message_suspend: "Dear {0},\n\nIn accordance with {1}s community guidelines, your account has been temporarily suspended. During the suspension, you will be unable to comment, flag or engage with fellow commenters. Please rejoin the conversation {2}."
title_notify: "Notify the user of their temporary suspension"
notify_suspend_until: "User {0} has been temporarily suspended. This suspension will automatically end {1}."
description_notify: "Suspending this user will temporarily disable their account."
write_message: "Write a message"
send: Send
reject_username:
username: username
no_cancel: "No cancel"
description_reject: "Would you like to temporarily ban this user because of their {0}? Doing so will temporarily suspend this user until they rewrite their {0}."
title_notify: "Notify the user of their temporary suspension"
description_notify: "Suspending this user will temporarily disable their account."
title_reject: "We noticed you rejected a username"
suspend_user: "Suspend User"
yes_suspend: "Yes suspend"
email_message_reject: "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns."
write_message: "Write a message"
send: Send
thank_you: "نحن نقدر سلامتك وردود الفعل. سيراجع المشرف التقرير الخاص بك"
user:
bio_flags: "flags for this bio"
user_bio: "User Bio"
username_flags: "flags for this username"
user_detail:
remove_suspension: "Remove Suspension"
suspend: "Suspend User"
remove_ban: "Remove Ban"
ban: "Ban User"
member_since: "Member Since"
email: "Email"
total_comments: "Total Comments"
reject_rate: "Reject Rate"
reports: "Reports"
all: "All"
rejected: "Rejected"
user_history: "User History"
user_history:
user_banned: "User banned"
ban_removed: "Ban removed"
username_status: "Username {0}"
suspended: "Suspended, {0}"
suspension_removed: "Suspension removed"
system: "System"
date: "Date"
action: "Action"
taken_by: "Taken By"
user_impersonating: "هذا المستخدم ينتحل شخصية"
user_no_comment: "لم تترك تعليقا مطلقا. إنضم إلى المحادثة!"
username_offensive: "اسم المستخدم هذا مسيء"
view_conversation: "عرض المحادثة"
install:
initial:
description: "Let's set up your Talk community in just a few short steps."
submit: "Get Started"
add_organization:
description: "Please tell us the name of your organization. This will appear in emails when inviting new team members."
label: "Organization Name"
save: "Save"
create:
email: "Email address"
username: "Username"
password: "Password"
confirm_password: "Confirm Password"
organization_contact_email: "Organization Contact Email"
save: "Save"
permitted_domains:
title: "Permitted domains"
description: "Enter the domains you would like to permit for Talk, e.g. your local, staging and production environments (ex. localhost:3000, staging.domain.com, domain.com)."
submit: "Finish install"
final:
description: "Thanks for installing Talk! We sent an email to verify your email address. While you finish setting up the account, you can start engaging with your readers now."
launch: "Launch Talk"
close: "Close this Installer"
admin_sidebar:
view_options: "View Options"
sort_comments: "Sort Comments"
view_conversation: "عرض المحادثة"
-3
View File
@@ -211,7 +211,6 @@ da:
NO_SPECIAL_CHARACTERS: "Brugernavne kan kun indeholder bogstaver og _"
PASSWORD_LENGTH: "Adgangskoden er for kort"
PROFANITY_ERROR: "Brugernavne må ikke inholde stødende indhold. Kontakt venligst administratoren, hvis du mener at dette er en fejl."
RATE_LIMIT_EXCEEDED: "Rate limit exceeded"
USERNAME_IN_USE: "Brugernavnet er allerede i brug"
USERNAME_REQUIRED: "Du skal indtaste et brugernavn"
EMAIL_NOT_VERIFIED: "E-mail address not verified"
@@ -287,10 +286,8 @@ da:
comment_spam: "Spam"
comment_noagree: "Uenig"
comment_other: "Andre"
suspect_word: "Suspect Word"
banned_word: "Forbudt ord"
body_count: "Body overstiger max længde"
trust: "Stol"
links: "Link"
modqueue:
account: "konto flag"
+51 -13
View File
@@ -20,10 +20,13 @@ de:
bio_offensive: "Diese Biographie ist unangemessen"
cancel: "Abbrechen"
confirm_email:
email_confirmation: "E-Mail-Bestätigung"
click_to_confirm: "Unten klicken, um E-Mail-Adresse zu bestätigen"
confirm: "Bestätigen"
password_reset:
mail_sent: 'Falls Sie eine registriertes Konto haben, wurde Ihnen ein Zurücksetzen-Link an diese E-Mail-Adresse geschickt'
set_new_password: "Passwort ändern"
change_password_help: "Bitte geben Sie ein neues Passwort ein. Benutzen Sie ein sicheres!"
new_password: "Neues Passwort"
new_password_help: "Das Passwort benötigt mindestens 8 Zeichen"
confirm_new_password: "Neues Passwort bestätigen"
@@ -120,9 +123,12 @@ de:
custom_css_url: "Benutzerdefinierte CSS-URL"
custom_css_url_desc: "URL eines CSS-Stylesheets zum Überschreiben des Standard-Designs"
days: Tage
description: "Als Administrator können Sie die Einstellungen für den Kommentarbereich dieses Artikels anpassen:"
description: "Ändern Sie die Einstellungen für den Kommentarbereich dieses Artikels."
disable_commenting_title: "Kommentieren global deaktivieren"
disable_commenting_desc: "Verfassen Sie eine Nachricht, die angezeigt wird, solange das Kommentieren deaktiviert ist."
domain_list_text: "Geben Sie Domains an, für die diese Talk-Instanz freigegeben werden soll, z.B. für lokale Test- oder Produktionsumgebungen (Bsp.: localhost:3000 staging.domain.com domain.com)."
domain_list_title: "Zugelassene Domains"
edit_info: "Information bearbeiten"
edit_comment_timeframe_heading: "Zeitlimit zur Bearbeitung von Kommentaren"
edit_comment_timeframe_text_pre: "Kommentatoren haben"
edit_comment_timeframe_text_post: "Sekunden Zeit, um ihre Kommentare zu bearbeiten."
@@ -148,17 +154,31 @@ de:
open_stream_configuration: "Dieser Kommentarbereich ist momentan geöffnet. Nach dem Schließen dieses Kommentarbereich wird es nicht mehr möglich sein, zu kommentieren. Bestehende Kommentare bleiben sichtbar."
require_email_verification: "E-Mail-Bestätigung erforderlich"
require_email_verification_text: "Neue Nutzer müssen ihre E-Mail-Adresse bestätigen."
save: "Speichern"
save_changes: "Änderungen speichern"
shortcuts: Tastaturkürzel
sign_out: "Abmelden"
stories: Artikel
stream_settings: "Einstellungen Kommentarbereich"
access_message: "Sie müssen Administrator sein, um auf die Einstellungen zuzugreifen. Fragen Sie ggf. einen Administrator, der Ihnen mehr Recht zuweisen kann!"
suspect_word_title: "Liste verdächtiger Wörter"
suspect_word_text: "Kommentare, die diese Wörter oder Phrasen enthalten (unabhängig von Groß-/Kleinschreibung), werden im Kommentarbereich markiert. Geben Sie ein Wort ein und bestätigen Sie mit Eingabetaste oder Tab. Es ist auch möglich, einen komma-separierten Text einzufügen."
tech_settings: "Technische Einstellungen"
organization_information: "Über die Organisation"
organization_info_copy: "Wir verwenden diese Informationen in automatisierten E-Mail-Benachrichtigungen, die Talk versendet. Damit können Nutzer Ihre Organisation identifizieren und sie haben die Möglichkeit bei Problemen in Kontakt mit Ihnen zu treten."
organization_info_copy_2: "Wir empfehlen, einee generische E-Mail-Adresse (z.B. community@yournewsroom.com) für diesen Zweck einzurichten. Die kann über die Zeit gleich bleiben, und gibt nach außen keine Namen preis, die von Nutzern im Fall von Konflikten für persönliche Angriffe missbraucht werden könnten."
organization_details: "Details zur Organisation"
organization_name: "Name der Organisation"
organization_contact_email: "E-Mail-Adresse der Organisation"
title: "Kommentarbereich konfigurieren"
weeks: Wochen
wordlist: "Gesperrte Wörter"
save_changes_dialog:
unsaved_changes: "Ungespeicherte Änderungen"
copy: "Sie haben einen oder mehrere Änderungen vorgenommen, ohne zu speichern. Möchten Sie jetzt speichern oder die Änderungen verwerfen?"
save_settings: "Einstellungen speichern"
discard: "Verwerfen"
cancel: "Abbrechen"
continue: "Fortfahren"
createdisplay:
check_the_form: "Ungültige Eingabe. Bitte prüfen Sie die Felder."
@@ -200,11 +220,17 @@ de:
we_received_a_request: "Wir haben eine Anfrage erhalten, Ihr Passwort zurückzusetzen. Sollten Sie dies nicht angefordert haben, können Sie diese Nachricht ignorieren."
if_you_did: "Falls doch,"
please_click: "klicken Sie bitte hier zum Zurücksetzen"
subject: "Passwort zurücksetzen"
password_change:
subject: "{0} Passwort-Änderung"
body: "Das Passwort Ihres Benutzerkontos wurde geändert.\n\nFalls Sie diese Änderung nicht angefordert haben, kontaktieren Sie uns bitte unter {0}."
embedlink:
copy: "In die Zwischenablage kopieren"
error:
PASSWORD_INCORRECT: "Ihr bestehendes Passwort wurde falsch eingegeben"
COMMENT_PARENT_NOT_VISIBLE: "Der Kommentar, auf den Sie antworten möchten, wurde entfernt oder existiert nicht."
EMAIL_VERIFICATION_TOKEN_INVALID: "Code zur E-Mail-Bestätigung ist ungültig."
EMAIL_ALREADY_VERIFIED: "E-Mail-Adresse ist bereits bestätigt."
PASSWORD_RESET_TOKEN_INVALID: "Ihr Link zum Passwort zurücksetzen ist ungültig."
COMMENT_TOO_SHORT: "Kommentare sollten mehr als ein Zeichen enthalten, bitte überprüfen Sie Ihren Kommentar und probieren Sie es erneut."
NOT_AUTHORIZED: "Sie sind nicht berechtigt, diese Aktion auszuführen."
@@ -223,19 +249,25 @@ de:
LOGIN_MAXIMUM_EXCEEDED: "Sie haben zu häufig erfolglos versucht, sich anzumelden. Bitte warten Sie."
PASSWORD_REQUIRED: "Passwort ist erforderlich"
COMMENTING_CLOSED: "Kommentarbereich ist bereits geschlossen"
COMMENTING_DISABLED: "Die Kommentarfunktion ist derzeit abgeschaltet"
NOT_FOUND: "Ressource nicht gefunden"
ALREADY_EXISTS: "Ressource existiert bereits"
INVALID_ASSET_URL: "Asset-URL ist ungültig"
CANNOT_IGNORE_STAFF: "Mitarbeiter können nicht ignoriert werden."
email: "E-Mail-Adresse ungültig"
INCORRECT_PASSWORD: "Falsches Passwort"
email: "Bitte geben Sie eine gültige E-Mail-Adresse ein."
DELETION_NOT_SCHEDULED: "Löschvorgang wurde nicht geplant"
confirm_password: "Passwörter nicht identisch. Bitte erneut überprüfen"
network_error: "Server-Verbindung fehlgeschlagen. Bitte überprüfen Sie ihre Internetverbindung und versuchen Sie es erneut."
email_not_verified: "E-Mail-Adresse {0} nicht bestätigt."
email_password: "E-Mail und/oder Passwort inkorrekt."
organization_name: "Namen von Organisationen dürfen nur Buchstaben und Zahlen enthalten."
organization_contact_email: "E-Mail-Adresse der Organisation ist ungültig."
password: "Passwort muss mindestens 8 Zeichen enthalten"
username: "Nutzernamen dürfen nur Buchstaben, Zahlen und _ enthalten"
unexpected: "Unerwarteter Fehler aufgetreten. Es tut uns leid!"
required_field: "Dieses Feld ist erforderlich"
temporarily_suspended: "Ihr Konto ist vorübergehend gesperrt. Es wird wieder aktiviert am {0}. Bei Fragen setzen Sie sich mit uns in Kontakt."
flag_comment: "Kommentar melden"
flag_reason: "Grund der Meldung (optional)"
flag_username: "Nutzername melden"
@@ -245,6 +277,7 @@ de:
comment: Kommentar
comment_is_ignored: "Dieser Kommentar ist nicht sichtbar, da Sie den Nutzer ignorieren."
comment_is_rejected: "Sie haben diesen Kommentar abgelehnt."
comment_is_deleted: "Der Kommentar wurde vom Nutzer gelöscht."
comment_is_hidden: "Dieser Kommentar ist nicht verfügbar."
comments: Kommentare
configure_stream: "Konfigurieren"
@@ -289,7 +322,6 @@ de:
suspect_word: "Verdächtiges Wort"
banned_word: "Unzulässiges Wort"
body_count: "Text überschreitet Zeichenlimit"
trust: "Vertrauen"
links: "Link"
modqueue:
account: "Konto-Markierungen"
@@ -333,6 +365,7 @@ de:
sort: "Sortieren"
show_shortcuts: "Tastaturkürzel anzeigen"
singleview: "Zen-Modus"
system_withheld: "System Withheld"
thismenu: "Dieses Menü öffnen"
jump_to_queue: "Zu bestimmter Liste springen"
thousand: T
@@ -355,6 +388,9 @@ de:
report_notif: "Vielen Dank für Ihre Meldung. Unsere Moderatoren wurden informiert und werden sich in Kürze darum kümmern."
report_notif_remove: "Ihre Meldung wurde entfernt."
reported: Gemeldet
comment_history_blank:
title: Sie haben noch keine Kommentare verfasst
info: Hier wird ein Verlauf Ihrer verfassten Kommentare erscheinen
settings:
from_settings_page: "Sie können auf Ihrer Profilseite Ihren Kommentarverlauf einsehen."
my_comment_history: "Mein Kommentarverlauf"
@@ -366,7 +402,7 @@ de:
stream:
all_comments: "Alle Kommentare"
temporarily_suspended: "Entsprechend der Community-Regeln von {0} wurde Ihr Konto vorübergehend gesperrt. Nehmen Sie {1} wieder an der Diskussion teil."
comment_not_found: "Kommentar nicht gefunden"
comment_not_found: "Dieser Kommentar wurde entfernt oder existiert nicht."
no_comments: "Es gibt noch keine Kommentare. Schreiben Sie doch einen..."
no_comments_and_closed: "Es gab zu diesem Artikel keine Kommentare."
step_1_header: "Ein Problem melden"
@@ -393,6 +429,8 @@ de:
one_hour: "1 Stunde"
hours: "{0} Stunden"
days: "{0} Tage"
hour: "{0} hours"
day: "{0} days"
cancel: "Abbrechen"
suspend_user: "Nutzer vorübergehend sperren"
email_message_suspend: "Sehr geehrte/r {0}, entsprechend der Community-Richtlinien von {1} wurde Ihr Konto vorübergehend gesperrt. Während der Sperrung können Sie weder kommentieren noch andere Aktionen ausführen. Nehmen Sie {2} wieder an der Diskussion teil."
@@ -432,15 +470,15 @@ de:
rejected: "Abgelehnte"
user_history: "Konto-Verlauf"
user_history:
user_banned: "User banned"
ban_removed: "Ban removed"
username_status: "Username {0}"
suspended: "Suspended, {0}"
suspension_removed: "Suspension removed"
user_banned: "Nutzer gesperrt"
ban_removed: "Sperrung aufgehoben"
username_status: "Nutzername {0}"
suspended: "Vorübergehend gesperrt, {0}"
suspension_removed: "Vorübergehende Sperrung aufgehoben"
system: "System"
date: "Date"
action: "Action"
taken_by: "Taken By"
date: "Datum"
action: "Aktion"
taken_by: "Durch"
user_impersonating: "Gibt sich für jemand anderen aus"
user_no_comment: "Sie haben noch keinen Kommentar abgegeben. Teilen Sie Ihre Meinung mit uns!"
username_offensive: "Dieser Nutzername ist unangemessen"
@@ -458,7 +496,7 @@ de:
username: "Nutzername"
password: "Passwort"
confirm_password: "Passwort bestätigen"
organization_contact_email: "Organization Contact Email"
organization_contact_email: "Kontakt-Adresse der Organisation"
save: "Speichern"
permitted_domains:
title: "Zugelassene Domains"
+13 -4
View File
@@ -124,6 +124,8 @@ en:
custom_css_url_desc: "URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external."
days: Days
description: "Change the comment settings on this story."
disable_commenting_title: "Deactivate commenting site-wide"
disable_commenting_desc: "Write a message that will be displayed while commenting is deactivated."
domain_list_text: "Enter the domains you would like to permit for Talk e.g. your local staging and production environments (ex. localhost:3000 staging.domain.com domain.com)."
domain_list_title: "Permitted Domains"
edit_info: "Edit Info"
@@ -162,7 +164,7 @@ en:
suspect_word_title: "Suspect words list"
suspect_word_text: "Comments which contain these words or phrases (not case-sensitive) will be highlighted in the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list."
tech_settings: "Tech Settings"
organization_information: "Organization information"
organization_information: "Organization Information"
organization_info_copy: "We use this information in email notifications generated by Talk. This connects the messages to your organization, and provides a way for users to contact you if they have an issue with their account."
organization_info_copy_2: "We recommend creating a generic email account (eg. community@yournewsroom.com) for this purpose. This means it can remain consistent over time, and doesn't expose a name that users could target if their account were blocked."
organization_details: "Organization Details"
@@ -225,6 +227,7 @@ en:
embedlink:
copy: "Copy to Clipboard"
error:
AUTHENTICATION: "An error occurred trying to authenticate your account."
PASSWORD_INCORRECT: "Your current password was entered incorrectly"
COMMENT_PARENT_NOT_VISIBLE: "The comment that you're replying to has been removed or doesn't exist."
EMAIL_VERIFICATION_TOKEN_INVALID: "Email verification token is invalid."
@@ -247,6 +250,7 @@ en:
LOGIN_MAXIMUM_EXCEEDED: "You have made too many unsuccessful password attempts. Please wait."
PASSWORD_REQUIRED: "Must input a password"
COMMENTING_CLOSED: "Commenting is already closed"
COMMENTING_DISABLED: "Commenting is currently disabled on this site"
NOT_FOUND: "Resource not found"
ALREADY_EXISTS: "Resource already exists"
INVALID_ASSET_URL: "Assert URL is invalid"
@@ -274,7 +278,7 @@ en:
comment: comment
comment_is_ignored: "This comment is hidden because you ignored this user."
comment_is_rejected: "You have rejected this comment."
comment_is_deleted: "This comment was deleted."
comment_is_deleted: "This commenter has deleted their account."
comment_is_hidden: "This comment is not available."
comments: comments
configure_stream: "Configure"
@@ -319,7 +323,7 @@ en:
suspect_word: "Suspect Word"
banned_word: "Banned Word"
body_count: "Body exceeds max length"
trust: "Trust"
trust: "Karma"
links: "Link"
modqueue:
account: "account flags"
@@ -463,10 +467,15 @@ en:
email: "Email"
total_comments: "Total Comments"
reject_rate: "Reject Rate"
reports: "Reports"
all: "All"
rejected: "Rejected"
user_history: "User History"
unreliable: "Unreliable"
karma: "Karma"
learn_more: "Learn More"
user_karma_score: "User Karma Score"
karma_docs_link: "https://docs.coralproject.net/talk/trust/#user-karma-score"
id: "ID"
user_history:
user_banned: "User banned"
ban_removed: "Ban removed"
-1
View File
@@ -310,7 +310,6 @@ es:
suspect_word: "Palabra sospechosa"
banned_word: "Palabra prohibida"
body_count: "El texto exede el límite permitido"
trust: "Trust"
links: "Link"
modqueue:
account: "reportes de cuentas"
+2 -3
View File
@@ -292,7 +292,6 @@ fi_FI:
suspect_word: "Epäilyttävä sana"
banned_word: "Kielletty sana"
body_count: "Liian pitkä viesti"
trust: "Luotettava"
links: "Linkki"
modqueue:
account: "Liputuksia"
@@ -413,7 +412,7 @@ fi_FI:
title_reject: "Huomasimme sinun hylänneen käyttäjänimen"
suspend_user: "Aseta väliaikainen käyttökielto"
yes_suspend: "Kyllä, sulje väliaikaisesti"
email_message_reject: "Toinen yhteisön jäsen on ilmiantanut käyttäjänimesi ja sen perusteella nimi on hylätty. Et voi enää osallistua keskusteluun. Ole ystävällisesti yhteydessä meihin, jos sinulla on asiasta kysyttävää."
email_message_reject: "Toinen yhteisön jäsen on ilmiantanut käyttäjänimesi ja sen perusteella nimi on hylätty. Et voi enää osallistua keskusteluun. Ole ystävällisesti yhteydessä meihin, jos sinulla on asiasta kysyttävää."
write_message: "Kirjoita viesti"
send: Lähetä
thank_you: "Arvostamme palautettasi. Moderaattorimme käy läpi tekemäsi ilmiannon."
@@ -462,4 +461,4 @@ fi_FI:
close: "Sulje asennusnäkymä"
admin_sidebar:
view_options: "Näytä asetukset"
sort_comments: "Järjestä kommentit"
sort_comments: "Järjestä kommentit"
-1
View File
@@ -300,7 +300,6 @@ fr:
suspect_word: "Mot suspect"
banned_word: "Mot banni"
body_count: "Le texte dépasse la longueur maximale"
trust: "Trust"
links: "Lien"
modqueue:
account: "Signalements du compte"
-1
View File
@@ -290,7 +290,6 @@ nl_NL:
suspect_word: "Verdacht woord"
banned_word: "Geblokeerd woord"
body_count: "Tekst is te lang"
trust: "Vertrouwen"
links: "Link"
modqueue:
account: "account meldingen"
+38 -84
View File
@@ -12,26 +12,26 @@ pt_BR:
note_reject_comment: "Banir esse usuário também colocará esse comentário na fila rejeitada."
note_ban_user: "Banir este usuário não os permitirá editar comentários ou remover qualquer coisa."
yes_ban_user: "Sim, banir o usuário"
write_a_message: "Write a message"
send: "Send"
notify_ban_headline: "Notify the user of ban"
notify_ban_description: "This will notify the user by email that they have been banned from the community"
email_message_ban: "Dear {0},\n\nSomeone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, like or report comments. if you think this has been done in error, please contact our community team."
write_a_message: "Escrever uma mensagem"
send: "Enviar"
notify_ban_headline: "Notificar o usuário do banimento"
notify_ban_description: "Isso notificará o usuário por email que ele foi banido da comunidade."
email_message_ban: "Caro {0},\n\nAlguém com acesso a sua conta violou nossas diretrizes da comunidade. Em consequência disso, sua conta foi banida. Você não poderá mais comentar, curtir ou denunciar comentários. Se você acha que isso foi um engano, entre em contato com nossa equipe."
bio_offensive: "Esse perfil é ofensiva"
cancel: "Cancelar"
confirm_email:
click_to_confirm: "Click below to confirm your email address"
confirm: "Confirm"
click_to_confirm: "Clique abaixo para confirmar seu endereço de email"
confirm: "Confirmar"
password_reset:
set_new_password: "Change Your Password"
new_password: "New Password"
new_password_help: "Password must be at least 8 characters"
confirm_new_password: "Confirm New Password"
change_password: "Change Password"
set_new_password: "Alterar sua senha"
new_password: "Nova senha"
new_password_help: "A senha deve ter mais de 8 caracteres"
confirm_new_password: "Confirmar nova senha"
change_password: "Alterar senha"
characters_remaining: "caracteres restantes"
comment:
anon: "Anônimo"
undo_reject: "Undo"
undo_reject: "Desfazer"
ban_user: "Banir o usuário"
comment: "Escreva um comentário..."
edited: Editado
@@ -61,11 +61,6 @@ pt_BR:
reaction: 'Reação'
reactions: 'Reações'
story: 'Conversas'
flagged_usernames:
notify_approved: '{0} approved username {1}'
notify_rejected: '{0} rejected username {1}'
notify_flagged: '{0} reported username {1}'
notify_changed: 'user {0} changed their username to {1}'
community:
account_creation_date: "Data de criação da conta"
active: Ativo
@@ -95,7 +90,7 @@ pt_BR:
status: Situação
username_and_email: "Nome de usuário e email"
yes_ban_user: "Sim, banir este usuário"
commenter: "Commenter"
commenter: "Comentador"
configure:
apply: Aplicar
banned_word_text: "Os comentários que contenham essas palavras ou frases (não sensíveis a maiúsculas e minúsculas) serão automaticamente removidos da lista de comentários. Digite uma palavra e pressione Enter ou Tab para adicionar ou cole uma lista separada por vírgulas."
@@ -186,10 +181,10 @@ pt_BR:
minutes_plural: "minutos"
email:
suspended:
subject: "Your account has been suspended"
subject: "Sua conta foi suspensa"
banned:
subject: "Your account has been banned"
body: "In accordance with The Coral Projects community guidelines, your account has been banned. You are now longer allowed to comment, flag or engage with our community."
subject: "Sua conta foi banida"
body: "De acordo com as diretrizes da comunidade do Coral Project, sua conta foi banida. Você não tem mais permissão para comentar, sinalizar ou interagir com nossa comunidade."
confirm:
has_been_requested: "Uma confirmação de e-mail foi solicitada para a seguinte conta:"
to_confirm: "Para confirmar a conta, visite este link: "
@@ -203,18 +198,18 @@ pt_BR:
embedlink:
copy: "Copiar para área de transferência"
error:
COMMENT_PARENT_NOT_VISIBLE: "The comment that you're replying to has been removed or doesn't exist."
EMAIL_VERIFICATION_TOKEN_INVALID: "Email verification token is invalid."
PASSWORD_RESET_TOKEN_INVALID: "Your password reset link is invalid."
COMMENT_PARENT_NOT_VISIBLE: "O comentário que você está respondendo foi removido ou não existe."
EMAIL_VERIFICATION_TOKEN_INVALID: "O token de verificação do email é inválido."
PASSWORD_RESET_TOKEN_INVALID: "Seu link de redefinição de senha é inválido."
COMMENT_TOO_SHORT: "Seu comentário precisar ter mais de um caracter. Revise seu comentário e envie novamente"
NOT_AUTHORIZED: "Você não está autorizado a executar esta ação."
NO_SPECIAL_CHARACTERS: "Nomes de usuários podem conter números de letras e _ somente"
PASSWORD_LENGTH: "A senha é muito curta"
PROFANITY_ERROR: "Os nomes de usuários não devem conter palavrões. Entre em contato com o administrador se você acredita que isso seja incorreto."
RATE_LIMIT_EXCEEDED: "Rate limit exceeded"
RATE_LIMIT_EXCEEDED: "Tentativas excedidas"
USERNAME_IN_USE: "Nome de usuário já em uso"
USERNAME_REQUIRED: "Must input a username"
EMAIL_NOT_VERIFIED: "E-mail address not verified"
USERNAME_REQUIRED: "O nome do usuário é obrigatório"
EMAIL_NOT_VERIFIED: "E-mail não verificado"
EDIT_WINDOW_ENDED: "Você não pode mais editar esse comentário. O tempo expirou."
EDIT_USERNAME_NOT_AUTHORIZED: "Você não tem permissão para revisar seu nome de usuário."
SAME_USERNAME_PROVIDED: "Você deve enviar um nome de usuário diferente."
@@ -226,7 +221,7 @@ pt_BR:
NOT_FOUND: "Recurso não encontrado"
ALREADY_EXISTS: "O recurso já existe"
INVALID_ASSET_URL: "O URL do recurso é inválido"
CANNOT_IGNORE_STAFF: "Cannot ignore Staff members."
CANNOT_IGNORE_STAFF: "Não é possível ignorar membros Staff."
email: "Não é um e-mail válido"
confirm_password: "Suas senhas não coincidem. Por favor, tente novamente."
network_error: "Falha ao conectar-se ao servidor. Verifique a sua conexão com a internet e tente novamente."
@@ -244,8 +239,8 @@ pt_BR:
banned_account_body: "Isso significa que você não pode curtir, informar ou escrever comentários."
comment: comentário
comment_is_ignored: "Este comentário está oculto porque você ignorou esse usuário."
comment_is_rejected: "You have rejected this comment."
comment_is_hidden: "This comment is not available."
comment_is_rejected: "Você rejeitou este comentário."
comment_is_hidden: "Este comentário não está disponível."
comments: comentários
configure_stream: "Configurar"
content_not_available: "Este conteúdo não está disponível"
@@ -255,7 +250,7 @@ pt_BR:
label: "Novo usuário"
msg: "Sua conta está suspensa porque seu nome de usuário foi considerado inapropriado. Para restaurar sua conta, insira um novo nome de usuário. Entre em contato conosco se você tiver alguma dúvida."
changed_name:
msg: "Your username change is under review by our moderation team."
msg: "Sua alteração de nome de usuário está sendo analisada por nossa equipe de moderação."
my_comments: "Meus comentários"
my_profile: "Meu perfil"
new_count: "Ver {0} {1}"
@@ -273,24 +268,6 @@ pt_BR:
loading_results: "Carregando resultados"
marketing: "Isso parece um anúncio/marketing"
moderate_this_stream: "Moderar comentários"
flags:
reasons:
user:
username_offensive: "Offensive"
username_nolike: "Dislike"
username_impersonating: "Impersonation"
username_spam: "Spam"
username_other: "Other"
comment:
comment_offensive: "Offensive"
comment_spam: "Spam"
comment_noagree: "Disagree"
comment_other: "Other"
suspect_word: "Suspect Word"
banned_word: "Banned Word"
body_count: "Body exceeds max length"
trust: "Trust"
links: "Link"
modqueue:
account: "contas marcadas"
actions: Ações
@@ -302,7 +279,7 @@ pt_BR:
notify_flagged: '{0} marcou o comentário "{1}"'
approve: "Aprovar"
approved: "Aprovado"
ban_user: "Prohibi-lo"
ban_user: "Banir"
billion: B
close: Fechar
dont_like_username: "Eu não gosto desse nome de usuário"
@@ -319,22 +296,22 @@ pt_BR:
newest_first: "Mais novos primeiro"
navigation: Navegação
next_comment: "Vá para o próximo comentário"
toggle_search: "Open search"
next_queue: "Switch queues"
toggle_search: "Abrir busca"
next_queue: "Trocar filas"
oldest_first: "Mais velhos primeiro"
premod: pré-moderação
prev_comment: "Vá para o comentário anterior"
reject: "Rejeitar"
rejected: "Rejeitado"
reply: "Reply"
reply: "Responder"
select_stream: "Selecione a lista"
shift_key: "⇧"
shortcuts: "Atalhos"
sort: "Sort"
sort: "Ordenar"
show_shortcuts: "Ver atalhos"
singleview: "Modo zen"
thismenu: "Abra este menu"
jump_to_queue: "Jump to specific queue"
jump_to_queue: "Ir para fila específica"
thousand: k
try_these: "Tente este"
view_more_shortcuts: "Ver mais atalhos"
@@ -343,7 +320,7 @@ pt_BR:
no_agree_comment: "Eu não concordo com este comentário"
no_like_bio: "Eu não gosto dessa descrição de perfil"
no_like_username: "Eu não gosto deste nome de usuário"
already_flagged_username: "You have already flagged this username."
already_flagged_username: "Você já sinalizou este usuário."
other: Outro
permalink: Compartilhar
personal_info: "Este comentário revela informações de identificação pessoal"
@@ -367,8 +344,8 @@ pt_BR:
all_comments: "Todos os comentários"
temporarily_suspended: "De acordo com as diretrizes da comunidade de {0}, sua conta foi temporariamente suspensa. Por favor, volte para a conversa {1}."
comment_not_found: "Este comentário foi removido ou não existe."
no_comments: "There are no comments yet, why dont you write one?"
no_comments_and_closed: "There were no comments on this article."
no_comments: "Ainda não comentários, por que não escrever um?"
no_comments_and_closed: "Não houve comentários sobre este artigo."
step_1_header: "Relatar um problema"
step_2_header: "Ajude-nos a entender"
step_3_header: "Obrigdo por sua contribuição"
@@ -418,29 +395,6 @@ pt_BR:
bio_flags: "Marcadas para este perfil"
user_bio: "Perfil do usuário"
username_flags: "Marcadas para este usuário"
user_detail:
remove_suspension: "Remove Suspension"
suspend: "Suspend User"
remove_ban: "Remove Ban"
ban: "Ban User"
member_since: "Member Since"
email: "Email"
total_comments: "Total Comments"
reject_rate: "Reject Rate"
reports: "Reports"
all: "All"
rejected: "Rejected"
user_history: "User History"
user_history:
user_banned: "User banned"
ban_removed: "Ban removed"
username_status: "Username {0}"
suspended: "Suspended, {0}"
suspension_removed: "Suspension removed"
system: "System"
date: "Date"
action: "Action"
taken_by: "Taken By"
user_impersonating: "Este usuário está representando"
user_no_comment: "Você nunca deixou um comentário. Participe da conversa!"
username_offensive: "Esse nome de usuário é ofensivo"
@@ -469,5 +423,5 @@ pt_BR:
launch: "Iniciar Talk"
close: "Feche este instalador"
admin_sidebar:
view_options: "View Options"
sort_comments: "Sort Comments"
view_options: "Opções de visualização"
sort_comments: "Ordenar comentários"
+1 -67
View File
@@ -12,22 +12,11 @@ zh_CN:
note_reject_comment: "封禁该用户将使这条评论被移入“被拒”队列。"
note_ban_user: "封禁该用户将使其无法编辑或删除评论。"
yes_ban_user: "是的,封禁该用户"
write_a_message: "Write a message"
send: "Send"
notify_ban_headline: "Notify the user of ban"
notify_ban_description: "This will notify the user by email that they have been banned from the community"
email_message_ban: "Dear {0},\n\nSomeone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, like or report comments. if you think this has been done in error, please contact our community team."
bio_offensive: "该简介含有冒犯言语"
cancel: "取消"
confirm_email:
click_to_confirm: "Click below to confirm your email address"
confirm: "Confirm"
password_reset:
set_new_password: "Change Your Password"
new_password: "New Password"
new_password_help: "Password must be at least 8 characters"
confirm_new_password: "Confirm New Password"
change_password: "Change Password"
characters_remaining: "字符剩余可用"
comment:
anon: "匿名"
@@ -61,11 +50,6 @@ zh_CN:
reaction: '回应'
reactions: '回应'
story: '文章'
flagged_usernames:
notify_approved: '{0} approved username {1}'
notify_rejected: '{0} rejected username {1}'
notify_flagged: '{0} reported username {1}'
notify_changed: 'user {0} changed their username to {1}'
community:
account_creation_date: "账户创建日期"
active: "活动中"
@@ -203,9 +187,6 @@ zh_CN:
embedlink:
copy: "复制到粘贴板"
error:
COMMENT_PARENT_NOT_VISIBLE: "The comment that you're replying to has been removed or doesn't exist."
EMAIL_VERIFICATION_TOKEN_INVALID: "Email verification token is invalid."
PASSWORD_RESET_TOKEN_INVALID: "Your password reset link is invalid."
COMMENT_TOO_SHORT: "评论至少应有一个字符。请修改您的评论,再度尝试。"
NOT_AUTHORIZED: "您没有权限进行该操作"
NO_SPECIAL_CHARACTERS: "用户名只能包含字母、数字跟下划线"
@@ -237,7 +218,6 @@ zh_CN:
username: "用户名只能包含字母、数字跟下划线"
unexpected: "发生了异常错误。对不起!"
required_field: "该字段必填"
temporarily_suspended: "Your account is currently suspended. It will be reactivated {0}. Please contact us if you have any questions."
flag_comment: "举报评论"
flag_reason: "举报理由(可选)"
flag_username: "举报用户名"
@@ -256,8 +236,6 @@ zh_CN:
error: "用户名只能包含字母、数字跟下划线"
label: "新用户名"
msg: "由于您的用户名不当,您的帐号目前被暂停使用。如要恢复您的帐户,请输入一个新的用户名。如有任何疑问,请与我们联系。"
changed_name:
msg: "Your username change is under review by our moderation team."
my_comments: "我的评论"
my_profile: "我的资料"
new_count: "查看 {0} 更多 {1}"
@@ -275,24 +253,6 @@ zh_CN:
loading_results: "加载结果中"
marketing: "这看起来像是广告"
moderate_this_stream: "审查该流"
flags:
reasons:
user:
username_offensive: "Offensive"
username_nolike: "Dislike"
username_impersonating: "Impersonation"
username_spam: "Spam"
username_other: "Other"
comment:
comment_offensive: "Offensive"
comment_spam: "Spam"
comment_noagree: "Disagree"
comment_other: "Other"
suspect_word: "Suspect Word"
banned_word: "Banned Word"
body_count: "Body exceeds max length"
trust: "Trust"
links: "Link"
modqueue:
account: "帐户标记"
actions: "操作"
@@ -420,29 +380,6 @@ zh_CN:
bio_flags: "对简介的举报"
user_bio: "用户简介"
username_flags: "对用户名的举报"
user_detail:
remove_suspension: "Remove Suspension"
suspend: "Suspend User"
remove_ban: "Remove Ban"
ban: "Ban User"
member_since: "Member Since"
email: "Email"
total_comments: "Total Comments"
reject_rate: "Reject Rate"
reports: "Reports"
all: "All"
rejected: "Rejected"
user_history: "User History"
user_history:
user_banned: "User banned"
ban_removed: "Ban removed"
username_status: "Username {0}"
suspended: "Suspended, {0}"
suspension_removed: "Suspension removed"
system: "System"
date: "Date"
action: "Action"
taken_by: "Taken By"
user_impersonating: "冒名用户"
user_no_comment: "您未曾发表评论。现在就来加入对话吧!"
username_offensive: "用户名有冒犯性"
@@ -468,7 +405,4 @@ zh_CN:
final:
description: "感谢您安装 Talk!我们已向您的邮箱发送一封验证邮件。当您进行帐号设置时,您可以开始跟您的读者开始互动。"
launch: "启动 Talk"
close: "关闭安装程序"
admin_sidebar:
view_options: "View Options"
sort_comments: "Sort Comments"
close: "关闭安装程序"
-70
View File
@@ -12,22 +12,8 @@ zh_TW:
note_reject_comment: "封禁該用戶將使這條評論被移入“被拒”列表。"
note_ban_user: "封禁該用戶將使其無法編輯或刪除評論。"
yes_ban_user: "是的,封禁該用戶"
write_a_message: "Write a message"
send: "Send"
notify_ban_headline: "Notify the user of ban"
notify_ban_description: "This will notify the user by email that they have been banned from the community"
email_message_ban: "Dear {0},\n\nSomeone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, like or report comments. if you think this has been done in error, please contact our community team."
bio_offensive: "該介紹包含具有攻擊性的內容。"
cancel: "取消"
confirm_email:
click_to_confirm: "Click below to confirm your email address"
confirm: "Confirm"
password_reset:
set_new_password: "Change Your Password"
new_password: "New Password"
new_password_help: "Password must be at least 8 characters"
confirm_new_password: "Confirm New Password"
change_password: "Change Password"
characters_remaining: "剩餘字符數"
comment:
anon: "匿名用戶"
@@ -61,11 +47,6 @@ zh_TW:
reaction: '回應'
reactions: '回應'
story: '故事'
flagged_usernames:
notify_approved: '{0} approved username {1}'
notify_rejected: '{0} rejected username {1}'
notify_flagged: '{0} reported username {1}'
notify_changed: 'user {0} changed their username to {1}'
community:
account_creation_date: "賬戶創建日期"
active: 激活
@@ -203,9 +184,6 @@ zh_TW:
embedlink:
copy: "覆制到剪貼板"
error:
COMMENT_PARENT_NOT_VISIBLE: "The comment that you're replying to has been removed or doesn't exist."
EMAIL_VERIFICATION_TOKEN_INVALID: "Email verification token is invalid."
PASSWORD_RESET_TOKEN_INVALID: "Your password reset link is invalid."
COMMENT_TOO_SHORT: "評論長度必須超過一個字符,請您修改評論後重試。"
NOT_AUTHORIZED: "您無權執行該操作。"
NO_SPECIAL_CHARACTERS: "用戶名只能包含字母、數字和下劃線"
@@ -237,7 +215,6 @@ zh_TW:
username: "用戶名只能包含字母、數字和下劃線。"
unexpected: "發生了意外錯誤。抱歉!"
required_field: "該字段必填"
temporarily_suspended: "Your account is currently suspended. It will be reactivated {0}. Please contact us if you have any questions."
flag_comment: "舉報評論"
flag_reason: "舉報原因(可選)"
flag_username: "舉報用戶名"
@@ -256,8 +233,6 @@ zh_TW:
error: "用戶名只能包含字母、數字和下劃線。"
label: "新用戶名"
msg: "由於您的用戶名不當,您的帳號目前已被暫停使用。如要恢復您的帳戶,請輸入一個新的用戶名。如有任何疑問,請與我們聯繫。"
changed_name:
msg: "Your username change is under review by our moderation team."
my_comments: "我的評論"
my_profile: "我的概況"
new_count: "查看{0}更多{1}"
@@ -275,24 +250,6 @@ zh_TW:
loading_results: "加載結果"
marketing: "這看起來像是廣告/營銷"
moderate_this_stream: "審核這個流"
flags:
reasons:
user:
username_offensive: "Offensive"
username_nolike: "Dislike"
username_impersonating: "Impersonation"
username_spam: "Spam"
username_other: "Other"
comment:
comment_offensive: "Offensive"
comment_spam: "Spam"
comment_noagree: "Disagree"
comment_other: "Other"
suspect_word: "Suspect Word"
banned_word: "Banned Word"
body_count: "Body exceeds max length"
trust: "Trust"
links: "Link"
modqueue:
account: "帳戶標記"
actions: 操作
@@ -345,7 +302,6 @@ zh_TW:
no_agree_comment: "我不同意這個評論"
no_like_bio: "我不喜歡這個個人簡介"
no_like_username: "我不喜歡這個用戶名"
already_flagged_username: "You have already flagged this username."
other: 其他
permalink: 分享
personal_info: "該評論洩露了個人身份資訊"
@@ -420,29 +376,6 @@ zh_TW:
bio_flags: "該簡介的標記"
user_bio: "用戶簡介"
username_flags: "該用戶名的標記"
user_detail:
remove_suspension: "Remove Suspension"
suspend: "Suspend User"
remove_ban: "Remove Ban"
ban: "Ban User"
member_since: "Member Since"
email: "Email"
total_comments: "Total Comments"
reject_rate: "Reject Rate"
reports: "Reports"
all: "All"
rejected: "Rejected"
user_history: "User History"
user_history:
user_banned: "User banned"
ban_removed: "Ban removed"
username_status: "Username {0}"
suspended: "Suspended, {0}"
suspension_removed: "Suspension removed"
system: "System"
date: "Date"
action: "Action"
taken_by: "Taken By"
user_impersonating: "此用戶正在冒充"
user_no_comment: "您尚未評論過。加入對話吧!"
username_offensive: "這個用戶名有冒犯性"
@@ -469,6 +402,3 @@ zh_TW:
description: "感謝安裝Talk!我們給您發送了一封郵件以驗證您的電子郵箱地址。在完成帳戶設置後,您即可開始與您的讀者互動。"
launch: "啟動Talk"
close: "關閉安裝程序"
admin_sidebar:
view_options: "View Options"
sort_comments: "Sort Comments"
+55
View File
@@ -0,0 +1,55 @@
const helmet = require('helmet');
const { WEBSOCKET_LIVE_URI, ENABLE_STRICT_CSP } = require('../config');
const { BASE_PATH, BASE_URL, STATIC_URL } = require('../url');
const { URL } = require('url');
// websocketUri represents the host where we can connect for websocket requests.
const websocketUri = new URL(WEBSOCKET_LIVE_URI || BASE_URL);
websocketUri.protocol = websocketUri.protocol.startsWith('https')
? 'wss'
: 'ws';
const { origin: websocketSrc } = websocketUri;
// staticSrc represents any static asset hosted on the static host.
const { host: staticSrc } = new URL(STATIC_URL);
// nonceSrc represents the nonce source that is used to indicate a safe resource
// to load.
const nonceSrc = (req, res) => `'nonce-${res.locals.nonce}'`;
module.exports = helmet.contentSecurityPolicy({
directives: {
reportUri: `${BASE_PATH}api/v1/csp`, // report all policy violations to our reporting uri
defaultSrc: ["'none'"], // by default, do not allow anything at all
scriptSrc: [
"'self'",
'https://ajax.googleapis.com', // for jquery
staticSrc, // for any static files loaded from a cdn
nonceSrc,
],
styleSrc: [
"'self'",
'https://maxcdn.bootstrapcdn.com', // for bootstrap css
'https://fonts.googleapis.com', // for google fonts
'https://code.getmdl.io', // for mdl css
staticSrc, // for any static files loaded from a cdn
nonceSrc,
],
connectSrc: ["'self'", websocketSrc],
fontSrc: [
"'self'",
'https://maxcdn.bootstrapcdn.com', // for font-awesome
'https://fonts.gstatic.com', // for google fonts
staticSrc, // for any static files loaded from a cdn
nonceSrc,
],
imgSrc: [
"'self'",
staticSrc, // for any static files loaded from a cdn
nonceSrc,
],
},
browserSniff: false,
// Allow the configuration to disable strict enforcement of CSP.
reportOnly: !ENABLE_STRICT_CSP,
});
+16
View File
@@ -0,0 +1,16 @@
const { get, merge } = require('lodash');
const uuid = require('uuid/v4');
// nonce is designed to create a random value that can be used in conjunction
// with the csp middleware.
module.exports = (req, res, next) => {
const nonce = uuid();
// Attach the nonce to the locals.
res.locals.nonce = nonce;
res.locals.data = merge({}, get(res.locals, 'data', {}), {
SCRIPT_NONCE: nonce,
});
next();
};
+7 -3
View File
@@ -48,7 +48,7 @@ const attachStaticLocals = locals => {
for (const key in TEMPLATE_LOCALS) {
const value = TEMPLATE_LOCALS[key];
locals[key] = value;
merge(locals, { [key]: value });
}
};
@@ -94,9 +94,13 @@ const createResolveFactory = (() => {
module.exports = async (req, res, next) => {
try {
// Attach the custom css url.
const { customCssUrl } = await SettingsService.select('customCssUrl');
// Attach the custom css url and organization name.
const { customCssUrl, organizationName } = await SettingsService.select(
'customCssUrl',
'organizationName'
);
res.locals.customCssUrl = customCssUrl;
res.locals.organizationName = organizationName;
} catch (err) {
console.warn(err);
}
+6 -13
View File
@@ -9,7 +9,8 @@ const Action = new Schema(
id: {
type: String,
default: uuid.v4,
unique: true,
unique: 1,
index: 1,
},
action_type: {
type: String,
@@ -19,7 +20,10 @@ const Action = new Schema(
type: String,
enum: ITEM_TYPES,
},
item_id: String,
item_id: {
type: String,
index: 1,
},
user_id: String,
// The element that summaries will additionally group on in addtion to their action_type, item_type, and
@@ -37,15 +41,4 @@ const Action = new Schema(
}
);
// Create an index on the `item_id` field so that queries looking for
// actions based on the item id can resolve faster.
Action.index(
{
item_id: 1,
},
{
background: true,
}
);
module.exports = Action;
+55 -104
View File
@@ -55,12 +55,16 @@ const Comment = new Schema(
type: String,
default: uuid.v4,
unique: true,
index: true,
},
body: {
type: String,
},
body_history: [BodyHistoryItemSchema],
asset_id: String,
asset_id: {
type: String,
index: true,
},
author_id: String,
status_history: [Status],
status: {
@@ -90,7 +94,6 @@ const Comment = new Schema(
// deleted_at stores the date that the given comment was deleted.
deleted_at: {
type: Date,
default: null,
},
// Additional metadata stored on the field.
@@ -110,95 +113,67 @@ const Comment = new Schema(
}
);
// Add the indexes for the id of the comment.
Comment.index(
{
id: 1,
},
{
unique: true,
background: false,
}
);
Comment.index(
{
status: 1,
created_at: 1,
},
{
background: true,
}
);
Comment.index(
{
status: 1,
created_at: 1,
asset_id: 1,
},
{
background: true,
}
);
// Create a sparse index to search across.
Comment.index(
{
created_at: 1,
'action_counts.flag': 1,
status: 1,
},
{
background: true,
sparse: true,
}
);
// Create a sparse index to search across.
Comment.index(
{
'action_counts.flag': 1,
status: 1,
},
{
background: true,
sparse: true,
}
);
// Add an index that is optimized for finding flagged comments.
Comment.index(
{
asset_id: 1,
created_at: 1,
'action_counts.flag': 1,
},
{
background: true,
}
);
// Add an index for the reply sort.
Comment.index(
{
asset_id: 1,
deleted_at: 1,
created_at: -1,
reply_count: -1,
},
{
background: true,
}
{ partialFilterExpression: { deleted_at: null } }
);
// Add an index that is optimized for finding a user's comments.
Comment.index(
{
author_id: 1,
deleted_at: 1,
status: 1,
created_at: -1,
},
{ partialFilterExpression: { deleted_at: null } }
);
Comment.index({
asset_id: 1,
created_at: -1,
});
Comment.index({
asset_id: 1,
created_at: 1,
});
Comment.index({
author_id: 1,
created_at: -1,
});
Comment.index({
asset_id: 1,
status: 1,
});
Comment.index({
asset_id: 1,
parent_id: 1,
reply_count: -1,
created_at: -1,
});
Comment.index({
asset_id: 1,
reply_count: -1,
created_at: -1,
});
Comment.index(
{
'action_counts.flag': 1,
status: 1,
created_at: -1,
},
{
background: true,
partialFilterExpression: {
'action_counts.flag': { $exists: true, $gt: 0 },
deleted_at: null,
},
}
);
@@ -210,34 +185,10 @@ Comment.index(
status: 1,
},
{
background: true,
}
);
// Optimize for tag searches/counts.
Comment.index(
{
'tags.tag.name': 1,
status: 1,
},
{
background: true,
sparse: true,
}
);
// Add an index that is optimized for sorting based on the created_at timestamp
// but also good at locating comments that have a specific asset id.
Comment.index(
{
asset_id: 1,
created_at: 1,
},
{
background: true,
}
);
Comment.virtual('edited').get(function() {
return this.body_history.length > 1;
});
+10
View File
@@ -12,6 +12,8 @@ const Setting = new Schema(
id: {
type: String,
default: '1',
unique: 1,
index: true,
},
moderation: {
type: String,
@@ -66,6 +68,14 @@ const Setting = new Schema(
type: String,
default: 'Expired',
},
disableCommenting: {
type: Boolean,
default: false,
},
disableCommentingMessage: {
type: String,
default: '',
},
wordlist: {
banned: {
type: Array,
+29 -28
View File
@@ -58,6 +58,7 @@ const User = new Schema(
default: uuid.v4,
unique: true,
required: true,
index: true,
},
// This is sourced from the social provider or set manually during user setup
@@ -107,6 +108,7 @@ const User = new Schema(
status: {
type: String,
enum: USER_STATUS_USERNAME,
index: true,
},
// History stores the history of username status changes.
@@ -135,6 +137,7 @@ const User = new Schema(
type: Boolean,
required: true,
default: false,
index: true,
},
history: [
{
@@ -226,41 +229,26 @@ User.index(
}
);
User.index(
{
lowercaseUsername: 1,
'profiles.id': 1,
created_at: -1,
},
{
background: true,
}
);
User.index({
lowercaseUsername: 1,
'profiles.id': 1,
created_at: -1,
});
// This query is executed often, to count the number of flagged accounts with
// usernames.
User.index(
{
'action_counts.flag': 1,
'status.username.status': 1,
},
{
background: true,
}
);
User.index({
'action_counts.flag': 1,
'status.username.status': 1,
});
// Sorting users by created at is the default people search.
User.index(
{
created_at: -1,
},
{
background: true,
}
);
User.index({
created_at: -1,
});
/**
* returns true if a commenter is staff
* returns true if a commenter is staff.
*/
User.method('isStaff', function() {
return this.role !== 'COMMENTER';
@@ -330,6 +318,9 @@ User.virtual('hasVerifiedEmail').get(function() {
});
});
/**
* system returns true when the user is a system user.
*/
User.virtual('system')
.get(function() {
return this._system;
@@ -348,6 +339,11 @@ User.virtual('banned')
})
.set(function(status) {
this.status.banned.status = status;
if (!this.status.banned.history) {
this.status.banned.history = [];
}
this.status.banned.history.push({
status,
created_at: new Date(),
@@ -366,6 +362,11 @@ User.virtual('suspended')
})
.set(function(until) {
this.status.suspension.until = until;
if (!this.status.suspension.history) {
this.status.suspension.history = [];
}
this.status.suspension.history.push({
until,
created_at: new Date(),
+3 -3
View File
@@ -10,15 +10,15 @@ module.exports = {
selenium: {
start_process: true,
server_path:
'node_modules/selenium-standalone/.selenium/selenium-server/3.7.1-server.jar',
'node_modules/selenium-standalone/.selenium/selenium-server/3.8.1-server.jar',
log_path: REPORTS_FOLDER,
host: '127.0.0.1',
port: 6666,
cli_args: {
'webdriver.chrome.driver':
'node_modules/selenium-standalone/.selenium/chromedriver/2.33-x64-chromedriver',
'node_modules/selenium-standalone/.selenium/chromedriver/2.37-x64-chromedriver',
'webdriver.gecko.driver':
'node_modules/selenium-standalone/.selenium/geckodriver/0.19.1-x64-geckodriver',
'node_modules/selenium-standalone/.selenium/geckodriver/0.20.1-x64-geckodriver',
},
},
test_settings: {
-27
View File
@@ -1,27 +0,0 @@
{
"name": "talk",
"version": "4.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"exenv": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
"integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
},
"react-side-effect": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-1.1.5.tgz",
"integrity": "sha512-Z2ZJE4p/jIfvUpiUMRydEVpQRf2f8GMHczT6qLcARmX7QRb28JDBTpnM2g/i5y/p7ZDEXYGHWg0RbhikE+hJRw==",
"requires": {
"exenv": "1.2.2",
"shallowequal": "1.0.2"
}
},
"shallowequal": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.0.2.tgz",
"integrity": "sha512-zlVXeVUKvo+HEv1e2KQF/csyeMKx2oHvatQ9l6XjCUj3agvC8XGf6R9HvIPDSmp8FNPvx7b5kaEJTRi7CqxtEw=="
}
}
}
+8 -5
View File
@@ -1,6 +1,6 @@
{
"name": "talk",
"version": "4.4.0",
"version": "4.4.2",
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
"main": "app.js",
"private": true,
@@ -58,7 +58,6 @@
"dependencies": {
"@coralproject/gql-merge": "^0.1.0",
"@coralproject/graphql-anywhere-optimized": "^0.1.0",
"accepts": "^1.3.4",
"apollo-client": "^1.9.1",
"apollo-engine": "^0.8.1",
"apollo-server-express": "^1.2.0",
@@ -84,13 +83,14 @@
"brotli-webpack-plugin": "^0.5.0",
"bunyan": "^1.8.12",
"bunyan-debug-stream": "^1.0.8",
"cli-table": "^0.3.1",
"cli-table2": "^0.2.0",
"clipboard": "^1.7.1",
"colors": "^1.1.2",
"commander": "^2.11.0",
"common-tags": "^1.4.0",
"compression": "1.7.1",
"compression-webpack-plugin": "^1.0.0",
"consolidate": "0.14.0",
"cookie-parser": "^1.4.3",
"copy-webpack-plugin": "^4.0.0",
"cross-spawn": "^5.1.0",
@@ -108,6 +108,7 @@
"express-static-gzip": "^0.3.1",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^0.11.2",
"fluent-langneg": "^0.1.0",
"form-data": "^2.3.1",
"fs-extra": "^4.0.1",
"graphql": "^0.10.1",
@@ -149,7 +150,7 @@
"metascraper-title": "^3.9.2",
"minimist": "^1.2.0",
"moment": "^2.18.1",
"mongoose": "^4.12.3",
"mongoose": "^5.1.1",
"ms": "^2.0.0",
"murmurhash-js": "^1.0.0",
"name-all-modules-plugin": "^1.0.1",
@@ -157,6 +158,7 @@
"node-fetch": "^1.7.2",
"nodemailer": "^2.6.4",
"npm-run-all": "^4.1.2",
"nunjucks": "^3.1.3",
"parallel-webpack": "^2.2.0",
"passport": "^0.4.0",
"passport-jwt": "^3.0.0",
@@ -219,6 +221,7 @@
"babel-plugin-dynamic-import-node": "^1.1.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"browserstack-local": "^1.3.0",
"casual": "^1.5.19",
"chai": "^3.5.0",
"chai-as-promised": "^6.0.0",
"chai-datetime": "^1.5.0",
@@ -230,7 +233,7 @@
"husky": "^0.14.3",
"identity-obj-proxy": "^3.0.0",
"jest-junit": "^3.6.0",
"lint-staged": "^7.0.0",
"lint-staged": "^7.1.0",
"mocha": "^3.1.2",
"mocha-junit-reporter": "^1.12.1",
"nightwatch": "^0.9.16",
+1
View File
@@ -9,4 +9,5 @@ export {
getDefinitionName,
getShallowChanges,
createDefaultResponseFragments,
handlePopupAuth,
} from 'coral-framework/utils';
+15 -3
View File
@@ -11,10 +11,22 @@ function getReactionConfig(reaction) {
if (CREATE_MONGO_INDEXES) {
// Create the index on the comment model based on the reaction config.
Comment.collection.createIndex(
Comment.collection.ensureIndex(
{
created_at: 1,
[`action_counts.${sc(reaction)}`]: 1,
asset_id: 1,
[`action_counts.${sc(reaction)}`]: -1,
created_at: -1,
},
{
background: true,
}
);
Comment.collection.ensureIndex(
{
asset_id: 1,
[`action_counts.${sc(reaction)}`]: -1,
created_at: -1,
},
{
background: true,

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