Compare commits

...
849 Commits
Author SHA1 Message Date
Kim Gardner f738da4dd5 Merge pull request #744 from coralproject/redis-flush
Flush Redis before each test
2017-07-07 16:19:52 +01:00
Kim Gardner b796bf2607 Merge branch 'master' into redis-flush 2017-07-07 16:06:22 +01:00
Kim Gardner 3924ced659 Merge pull request #743 from coralproject/change-org-name
Added new cli-settings
2017-07-07 16:01:55 +01:00
Wyatt Johnson 23573501bd Configured redis to flush before each test and changed default test
redis database
2017-07-07 08:57:56 -06:00
Wyatt Johnson 787c00325d Added new cli-settings 2017-07-07 08:40:18 -06:00
Kim Gardner db371d1c82 Merge pull request #742 from coralproject/bump-version-2-3-0
Update version number
2017-07-07 15:25:36 +01:00
David Erwin b744a98a3d Merge branch 'master' into bump-version-2-3-0 2017-07-07 10:20:44 -04:00
Kim Gardner 37439a0896 Merge pull request #741 from coralproject/rm-commonjs-framework
Remove CommonJS style exports from framework
2017-07-07 15:19:57 +01:00
Kim Gardner af3cd8aa68 Update version number 2017-07-07 15:09:25 +01:00
Kiwi 7baa551a5c Merge branch 'master' into rm-commonjs-framework 2017-07-07 12:39:00 +02:00
Chi Vinh Le b7c4dbd224 Remove CommonJS style exports in framework 2017-07-07 17:27:05 +07:00
Belén Curcio b28902312d Merge pull request #740 from coralproject/threading-cleanup
Cleanup
2017-07-05 12:41:28 -03:00
Kiwi f32c03f002 Merge branch 'master' into threading-cleanup 2017-07-05 17:13:43 +02:00
Chi Vinh Le 693879647e Cleanup 2017-07-05 22:11:55 +07:00
Kim Gardner c01d1f95bc Merge pull request #735 from coralproject/french-translation
French translations
2017-07-05 15:59:36 +01:00
Kim Gardner 65d6d3221b Merge branch 'master' into french-translation 2017-07-05 15:36:14 +01:00
Kim Gardner 15af61de67 Merge pull request #738 from coralproject/threading
Deep Threading Support
2017-07-05 15:35:43 +01:00
Kim Gardner 1adca208dd Merge branch 'master' into french-translation 2017-07-05 15:26:39 +01:00
Kim Gardner 935982cb08 Merge branch 'master' into threading 2017-07-05 15:23:45 +01:00
Kim Gardner 4b9f3390d3 Merge pull request #739 from coralproject/bump-version-2-2-1
Update version 2.2.1
2017-07-05 11:57:27 +01:00
Kim Gardner f94c36e9c9 Update version 2.2.1 2017-07-05 11:43:18 +01:00
Kim Gardner c976ba0ea4 Merge branch 'master' into french-translation 2017-07-05 09:17:36 +01:00
Chi Vinh Le 6d71bcbee9 Fix styling issues 2017-07-04 22:56:58 +07:00
Chi Vinh Le 9be2a520db Merge branch 'master' into threading 2017-07-04 22:42:49 +07:00
Chi Vinh Le d155615102 Add pending comment 2017-07-04 22:41:38 +07:00
Kim Gardner 4d71a5a448 Merge pull request #737 from coralproject/fix-plugin-slot-logic
Fix isSlotEmpty logic and handle empty plugin_config
2017-07-04 16:35:05 +01:00
Chi Vinh Le cfa7a74742 Fix isSlotEmpty logic and handle empty plugin_config 2017-07-04 22:22:52 +07:00
Chi Vinh Le 524be1b869 Allow customizing thread levels via TALK_THREADING_LEVEL env 2017-07-04 22:10:40 +07:00
Chi Vinh Le 31ab54d13c First round styling 2017-07-04 21:32:33 +07:00
David Erwin c5203e3460 Merge pull request #734 from coralproject/dont-show-popup-when-empty
Don't show popup when empty
2017-07-03 14:40:31 -04:00
David Erwin 79c1bd9bdd Merge branch 'master' into dont-show-popup-when-empty 2017-07-03 14:35:44 -04:00
David Erwin e2575cb346 Merge pull request #733 from coralproject/disable-plugin-components-via-config
Disable plugin components via plugin_config
2017-07-03 14:28:54 -04:00
Chi Vinh Le 3a2a4379b8 Change to 4 levels 2017-07-04 01:09:27 +07:00
Chi Vinh Le f1899f2f4a Threading support 2017-07-04 01:09:12 +07:00
Belén Curcio 4d821afa6a Merge branch 'master' into disable-plugin-components-via-config 2017-07-03 15:08:36 -03:00
David Erwin c16278221c Merge pull request #719 from coralproject/docs-contribution-guide
Rewrite contributor's guide
2017-07-03 10:24:28 -04:00
David Erwin a50b39091f ABC Always Be Clarifying 2017-07-03 10:13:09 -04:00
David Erwin 4c7506e4f5 Merge branch 'docs-contribution-guide' of https://github.com/coralproject/talk into docs-contribution-guide 2017-07-03 10:06:56 -04:00
David Erwin 2f06281439 Move contributor's guide 2017-07-03 10:03:51 -04:00
Chi Vinh Le fd1f8b24c1 Don't show popup when empty 2017-07-03 19:40:26 +07:00
Chi Vinh Le 270c3b7634 Disable plugin components via plugin_config 2017-07-03 19:01:17 +07:00
Kim Gardner e4647cab98 Merge branch 'master' into docs-contribution-guide 2017-07-03 12:12:56 +01:00
Kim Gardner 60b4909395 Merge pull request #732 from coralproject/update-lockfile
Update yarn.lock
2017-06-30 17:53:57 +01:00
Kim Gardner 0973600e68 Update yarn.lock 2017-06-30 17:45:45 +01:00
Kim Gardner e3ead31964 Merge pull request #731 from coralproject/button-states
Support post, reply, edit, load and view more loading states via global css classnames
2017-06-30 17:32:47 +01:00
Chi Vinh Le 7858ccc45d Switch to prop-types 2017-06-30 23:25:11 +07:00
Chi Vinh Le c1807daeaf Correct spanish translation key 2017-06-30 21:10:19 +07:00
Chi Vinh Le e244d4a286 Merge branch 'master' into button-states
Conflicts:
	client/coral-embed-stream/src/components/Stream.js
2017-06-30 17:46:17 +07:00
Chi Vinh Le 9de99a9ef4 Support post, reply, edit, load and view more loading states via global css classnames 2017-06-30 17:00:42 +07:00
Belén Curcio 01883851ea Merge pull request #729 from coralproject/permalink-id
Permalink by Query Param instead of Hash
2017-06-29 14:05:07 -03:00
Belen Curcio c8d9447033 Adding the polifill to the package.json 2017-06-29 13:44:40 -03:00
Kim Gardner 4319bb9da3 Merge branch 'master' into docs-contribution-guide 2017-06-29 17:31:33 +01:00
Belen Curcio 9f8c8e9c83 Merge branch 'permalink-id' of github.com:coralproject/talk into permalink-id 2017-06-29 13:30:49 -03:00
Kim Gardner 997dffd326 Merge branch 'master' into permalink-id 2017-06-29 17:30:38 +01:00
Kim Gardner f6d652df0d Merge pull request #730 from coralproject/fix-build
Update dependencies to fix build
2017-06-29 17:30:20 +01:00
Belen Curcio c3ee442775 Replacing moderation comment and history hashes 2017-06-29 13:30:12 -03:00
Belen Curcio bad869bdfe Merge branch 'permalink-id' of github.com:coralproject/talk into permalink-id 2017-06-29 13:27:08 -03:00
Belen Curcio 2289c405d1 Translatios 2017-06-29 13:25:50 -03:00
Belen Curcio 354295c0ae Adding polyfill 2017-06-29 13:20:27 -03:00
Kim Gardner 737cea6e76 Reinstall modules 2017-06-29 16:59:00 +01:00
Kim Gardner 867f6966ba Update dependencies to fix build 2017-06-29 16:52:01 +01:00
Belén Curcio fbefd7fd20 Merge branch 'master' into permalink-id 2017-06-29 12:35:06 -03:00
Kim Gardner a0a7971866 Merge pull request #725 from StephanieDClark/add-tab-classes
Updates to tab classes
2017-06-29 16:22:52 +01:00
Kim Gardner 16355169ce Merge branch 'master' into add-tab-classes 2017-06-29 16:16:24 +01:00
Kim Gardner a7f4dea74a Merge branch 'master' into permalink-id 2017-06-29 15:58:23 +01:00
Kim Gardner 8aa0c16769 Merge pull request #728 from coralproject/bump-version-2-2-0
Bump version to 2.2.0
2017-06-29 15:57:20 +01:00
Belen Curcio d31044f39e linting 2017-06-29 11:25:52 -03:00
Belén Curcio a804973990 Merge branch 'master' into permalink-id 2017-06-29 11:22:28 -03:00
Belen Curcio db9c3b45c1 Stream Error 2017-06-29 11:06:52 -03:00
Belen Curcio ddac5c2c85 Avoid Error due to invalid queries 2017-06-29 10:48:29 -03:00
Belen Curcio 0312530b86 Modifying permalink plugin 2017-06-29 10:20:33 -03:00
Belen Curcio 87f8ed401f Using CommentId by queryParam 2017-06-29 10:18:40 -03:00
Kim Gardner 06ebd9ccc5 Bump version to 2.2.0 2017-06-29 12:15:00 +01:00
Kim Gardner e615624b62 Merge branch 'master' into add-tab-classes 2017-06-29 11:06:33 +01:00
Kim Gardner 9803ccfaf5 Merge pull request #724 from coralproject/permalink-patch
Permalink plugin now enabled by default
2017-06-29 10:35:11 +01:00
Wyatt Johnson ab84436a78 Update plugins.default.json 2017-06-28 15:32:53 -06:00
StephanieDClark ccaa744a78 Updates to tab classes 2017-06-28 14:26:41 -07:00
Belén Curcio d55fc6b601 Merge branch 'master' into docs-contribution-guide 2017-06-28 12:08:40 -03:00
Kim Gardner 32318567c9 Merge pull request #723 from coralproject/dont-alias-username-2
Don't alias username 2
2017-06-28 15:22:55 +01:00
Kiwi 6cf8c01d61 Merge branch 'master' into dont-alias-username-2 2017-06-28 21:17:41 +07:00
Chi Vinh Le b92afff4e7 Don't alias username 2 2017-06-28 21:14:34 +07:00
Kim Gardner 089ed0f09b Merge pull request #722 from coralproject/edit-name-fix
Allow editing to the same username in the "create username" flow
2017-06-28 14:43:17 +01:00
Belén Curcio 2258125789 Merge branch 'master' into edit-name-fix 2017-06-28 10:22:31 -03:00
Belén Curcio 8de4b475a1 Merge pull request #721 from coralproject/smooth-scroll
Smooth Scroll on Moderation
2017-06-28 10:21:56 -03:00
Belen Curcio 4691ee07de Merge branch 'smooth-scroll' of github.com:coralproject/talk into smooth-scroll 2017-06-28 09:07:19 -03:00
Belen Curcio f767b6a998 Removing old 2017-06-28 09:06:46 -03:00
Kiwi 9b26b0a434 Merge branch 'master' into edit-name-fix 2017-06-28 18:56:34 +07:00
Kim Gardner 7817804bae Merge branch 'master' into smooth-scroll 2017-06-28 12:51:43 +01:00
Kim Gardner b2738afdbe Merge pull request #718 from coralproject/no-username-alias
Don't alias username
2017-06-28 12:51:27 +01:00
Kim Gardner e0a32e5a70 Merge branch 'master' into smooth-scroll 2017-06-28 12:27:40 +01:00
Kiwi b7a981e84b Merge branch 'master' into no-username-alias 2017-06-28 16:37:01 +07:00
Kiwi 52431a4c4c Merge branch 'master' into edit-name-fix 2017-06-28 16:36:20 +07:00
Kim Gardner ca0431054b Merge pull request #720 from coralproject/flag-reason
BUG: Flag Username - Offensive, doesn't show up in the Community Admin
2017-06-28 10:33:52 +01:00
Kim Gardner 3f03994a51 Merge branch 'master' into flag-reason 2017-06-28 10:27:48 +01:00
Kim Gardner 198efd02b4 Merge branch 'master' into smooth-scroll 2017-06-28 10:27:00 +01:00
Kiwi aff486e909 Merge branch 'master' into no-username-alias 2017-06-28 16:22:12 +07:00
Kiwi 55e62b2b22 Merge branch 'master' into edit-name-fix 2017-06-28 16:20:01 +07:00
Kim Gardner 37e6c2c45c Merge pull request #717 from coralproject/live-suspension
Live update on stream when banned user, suspended user or rejected username
2017-06-28 10:16:18 +01:00
Chi Vinh Le 270491dc0f Allow editing to the same username in the "create username" flow 2017-06-28 16:08:46 +07:00
Belén Curcio 04b562543e Merge branch 'master' into flag-reason 2017-06-27 16:53:56 -03:00
Belen Curcio dd76ac1ed4 Now the smooth flag works 2017-06-27 16:51:00 -03:00
Belen Curcio e03e15fb3b renaming 2017-06-27 16:20:08 -03:00
Belen Curcio f13682c125 flagMap added to the admin side 2017-06-27 16:12:13 -03:00
David Erwin 3695ce5cb7 Update links 2017-06-27 15:08:21 -04:00
David Erwin d68169757d Rewrite contributor's guide 2017-06-27 15:04:00 -04:00
Belén Curcio 6d4765b45d Merge branch 'master' into no-username-alias 2017-06-27 16:03:03 -03:00
David Erwin b94824b8ee Merge branch 'master' into live-suspension 2017-06-27 14:14:21 -04:00
Belén Curcio cb1b9a1463 Merge pull request #714 from coralproject/unify-taglists
Refactor TagsInput and improve UX
2017-06-27 14:54:57 -03:00
Belen Curcio 35ccc5070d Flag refactor 2017-06-27 14:52:12 -03:00
Kiwi efb18cdb32 Merge branch 'master' into unify-taglists 2017-06-28 00:28:32 +07:00
Kiwi 2338ff1694 Merge branch 'master' into no-username-alias 2017-06-28 00:15:14 +07:00
Chi Vinh Le b134d4099c More concise and functional subscribing 2017-06-28 00:13:47 +07:00
Kim Gardner 1b40521f88 Merge branch 'master' into live-suspension 2017-06-27 17:53:58 +01:00
Kim Gardner 88473a1c71 Merge pull request #716 from coralproject/coral-plugin-permalink
Talk Plugin Permalink
2017-06-27 17:53:38 +01:00
Chi Vinh Le 97ab338486 Don't alias username 2017-06-27 23:51:49 +07:00
Kiwi 8d4e45c0ed Merge branch 'master' into coral-plugin-permalink 2017-06-27 23:46:55 +07:00
Kim Gardner 53b481dc68 Merge branch 'master' into unify-taglists 2017-06-27 16:25:00 +01:00
Chi Vinh Le b67ac375ba Pass current user to CommentBox 2017-06-27 22:14:06 +07:00
Chi Vinh Le 500d5d582e Fix edit 2017-06-27 21:51:19 +07:00
Kiwi 5b9fe5688f Merge branch 'master' into live-suspension 2017-06-27 21:44:26 +07:00
Chi Vinh Le 175dd30d27 Keep comment box when user is degraded (live) 2017-06-27 21:40:48 +07:00
David Erwin 33990053fb Merge pull request #709 from coralproject/docs-plugins-quickstart
Docs plugins quickstart
2017-06-27 10:19:53 -04:00
Kim Gardner 3f4747c2ec Fix typo 2017-06-27 14:11:04 +01:00
Chi Vinh Le db3187cb56 Merge branch 'master' into live-suspension 2017-06-27 20:08:53 +07:00
Chi Vinh Le c90c177b7c Live update on stream when banned user, suspended user or rejected username 2017-06-27 20:06:27 +07:00
Kim Gardner 933891b2e3 Merge branch 'master' into docs-plugins-quickstart 2017-06-27 13:41:16 +01:00
Kim Gardner 43dd72c715 Merge branch 'master' into unify-taglists 2017-06-27 13:40:24 +01:00
Kim Gardner a7f0de6e62 Merge branch 'master' into coral-plugin-permalink 2017-06-27 13:40:15 +01:00
Kim Gardner 53742934f9 Merge pull request #715 from coralproject/commentCount
Comment Count by Asset URL
2017-06-27 13:01:47 +01:00
Belen Curcio b8aed80f1c Merge branch 'coral-plugin-permalink' of github.com:coralproject/talk into coral-plugin-permalink 2017-06-26 17:35:07 -03:00
Belen Curcio 4e94727749 Merge branch 'master' of github.com:coralproject/talk into coral-plugin-permalink 2017-06-26 17:34:53 -03:00
Belén Curcio 8412fa5613 Merge branch 'master' into coral-plugin-permalink 2017-06-26 16:26:46 -03:00
Belen Curcio bee1cc124c renamed to talk 2017-06-26 16:26:05 -03:00
Belen Curcio 2ac6ad8eb8 Using the plugin api 2017-06-26 16:16:40 -03:00
Belen Curcio fa9a4ecc50 Clearing the timeout 2017-06-26 16:08:32 -03:00
Belen Curcio b00d2f569e Removing styles from default.css 2017-06-26 16:01:07 -03:00
Belen Curcio 5e1450ffb0 Renaming to talk 2017-06-26 15:46:53 -03:00
Belen Curcio a14e4859a9 refactor 2017-06-26 14:49:24 -03:00
Belen Curcio 2745245564 updated 2017-06-26 14:39:10 -03:00
Kim Gardner 3bb1279acf Merge branch 'master' into docs-plugins-quickstart 2017-06-26 16:15:59 +01:00
Belen Curcio fedca113d2 Adding plugin 2017-06-26 11:50:51 -03:00
Belen Curcio 59407e99d7 commentCount by asset_url 2017-06-26 09:59:28 -03:00
Kiwi c7c33dcf13 Merge branch 'master' into unify-taglists 2017-06-26 19:08:38 +07:00
Kim Gardner 0e152797ad Merge pull request #710 from ALawliet/fix-highlight-suspect-banned-words-regex
Make highlight suspect/banned words regex case insensitive
2017-06-26 12:48:11 +01:00
Kim Gardner 58d9f294b7 Merge branch 'master' into fix-highlight-suspect-banned-words-regex 2017-06-26 12:25:30 +01:00
Kim Gardner fddbf2c00f Merge pull request #713 from coralproject/validate-confirm-password
Validate `confirmPassword` in the web installer
2017-06-26 12:25:16 +01:00
Chi Vinh Le 10ae39ba09 Merge branch 'master' into unify-taglists 2017-06-26 17:39:06 +07:00
Chi Vinh Le a025f4ca24 Validate confirmPassword in the web installer 2017-06-26 17:35:45 +07:00
Chi Vinh Le eb4d85fb86 Refactor TagsInput and Improve UX 2017-06-26 17:34:15 +07:00
A Lawliet 5e6a295f11 Make highlight suspect/banned words regex case insensitive 2017-06-24 15:50:44 -04:00
David Erwin b864761ef8 Merge branch 'master' into docs-plugins-quickstart 2017-06-23 16:22:19 -04:00
David Erwin 01f6bfa979 Clean up the quickstart 2017-06-23 16:18:29 -04:00
Kim Gardner e2a9a42ec6 Merge pull request #708 from coralproject/bugs-fix
BUG: Wrong label in Admin Configure
2017-06-23 18:37:46 +01:00
Belén Curcio b65c07a8a2 Merge branch 'master' into bugs-fix 2017-06-23 14:32:54 -03:00
David Erwin a49a53903d Finish quickstart first draft 2017-06-23 13:29:35 -04:00
Belen Curcio 5984f57682 Close after, wrong label 2017-06-23 14:29:22 -03:00
Kiwi 1c844061f4 Merge pull request #705 from Goyapa/issue-676-set-domain-whitelist
Issue-676 add addOnBlur preventing loss of domain-whitelist settings
2017-06-24 00:29:19 +07:00
Kiwi bc878051f8 Merge branch 'master' into issue-676-set-domain-whitelist 2017-06-24 00:22:34 +07:00
Kiwi b805451d37 Merge pull request #700 from coralproject/refactor-ban-suspend-actions
Refactor and reuse "Actions" in "Moderate" and "Community"
2017-06-23 22:43:38 +07:00
Chi Vinh Le 4a5424b81d Translation Querido/a 2017-06-23 22:38:30 +07:00
Kiwi 18a767b8a4 Merge branch 'master' into refactor-ban-suspend-actions 2017-06-23 22:24:01 +07:00
Kim Gardner 32b6627144 Merge pull request #696 from coralproject/bugs-fix
Bug Fixes
2017-06-23 16:22:06 +01:00
Kim Gardner e1b3eb9ff7 Merge branch 'master' into issue-676-set-domain-whitelist 2017-06-23 16:19:46 +01:00
Kim Gardner b76578b866 Merge branch 'master' into refactor-ban-suspend-actions 2017-06-23 16:17:26 +01:00
Kim Gardner c09d1524a9 Merge branch 'master' into bugs-fix 2017-06-23 16:17:09 +01:00
Kim Gardner 548b56be33 Merge pull request #707 from coralproject/redirect-to-all
If not specified redirect to 'all' tab
2017-06-23 16:16:57 +01:00
Belen Curcio ea030af440 missing keys 2017-06-23 09:59:05 -03:00
Belen Curcio af59a2e836 Merge branch 'bugs-fix' of github.com:coralproject/talk into bugs-fix 2017-06-23 09:56:43 -03:00
Belen Curcio 2ab2b41b55 unbind 2017-06-23 09:54:21 -03:00
Belén Curcio 4bb64231e9 Merge branch 'master' into issue-676-set-domain-whitelist 2017-06-23 09:50:29 -03:00
Belén Curcio 2800458519 Merge branch 'master' into bugs-fix 2017-06-23 09:49:54 -03:00
Kiwi 90bc9be1b2 Merge branch 'master' into redirect-to-all 2017-06-23 18:29:52 +07:00
Kiwi 64a4bf6760 Merge pull request #703 from ALawliet/fix-moderate-link
Fix Moderate → link by setting to "All" for that story
2017-06-23 18:29:38 +07:00
Kiwi 6d5b8e73d4 Merge branch 'master' into redirect-to-all 2017-06-23 18:18:18 +07:00
Kiwi 87c14dd824 Merge branch 'master' into fix-moderate-link 2017-06-23 18:15:36 +07:00
Chi Vinh Le e89a15f2d2 If not specified redirect to 'all' tab 2017-06-23 18:11:54 +07:00
Kim Gardner 7d84cff0f7 Merge pull request #698 from ALawliet/master
Search dropdown: clear search when closed
2017-06-23 12:10:10 +01:00
Kim Gardner 6fa74f84f1 Merge branch 'master' into fix-moderate-link 2017-06-23 09:53:35 +01:00
Kim Gardner 10ed5c9a4a Merge branch 'master' into master 2017-06-23 09:53:23 +01:00
Michael Macherey 40b4c0cab4 Issue-676 add addOnBlur preventing loss of domain-whitelist settings
Whithout addOnBlur set true, typed domain-whitelist settings like localhost:3000 get lost and will not be set in configure->Tech Settings->Permitted Domains.
This address issue-676.
2017-06-23 00:35:49 +02:00
David Erwin a3059a9fa3 Update docs menu 2017-06-22 17:42:42 -04:00
David Erwin d57d05fda9 Move plugin docs into new docs 2017-06-22 17:33:16 -04:00
David Erwin 57e0a160a2 Add plugins overview and quickstart 2017-06-22 17:25:01 -04:00
Belén Curcio bb544a92b3 Merge pull request #704 from coralproject/keep-alive
Added support for keep alive websocket frames
2017-06-22 17:45:02 -03:00
Wyatt Johnson 010f0cb0d7 Added support for keep alive websocket frames 2017-06-22 14:32:49 -06:00
A Lawliet 60dd6a37d7 Fix Moderate → link by setting to "All" for that story 2017-06-22 13:44:48 -04:00
Chi Vinh Le 455a034802 Don't show dropdown on current logged in user 2017-06-22 22:13:09 +07:00
Chi Vinh Le 46e707ba51 Use snake case for new translations 2017-06-22 22:04:11 +07:00
Kiwi e7e66f1e6f Merge branch 'master' into refactor-ban-suspend-actions 2017-06-22 21:57:30 +07:00
Chi Vinh Le 8bb38c5ee5 Refactor and reuse "Actions" in "Moderate" and "Community" 2017-06-22 21:50:19 +07:00
A Lawliet c41b3c27ba Prevent unnecessary render - no need to clear search if already empty, just close dropdown 2017-06-22 10:29:41 -04:00
Kim Gardner cfd55907bd Merge branch 'master' into bugs-fix 2017-06-22 14:34:04 +01:00
Kim Gardner 91907e7647 Merge branch 'master' into master 2017-06-22 14:28:01 +01:00
Kim Gardner a5048eadb4 Merge pull request #699 from coralproject/update-circle-config
Limit circle deploys off only our master branch
2017-06-22 14:27:42 +01:00
Kim Gardner bde08ea256 Merge branch 'master' into update-circle-config 2017-06-22 14:07:53 +01:00
Kim Gardner 3de6ba1b24 Limit circle deploys off only our master branch 2017-06-22 14:05:50 +01:00
Kim Gardner dac9401c44 Merge branch 'master' into bugs-fix 2017-06-22 12:30:49 +01:00
A Lawliet bba4474858 Search dropdown: clear search when closed 2017-06-22 01:51:06 -04:00
Kiwi 259f98a4ec Merge pull request #697 from coralproject/bug-sweep
Bug sweep
2017-06-22 01:05:11 +07:00
Chi Vinh Le f7a8f9bbd1 Oops this comment needs a rewrite 2017-06-22 00:59:04 +07:00
Kiwi 83096dbcce Merge branch 'master' into bug-sweep 2017-06-22 00:52:30 +07:00
Kiwi 555a71fd9b Merge pull request #695 from coralproject/bug-killing-spree
Bug killing spree
2017-06-22 00:52:00 +07:00
Kiwi 2c57ec3c7c Merge branch 'master' into bug-killing-spree 2017-06-22 00:47:27 +07:00
Chi Vinh Le c0e9e4272b Merge branch 'master' into bug-killing-spree
Conflicts:
	client/coral-admin/src/routes/Moderation/components/UserDetail.js
2017-06-22 00:46:17 +07:00
Belén Curcio a31810ddf4 Merge branch 'master' into bug-sweep 2017-06-21 14:43:32 -03:00
Belén Curcio f6b2a25e72 Merge pull request #687 from coralproject/pat
Tokens
2017-06-21 14:43:05 -03:00
Wyatt Johnson bd7443672a Merge branch 'master' into pat 2017-06-21 11:36:47 -06:00
Belén Curcio 358ea0d37c Merge branch 'master' into bug-sweep 2017-06-21 14:35:50 -03:00
Kiwi c0dcfe5f4e Merge pull request #694 from coralproject/copy
withCopyToClipboard & ButtonCopyToClipboard
2017-06-22 00:35:25 +07:00
Wyatt Johnson 91b7cd10f8 fixing all the 👻 2017-06-21 11:35:00 -06:00
Wyatt Johnson f537b4a772 Merge branch 'master' into pat 2017-06-21 11:34:18 -06:00
Belen Curcio a9cefd8cbd deleting old ref 2017-06-21 14:23:51 -03:00
Belen Curcio f08c2f4152 Merge branch 'copy' of github.com:coralproject/talk into copy 2017-06-21 13:31:53 -03:00
Belen Curcio cdcd543fe0 good looking export 2017-06-21 13:30:09 -03:00
Belen Curcio 66aec74b38 good looking export 2017-06-21 13:29:44 -03:00
Belen Curcio 85dcc8d0ec Merge branch 'master' of github.com:coralproject/talk into copy 2017-06-21 13:28:33 -03:00
Chi Vinh Le 9d572f6c3b Add proptype 2017-06-21 22:27:13 +07:00
Chi Vinh Le 012ff926eb Use router for 'moderate all' link and only show when necessary 2017-06-21 22:21:42 +07:00
Kiwi 91fd8ceabd Merge branch 'master' into copy 2017-06-21 21:52:51 +07:00
Kiwi 4a354f0b9a Merge branch 'master' into bug-sweep 2017-06-21 21:48:35 +07:00
Chi Vinh Le 3beb62f837 Merge branch 'master' into bug-killing-spree
Conflicts:
	client/coral-admin/src/routes/Moderation/components/UserDetail.js
2017-06-21 21:47:01 +07:00
Chi Vinh Le 57ecba6f33 Remove unessecary binds 2017-06-21 21:36:27 +07:00
Kim Gardner 712a53db24 Merge pull request #693 from coralproject/show-version-in-nav
Show Talk version number in nav
2017-06-21 15:35:52 +01:00
Chi Vinh Le f709057933 Require username to be different when editing it 2017-06-21 21:18:28 +07:00
Belen Curcio ac89ddc43f Show user details from flag history 2017-06-21 11:04:45 -03:00
Belen Curcio b0156fa11f missing header css 2017-06-21 10:53:12 -03:00
Belen Curcio 16eb93792b Merge branch 'master' of github.com:coralproject/talk into bugs-fix 2017-06-21 10:43:28 -03:00
Belen Curcio 1b1f2a3608 Changing moderation keys: d approve and f reject 2017-06-21 10:42:01 -03:00
Chi Vinh Le 6628e6d08d Correctly handle login with mixed case email 2017-06-21 20:34:33 +07:00
Kim Gardner e2f94aa141 Merge branch 'master' into show-version-in-nav 2017-06-21 14:29:49 +01:00
Kim Gardner f20b9819aa Merge branch 'master' into copy 2017-06-21 14:29:36 +01:00
Chi Vinh Le 91fa7b2d8d Show correct login error messages in admin 2017-06-21 20:24:54 +07:00
Kim Gardner 74fff2f1ac Add all stories link to story search dropdown 2017-06-21 14:14:33 +01:00
Chi Vinh Le 4b063607ab Return the returns 2017-06-21 20:04:38 +07:00
Chi Vinh Le 9b26bc9d96 Fix Typo 2017-06-21 19:53:00 +07:00
Chi Vinh Le 38eaff5397 Don't render title when it's null 2017-06-21 19:41:22 +07:00
Chi Vinh Le 420513804c Show correct notification message when edit fails 2017-06-21 19:37:19 +07:00
Chi Vinh Le b0079c191b Remove non existant 'truthy' tests 2017-06-21 19:31:55 +07:00
Chi Vinh Le 9815248b8d Prevent errors being swallowed 2017-06-21 19:17:48 +07:00
Kim Gardner 79346738d7 Have Moderate this Stream link also default to All 2017-06-21 13:01:23 +01:00
Kim Gardner e16553419d Merge pull request #686 from coralproject/subscribe-mod-actions
Mod Queue Live Updates
2017-06-21 12:24:32 +01:00
Kim Gardner dccf182591 Merge branch 'master' into copy 2017-06-21 12:23:50 +01:00
Kim Gardner 33057e197f Merge branch 'master' into show-version-in-nav 2017-06-21 12:23:20 +01:00
Kim Gardner 605e759bc6 Merge branch 'master' into pat 2017-06-21 12:22:28 +01:00
Chi Vinh Le 706a12d2e2 No MoreMore 2017-06-21 02:48:59 +07:00
Kiwi adeca927a6 Merge branch 'master' into bug-killing-spree 2017-06-21 01:37:46 +07:00
Chi Vinh Le fc14e9d2fd Prevent double execution of fetchMore 2017-06-21 01:29:10 +07:00
Chi Vinh Le 49420e152e Click outside closes TopRightMenu, detect click outside iframe 2017-06-21 01:29:10 +07:00
Chi Vinh Le 644c291d7f Don't cut off long comment 2017-06-21 01:29:07 +07:00
Kiwi 40d7500a75 Merge branch 'master' into subscribe-mod-actions 2017-06-20 21:12:09 +07:00
Kim Gardner 3d27c4538a Merge pull request #691 from coralproject/bump-version-2-1-0
Bump version 2.1.0
2017-06-20 14:51:36 +01:00
Belen Curcio 942ea7b916 ButtonCopyToClipboard 2017-06-20 10:37:10 -03:00
Chi Vinh Le 4d53a72767 Always default to all queue 2017-06-20 20:00:00 +07:00
Chi Vinh Le 23fcba4ccc Don't show caret when logged out 2017-06-20 19:55:53 +07:00
Belén Curcio bee4b4454c Merge branch 'master' into show-version-in-nav 2017-06-20 09:52:50 -03:00
Belén Curcio 679c6ce335 Merge branch 'master' into bump-version-2-1-0 2017-06-20 09:52:26 -03:00
Belén Curcio 71d2d0ab10 Merge pull request #692 from coralproject/update-readme
Update README
2017-06-20 09:52:14 -03:00
Belen Curcio d97fb0a83f Refs by ReactDOMNode 2017-06-20 09:47:37 -03:00
Chi Vinh Le 14311dc298 Merge branch 'master' into subscribe-mod-actions
Conflicts:
	client/coral-admin/src/routes/Moderation/components/Comment.js
2017-06-20 19:34:56 +07:00
Belen Curcio 7faa00c463 withCopyToClipboard 2017-06-20 09:05:01 -03:00
Belen Curcio d23d426567 Adding the translations 2017-06-20 08:37:53 -03:00
Belen Curcio fe8f3ae4bb Moving Copy component 2017-06-20 08:32:08 -03:00
Kim Gardner 646b5be8c8 Show version in nav 2017-06-20 10:48:01 +01:00
Kim Gardner ea12c07cd4 Update README 2017-06-20 10:29:39 +01:00
Kim Gardner bb4cd91b0c Merge branch 'master' into pat 2017-06-20 10:12:47 +01:00
Kim Gardner dca759a5e0 Bump version 2.1.0 2017-06-20 10:02:10 +01:00
Kim Gardner 018b35e42a Merge pull request #688 from coralproject/fixes
Bug-Fixes
2017-06-20 09:59:00 +01:00
Kim Gardner 672e36c66c Merge branch 'master' into fixes 2017-06-20 09:54:38 +01:00
Kim Gardner b3db231f09 Merge pull request #689 from coralproject/keyboard-shortcuts
Keyboard shortcuts
2017-06-20 09:53:49 +01:00
Kim Gardner 38a53d61f3 Merge branch 'master' into fixes 2017-06-20 09:53:24 +01:00
Kim Gardner fc15bcae4e Merge branch 'master' into keyboard-shortcuts 2017-06-20 09:46:04 +01:00
Kim Gardner 6959699ecc Merge pull request #690 from coralproject/admin-links
Settings Cog Links
2017-06-20 09:45:44 +01:00
Belen Curcio 99bb4b7198 New Links 2017-06-19 18:09:38 -03:00
Belen Curcio 12a5654fb8 Keyboard shorcuts working 2017-06-19 17:48:20 -03:00
Belen Curcio dad6534011 Fix close modal 2017-06-19 17:26:57 -03:00
Belen Curcio c36a0bc9e9 d approve and f reject 2017-06-19 17:23:57 -03:00
Belen Curcio 30a385e03b Linting 2017-06-19 17:09:49 -03:00
Wyatt Johnson 174088fc62 Merge branch 'master' into pat 2017-06-19 13:51:35 -06:00
Belen Curcio 16ef833fd8 Ádding Copy component, and UserDetail fic 2017-06-19 16:17:50 -03:00
Chi Vinh Le 523f23269b Translate, pep up notifications 2017-06-20 01:38:38 +07:00
Kiwi 8dbc17c8d9 Merge branch 'master' into subscribe-mod-actions 2017-06-20 01:12:54 +07:00
Chi Vinh Le b8967dcdb4 More accurate loading detection 2017-06-20 01:09:37 +07:00
Chi Vinh Le 70276c3358 Fix subscription issues 2017-06-20 01:01:39 +07:00
Belen Curcio 1f2eca300a Report vbox 2017-06-19 14:55:58 -03:00
Belen Curcio 3c99b8e7fb Sign In Button Position 2017-06-19 14:35:26 -03:00
Belen Curcio 073fccebd3 Show likes 2017-06-19 14:30:56 -03:00
Belén Curcio 13e311de7f Merge pull request #684 from coralproject/action-focus-states
Action focus states
2017-06-19 14:09:22 -03:00
Kim Gardner ba81399707 Merge branch 'master' into pat 2017-06-19 18:03:23 +01:00
Belen Curcio cf93727f73 Merge branch 'master' of github.com:coralproject/talk into action-focus-states 2017-06-19 14:03:07 -03:00
Chi Vinh Le fbbd0fa027 Merge branch 'master' into subscribe-mod-actions
Conflicts:
	client/coral-admin/src/routes/Moderation/components/Moderation.js
	client/coral-admin/src/routes/Moderation/components/styles.css
	client/coral-admin/src/routes/Moderation/containers/Moderation.js
2017-06-19 23:52:10 +07:00
Kim Gardner 332679a7bc Merge pull request #680 from coralproject/search-stories
Search stories
2017-06-19 17:44:16 +01:00
Wyatt Johnson 2a2ca86263 Merge branch 'master' into pat 2017-06-19 09:40:25 -06:00
Wyatt Johnson 393ad8530a added tests, fixed some services 2017-06-19 08:55:08 -06:00
Chi Vinh Le 0cd3921f44 Simplification 2017-06-19 21:29:33 +07:00
Chi Vinh Le f954d2be90 Better loading detection 2017-06-19 21:23:30 +07:00
Chi Vinh Le 2a28306ab2 Merge branch 'master' into subscribe-mod-actions 2017-06-19 21:17:33 +07:00
Chi Vinh Le e1bc1476ea Remove the need for querying all assets 2017-06-19 21:08:12 +07:00
Chi Vinh Le 853c1e407e Simplify args 2017-06-19 21:08:12 +07:00
Belen Curcio 77fddba841 removing overflow hidden 2017-06-19 11:05:16 -03:00
David Erwin 17d36c69d0 Merge branch 'master' into action-focus-states 2017-06-19 09:23:40 -04:00
Belén Curcio 1cf6161b06 Merge branch 'master' into search-stories 2017-06-18 20:14:35 -03:00
Andrew Losowsky 9ef7544788 Merge pull request #685 from coralproject/bump-version-2-0-1
Update version number
2017-06-16 17:41:51 -04:00
Kim Gardner dfccf962d4 Update version number 2017-06-16 17:30:09 -04:00
IAmSamHankins 6c5d94ec3b adjustments to letter spacing, and container positioning. 2017-06-16 16:40:17 -04:00
IAmSamHankins e7eb300e12 search input box styling 2017-06-16 15:47:45 -04:00
IAmSamHankins 614f31d0da styling of search results bar 2017-06-16 15:39:24 -04:00
Belen Curcio 61591a9716 Removing extra log 2017-06-16 16:24:00 -03:00
Belen Curcio 367c570747 ModQueue query 2017-06-16 16:21:39 -03:00
Belen Curcio 991bdc6089 Updated BE 2017-06-16 16:18:02 -03:00
Samantha Hankins 4ce38d429e Merge branch 'master' into action-focus-states 2017-06-16 15:06:11 -04:00
IAmSamHankins dcee2a4438 adjust comment stream sizing to allow for chrome focus state 2017-06-16 15:00:42 -04:00
Belen Curcio 4403decbe9 Handling dropdown on Esc <3 2017-06-16 15:50:58 -03:00
Belen Curcio e30d5a9c62 Sizing 2017-06-16 15:41:32 -03:00
Belen Curcio fe94a5a6d1 Limit 10 2017-06-16 15:08:10 -03:00
Belen Curcio c7b58957cb Merge branch 'search-stories' of github.com:coralproject/talk into search-stories 2017-06-16 15:07:47 -03:00
Belen Curcio 89cf13b4cf Updates 2017-06-16 15:05:22 -03:00
Belén Curcio f0b6679141 Merge branch 'master' into search-stories 2017-06-16 14:50:05 -03:00
Kiwi 26253255ce Merge pull request #658 from alleyinteractive/TCP-7
Pass onAuthChange callback into embed
2017-06-17 00:33:44 +07:00
Max Eddy 64174b97af Cleanup 2017-06-16 13:04:27 -04:00
Max Eddy fe0c581ea9 Note logout action as well for onAuthChanged event. 2017-06-16 13:04:27 -04:00
Max Eddy 4982d8a9ad Use REST API result for check login message. 2017-06-16 13:04:27 -04:00
Max Eddy fd9b14a560 cb option for auth change event 2017-06-16 13:04:27 -04:00
Max Eddy 6139828844 Send post message with auth state to parent window. 2017-06-16 13:04:27 -04:00
Chi Vinh Le c14c3fbe23 Streamline perm names 2017-06-17 00:03:33 +07:00
Kim Gardner f34d546a37 Merge branch 'master' into search-stories 2017-06-16 12:58:12 -04:00
Chi Vinh Le 6da0028258 Reset websocket after login change 2017-06-16 23:00:40 +07:00
Chi Vinh Le 74fbadcc0f Refactor subscriptions 2017-06-16 22:55:22 +07:00
Kim Gardner 30ca7b3d64 Merge pull request #683 from coralproject/cli-fix
User CLI Fix
2017-06-16 09:49:53 -04:00
Chi Vinh Le 9573c9a7af Refactor & docs 2017-06-16 20:49:17 +07:00
Wyatt Johnson 1dec8bfaab small fix 2017-06-16 09:45:26 -04:00
Chi Vinh Le 2625b372e1 Handle comment flags 2017-06-16 20:06:13 +07:00
Chi Vinh Le 5f450adbaf Simplify subscription 2017-06-16 19:19:59 +07:00
Belen Curcio d386316f8a Styles, copy, funtionality, state changes and clicks. Ready to be tested. 2017-06-16 08:19:22 -03:00
Belen Curcio 5604aa8525 Overlay and Container 2017-06-16 07:33:22 -03:00
Belen Curcio b518358ea8 Lintin 2017-06-15 22:51:15 -03:00
Belen Curcio fe74f4a8d2 Search on enter, removed unused links 2017-06-15 20:24:22 -03:00
Wyatt Johnson b78d4f29ea Added PAT support 2017-06-15 18:12:53 -04:00
Belen Curcio ca08efea8a Adding proptypes as a package, and linting 2017-06-15 18:49:05 -03:00
Belen Curcio 5718ba6748 Merge branch 'search-stories' of github.com:coralproject/talk into search-stories 2017-06-15 17:42:43 -03:00
Belen Curcio 0123736275 Deleting unnecesary connects 2017-06-15 17:42:22 -03:00
Belen Curcio 18061e3e1c new func and style 2017-06-15 17:32:14 -03:00
Kim Gardner b40df9f10c Merge branch 'master' into search-stories 2017-06-15 15:18:57 -04:00
Andrew Losowsky 0c7f9cdb1e Merge pull request #682 from coralproject/fix-config-stream-typos
Fix typos
2017-06-15 14:50:57 -04:00
Chi Vinh Le ecf63ffb55 More accurate edit notification 2017-06-16 01:49:35 +07:00
Kim Gardner ba659b47be Fix typos 2017-06-15 14:44:06 -04:00
Chi Vinh Le f73f8d916e Only show notification once 2017-06-16 00:50:44 +07:00
Chi Vinh Le 93c8b115f5 Fix some queries 2017-06-16 00:43:48 +07:00
Belen Curcio 685695bf43 Clear search value 2017-06-15 14:32:50 -03:00
Belen Curcio d7c6d7dfac Merge branch 'search-stories' of github.com:coralproject/talk into search-stories 2017-06-15 14:32:27 -03:00
Belén Curcio c7cfd80c7f Merge branch 'master' into search-stories 2017-06-15 14:15:34 -03:00
Belen Curcio 928e19ed67 Merge branch 'master' of github.com:coralproject/talk into search-stories 2017-06-15 14:14:13 -03:00
Chi Vinh Le 5a2ce0f6a7 No div in p 2017-06-15 23:31:30 +07:00
Chi Vinh Le 312103098c More mod queue fixes 2017-06-15 23:27:31 +07:00
Chi Vinh Le 21f95c33c2 Use correct comment id in UserDetail 2017-06-15 22:54:36 +07:00
Chi Vinh Le cbcb84f7ac No need for editableUntil 2017-06-15 22:54:29 +07:00
Chi Vinh Le 303b3ea0f5 Support live comment edit update 2017-06-15 22:46:38 +07:00
Kim Gardner 0bfd4f8797 Merge pull request #681 from coralproject/fix-viewing-options
Use new import path for coral-ui
2017-06-15 07:46:16 -04:00
Chi Vinh Le a088827568 Use new import path for coral-ui 2017-06-15 18:31:53 +07:00
Chi Vinh Le 5ecdfe94cc Refactor state updater 2017-06-15 18:28:08 +07:00
Belén Curcio 648b46e5af Merge branch 'master' into search-stories 2017-06-14 18:44:36 -03:00
Belen Curcio e160dfaa3e Style and functionality 2017-06-14 18:40:44 -03:00
Kim Gardner fb910efbc0 Merge pull request #667 from coralproject/bump-version-2-0-0
Prep to release 2.0.0
2017-06-14 15:13:19 -04:00
Kim Gardner 8dd8544154 Merge branch 'master' into bump-version-2-0-0 2017-06-14 15:06:45 -04:00
Kim Gardner b053be8e4d Merge pull request #675 from coralproject/comment-link
Highlighting Links and New plugin: coral-plugin-comment-content
2017-06-14 15:06:31 -04:00
Chi Vinh Le 035c00ee09 Only create Slot elements once, support Class Components as defaultComponent 2017-06-15 01:28:06 +07:00
Kim Gardner 69ab46d32a Merge branch 'master' into bump-version-2-0-0 2017-06-14 14:26:57 -04:00
Chi Vinh Le c30d05fd6c Switch all plugins to components/ui 2017-06-15 01:21:12 +07:00
Chi Vinh Le 3658a804b0 Implement live updates for mod actions 2017-06-15 01:08:25 +07:00
Belen Curcio b40d592819 import lint 2017-06-14 13:27:06 -03:00
Belen Curcio 25376bd00f replacing coral-ui in plugins 2017-06-14 13:22:03 -03:00
Belen Curcio 787ba75ede Merge branch 'comment-link' of github.com:coralproject/talk into comment-link 2017-06-14 13:12:07 -03:00
Belen Curcio 60905bb99d Default Components for Slots 2017-06-14 13:02:51 -03:00
David Erwin fd875031dd Merge branch 'master' into comment-link 2017-06-13 17:35:19 -04:00
David Erwin 687b135adf Merge pull request #679 from coralproject/flagged-usernames-styling
style updates to flagged user names queue and updated translations
2017-06-13 17:32:39 -04:00
David Erwin 82acab2726 Merge branch 'master' into flagged-usernames-styling 2017-06-13 17:26:24 -04:00
David Erwin 987d4a5610 Merge pull request #672 from coralproject/asset-improvements
Added asset merging + url updates
2017-06-13 17:23:09 -04:00
David Erwin 4536d218d2 Merge branch 'master' into asset-improvements 2017-06-13 17:18:47 -04:00
David Erwin 5717f0bf3d Merge pull request #677 from coralproject/mod-panel-design-tweaks
hover states, link styling, alignment fixes
2017-06-13 17:10:24 -04:00
David Erwin d17144d436 Merge branch 'master' into mod-panel-design-tweaks 2017-06-13 17:06:02 -04:00
David Erwin 7d67566f00 Merge pull request #678 from coralproject/robust-live-reactions
Robust live reactions
2017-06-13 17:05:42 -04:00
Belen Curcio 5633dd27fc Link should be target blank 2017-06-13 15:53:59 -03:00
Belen Curcio fd2fcb4ed9 Parent target 2017-06-13 15:28:18 -03:00
Belen Curcio b8c5461b90 Adding react-linkify 2017-06-13 15:13:26 -03:00
Chi Vinh Le cdfa8379ff Change text and status of ErrAlreadyExists 2017-06-14 00:38:34 +07:00
IAmSamHankins 99745723fb style updates to flagged user names queue and updated translations 2017-06-13 12:40:07 -04:00
Samantha Hankins b6237892d7 Merge branch 'master' into mod-panel-design-tweaks 2017-06-13 11:04:13 -04:00
Chi Vinh Le 7d8f2aa876 More robust reactions 2017-06-13 22:02:54 +07:00
Chi Vinh Le aed265efcc Use correct fragment name 2017-06-13 22:01:58 +07:00
Belen Curcio 00d1b0e6f5 Ádding to plugins 2017-06-12 19:11:53 -03:00
Belen Curcio 65f7ee2ab3 coral-plugin-commnet-content 2017-06-12 19:09:42 -03:00
Belen Curcio c902a4b05b Merge branch 'search-stories' of github.com:coralproject/talk into search-stories 2017-06-12 16:31:14 -03:00
Belen Curcio 8a3833ec11 Merge issues 2017-06-12 16:30:59 -03:00
Belén Curcio d45c396013 Merge branch 'master' into search-stories 2017-06-12 16:27:39 -03:00
Belén Curcio d84ddfe1ec Merge branch 'master' into asset-improvements 2017-06-12 16:19:23 -03:00
Belén Curcio efc4db56d0 Merge pull request #674 from coralproject/live-reactions
Live reactions
2017-06-12 16:19:02 -03:00
Belén Curcio 32da779ac1 Merge branch 'master' into live-reactions 2017-06-12 14:46:26 -03:00
Belén Curcio 09d74e8fd6 Merge branch 'master' into asset-improvements 2017-06-12 14:34:20 -03:00
Belén Curcio 9fc139964f Merge pull request #670 from coralproject/client-targets
Webpack Plugin Targets
2017-06-12 14:32:45 -03:00
Belén Curcio 681e25ebdb Merge branch 'master' into client-targets 2017-06-12 14:09:09 -03:00
Chi Vinh Le e2fdba4898 Upgrade subscriptions-transport-ws 2017-06-13 00:00:40 +07:00
Chi Vinh Le ce76335fda Add some comments 2017-06-12 23:50:09 +07:00
Chi Vinh Le 70177d3a2c Cleanup 2017-06-12 23:45:46 +07:00
Chi Vinh Le d628c26693 Wrap mutations into framework 2017-06-12 23:25:58 +07:00
Chi Vinh Le 2a283a9a5a Support live updates 2017-06-12 23:21:27 +07:00
Chi Vinh Le 74ad3c3a35 Refactor all reactions and encapsulate server code 2017-06-12 17:02:14 +07:00
gaba d22f5215ed Adding french translation to the auth plugin. 2017-06-09 16:09:13 -07:00
riley c7d5ab935f merge master 2017-06-09 15:27:44 -06:00
IAmSamHankins cc4c943a21 hover states, link styling, alignment fixes 2017-06-09 17:27:35 -04:00
Riley Davis aa289ecbfe less terrible looking 2017-06-09 14:32:12 -06:00
Kim Gardner dfd580bf7c Merge branch 'master' into bump-version-2-0-0 2017-06-09 16:26:48 -04:00
Riley Davis de665b3a0d show stories in dropdown 2017-06-09 14:10:21 -06:00
Wyatt Johnson f929059daf added asset merging + url updates 2017-06-09 13:23:16 -06:00
Kim Gardner f2f5ddd131 Merge pull request #663 from coralproject/port-perm-checks
Port perm checks to the new framework
2017-06-09 13:24:43 -04:00
Belén Curcio 1fbeacdc36 Merge branch 'master' into port-perm-checks 2017-06-09 14:08:07 -03:00
Kim Gardner 3c58888129 Merge branch 'master' into client-targets 2017-06-09 13:00:00 -04:00
Kim Gardner b5065bad3e Merge branch 'master' into bump-version-2-0-0 2017-06-09 12:59:43 -04:00
Kim Gardner f6012526b3 Merge pull request #668 from coralproject/update-readme
Update README
2017-06-09 12:24:31 -04:00
Kim Gardner 452b2c3cdb Merge branch 'master' into bump-version-2-0-0 2017-06-09 12:17:42 -04:00
Kim Gardner fc64a98cb7 Merge branch 'master' into update-readme 2017-06-09 12:17:28 -04:00
Kiwi 8d24dcd58d Merge branch 'master' into port-perm-checks 2017-06-09 17:49:18 +07:00
Kiwi 5d59f2923e Merge branch 'master' into client-targets 2017-06-09 17:49:11 +07:00
Kiwi 291d3b583b Merge pull request #669 from coralproject/subscription-fixes
Subscription fixes
2017-06-09 17:45:31 +07:00
Chi Vinh Le c7ca3c5e4a Reset websocket after successful login 2017-06-09 17:26:42 +07:00
Kiwi 66c9e0e232 Merge branch 'master' into port-perm-checks 2017-06-09 16:59:48 +07:00
Wyatt Johnson 1d3cc906eb small fix 2017-06-08 17:34:01 -06:00
Wyatt Johnson 3bf35bdc7f Merge branch 'master' into client-targets 2017-06-08 17:32:51 -06:00
Wyatt Johnson 99174565fa fixes to webpack config (uncommenting) 2017-06-08 17:32:01 -06:00
Wyatt Johnson 8d2c1b7247 changed bundle format 2017-06-08 17:25:27 -06:00
Wyatt Johnson 399f1e7458 simplified webpack file 2017-06-08 16:53:22 -06:00
Wyatt Johnson 140f980c14 Merge branch 'master' into subscription-fixes 2017-06-08 15:09:37 -06:00
Wyatt Johnson 32708e9098 submscription fixes 2017-06-08 15:08:18 -06:00
Kim Gardner d0b3845d0f Update README 2017-06-08 15:55:41 -04:00
Kim Gardner dbb0a68766 Prep to release 2.0.0 2017-06-08 15:53:12 -04:00
Kim Gardner 3545bf01cd Merge pull request #522 from coralproject/142993479-tags
Get tags into a separate structure that can be applied to other models
2017-06-08 15:16:33 -04:00
gaba 19b6dfaa0a Adding a new language is not so straight forward. It needs to change at some point. 2017-06-08 11:39:51 -07:00
Chi Vinh Le 4e5680577e Include public comments 2017-06-09 01:17:27 +07:00
Chi Vinh Le 428246cbe2 Make it work without iframe 2017-06-09 01:16:39 +07:00
Wyatt Johnson a9a24b96c0 cleaned up plugins in webpack 2017-06-08 11:49:36 -06:00
gaba b009d7a67a Adding french translations. 2017-06-08 10:47:17 -07:00
Chi Vinh Le 1153c52c2c Correct optimistic response 2017-06-09 00:31:29 +07:00
Chi Vinh Le 995dd9fd60 Correct fragment names 2017-06-09 00:31:19 +07:00
Chi Vinh Le 007b9604af Merge branch '142993479-tags' of ssh://github.com/coralproject/talk into 142993479-tags 2017-06-09 00:24:48 +07:00
Chi Vinh Le 57747e8f13 Make mutations extendable 2017-06-09 00:22:58 +07:00
Wyatt Johnson 27445f7b19 Merge branch '142993479-tags' of github.com:coralproject/talk into 142993479-tags 2017-06-08 11:21:55 -06:00
Wyatt Johnson b06a58ebe1 adjusted flow 2017-06-08 11:21:44 -06:00
Wyatt Johnson b95e20e493 removed explicit migration step 2017-06-08 11:21:09 -06:00
Chi Vinh Le 5a89ca51f5 Merge branch '142993479-tags' of ssh://github.com/coralproject/talk into 142993479-tags 2017-06-09 00:20:19 +07:00
Wyatt Johnson 80900a98fd perform migrations during web install 2017-06-08 11:19:43 -06:00
Chi Vinh Le d55f22b09c Merge branch '142993479-tags' of ssh://github.com/coralproject/talk into 142993479-tags 2017-06-09 00:19:37 +07:00
Chi Vinh Le 9b839b76f5 Don't assume response name pattern 2017-06-08 23:55:37 +07:00
Chi Vinh Le 8eb8010a33 Implement Response interface 2017-06-08 23:54:08 +07:00
Belen Curcio 40709c5b79 Merge branch '142993479-tags' of github.com:coralproject/talk into 142993479-tags 2017-06-08 12:58:01 -03:00
Belen Curcio 6bc5793e57 Merge branch 'master' of github.com:coralproject/talk into 142993479-tags 2017-06-08 12:56:33 -03:00
Kim Gardner 490a5d3263 Merge branch 'master' into 142993479-tags 2017-06-08 11:56:14 -04:00
Kim Gardner 1c23f72807 Merge pull request #666 from coralproject/bump-version-1-9-1
Bump version 1.9.1
2017-06-08 11:55:08 -04:00
Kim Gardner 8c1eaf09bc Bump version 1.9.1 2017-06-08 11:51:16 -04:00
Kim Gardner 7e5adcd477 Merge branch 'master' into 142993479-tags 2017-06-08 11:50:18 -04:00
Belen Curcio 283c13fae8 Ádding typenames to update the store 2017-06-08 12:43:02 -03:00
Wyatt Johnson 6e82c180f7 Merge pull request #665 from coralproject/turn-on-off-topic
Turn on off-topic plugin by default
2017-06-08 09:42:08 -06:00
Belen Curcio 8d5085b7a3 Merge branch 'master' of github.com:coralproject/talk into 142993479-tags 2017-06-08 12:39:10 -03:00
Belen Curcio ebbbe72d15 Merge branch '142993479-tags' of github.com:coralproject/talk into 142993479-tags 2017-06-08 12:37:50 -03:00
Belen Curcio f6798af74d Lint 2017-06-08 12:37:37 -03:00
Belen Curcio 1f261f5216 Fully working 2017-06-08 12:35:42 -03:00
Kim Gardner 81f3cc808c Turn on off-topic plugin by default 2017-06-08 11:33:06 -04:00
Wyatt Johnson 480bab3c1a changed some text around 2017-06-08 09:12:08 -06:00
Wyatt Johnson 3a285d92a5 Added more warnings 2017-06-08 09:10:53 -06:00
Kim Gardner 177edf8ed8 Merge branch 'master' into port-perm-checks 2017-06-08 11:10:05 -04:00
Kim Gardner 55928dff08 Merge pull request #664 from coralproject/fix-reply-bugs
Fix bugs related to reply box
2017-06-08 11:09:10 -04:00
Belen Curcio 82a72e506f Off-topic and Staff working 2017-06-08 12:04:33 -03:00
Kim Gardner 9695db0678 Merge branch 'master' into fix-reply-bugs 2017-06-08 11:04:04 -04:00
Kim Gardner f3dcfd23f3 Merge branch 'master' into 142993479-tags 2017-06-08 11:01:57 -04:00
Kim Gardner 655e53a35c Merge pull request #662 from coralproject/bump-version
Update version number
2017-06-08 11:01:09 -04:00
Belen Curcio 15b5dc2236 Linting 2017-06-08 11:58:38 -03:00
Belen Curcio 38077cf3cf Missing fragment 2017-06-08 11:56:38 -03:00
Belen Curcio ef43ba3a35 new merge conflicts to master 2017-06-08 11:55:06 -03:00
Chi Vinh Le d61ede2396 Fix bugs related to reply box 2017-06-08 18:44:07 +07:00
Chi Vinh Le fb39446c6a Port perm checks to the new framework 2017-06-08 18:28:46 +07:00
Wyatt Johnson 5d28f611b2 support for targeting new targets 2017-06-07 20:26:48 -06:00
Kim Gardner a40dd04c1b Update version number 2017-06-07 19:10:13 -04:00
Riley Davis 06da4d7d26 toggle story search 2017-06-07 16:36:27 -06:00
Riley Davis 9818b851db clean up the ModerationHeader component 2017-06-07 15:40:36 -06:00
Kim Gardner 9d25a4c14a Merge pull request #661 from coralproject/comment-stream-cleanup
Refinement of design for banned and suspend user messaging on comment stream.
2017-06-07 17:12:25 -04:00
Kim Gardner 801d0a92af Merge branch 'master' into comment-stream-cleanup 2017-06-07 17:07:39 -04:00
David Erwin 8bf86f34ee Merge pull request #654 from coralproject/off-topic-features
Off Topic Features
2017-06-07 15:43:03 -04:00
Belen Curcio 63a35cbceb Merge issues 2017-06-07 16:38:36 -03:00
Kim Gardner 14a35033da Merge branch 'master' into comment-stream-cleanup 2017-06-07 14:33:29 -04:00
Chi Vinh Le 05da69d097 Remove falsely commited css styles 2017-06-08 01:23:22 +07:00
Chi Vinh Le ffce78ef60 Move Icon and translation service to plugin-api 2017-06-08 01:18:02 +07:00
Chi Vinh Le 6561f733b7 Use correct prefix 2017-06-08 01:17:36 +07:00
Chi Vinh Le b9758ba942 Fix overlap not clickable 2017-06-08 00:59:44 +07:00
Chi Vinh Le 4e56406c37 Fix tabsize in css 2017-06-08 00:49:18 +07:00
Chi Vinh Le 06858bf071 Move Slot and withReaction to beta 2017-06-08 00:42:21 +07:00
Chi Vinh Le 66e4f02a1a Selectors, resolving merge errors, better class names, removing comment reducer 2017-06-08 00:29:05 +07:00
Wyatt Johnson 4cbc8b2226 Updates to setup process 2017-06-07 11:08:39 -06:00
IAmSamHankins c3d76c6a4f messaging style updates, and update to translation files. 2017-06-07 12:55:40 -04:00
Belen Curcio c8534a91a6 Merge branch 'off-topic-features' of github.com:coralproject/talk into off-topic-features 2017-06-07 13:53:54 -03:00
Belen Curcio 87ddfea689 indexes css 2017-06-07 13:53:33 -03:00
Wyatt Johnson 6827ff2f1f Added support for version enforcing 2017-06-07 10:27:42 -06:00
Belén Curcio 830441c531 Merge branch 'master' into off-topic-features 2017-06-07 13:24:58 -03:00
Riley Davis 7417d51973 Merge pull request #660 from coralproject/math-is-hard
percents have to be multiplied by 100
2017-06-07 10:22:45 -06:00
Belen Curcio 847a30ff92 Adding correct classNames 2017-06-07 13:19:36 -03:00
Riley Davis 9f4df35c3a percents have to be multiplied by 100 2017-06-07 10:15:50 -06:00
Belen Curcio f6ad9c9373 Adding camelcase for plugin reducers 🎉 <3 2017-06-07 13:14:52 -03:00
Belen Curcio 8872248d63 Addiing Stable API Architecture 2017-06-07 13:10:17 -03:00
Belen Curcio d780eea357 Merge branch 'off-topic-features' of github.com:coralproject/talk into off-topic-features 2017-06-07 12:38:31 -03:00
Belen Curcio 20aba92ad2 Linting 2017-06-07 12:37:59 -03:00
Chi Vinh Le 7a2fd95eca Merge branch 'master' into off-topic-features
Conflicts:
	client/coral-embed-stream/src/components/Comment.css
	client/coral-embed-stream/src/components/Comment.js
	client/coral-embed-stream/src/components/Stream.js
	client/coral-embed-stream/src/reducers/index.js
	client/coral-framework/helpers/plugins.js
2017-06-07 22:37:06 +07:00
Belen Curcio d7dfae89a3 Global classNames, and more 2017-06-07 12:05:53 -03:00
David Erwin fa33fe2ed2 Merge pull request #657 from coralproject/better-refetch-detection
Better refetch detection
2017-06-07 09:29:19 -04:00
Belen Curcio 456eedfd31 Clearing tags and naming convention 2017-06-07 10:04:52 -03:00
Belen Curcio 4725f07757 Viewing Options state and name conventions 2017-06-07 09:53:53 -03:00
Belen Curcio e24f6ca9f1 Renaming to commentClassNames 2017-06-07 09:28:12 -03:00
Chi Vinh Le ceb99cf205 Better refetch detection 2017-06-07 17:45:33 +07:00
Kiwi e47d3b085e Merge pull request #656 from coralproject/service-fix
Resolve facebook login issue
2017-06-07 16:57:19 +07:00
Belén Curcio 5e1fcc48e1 Merge branch 'master' into service-fix 2017-06-06 20:45:06 -03:00
Belen Curcio b5c9e92cc6 Adding streamBox slot and coral-plugin-viewing-options 🎉 2017-06-06 20:42:42 -03:00
Wyatt Johnson cf82f5b707 fixed enum 2017-06-06 17:32:52 -06:00
Wyatt Johnson 1e904584e7 Added model migrations 2017-06-06 17:31:52 -06:00
Wyatt Johnson 71ff1c31f8 moved plugin to lazy load 2017-06-06 16:49:51 -06:00
IAmSamHankins cc76517ba8 minor copy changes in Configure tab on stream, and style updates to banned user message on stream 2017-06-06 17:37:14 -04:00
Belen Curcio 6f909c3451 Containers and Components structure 2017-06-06 18:21:03 -03:00
Belen Curcio 2202c8ae5b Fully working filter 2017-06-06 18:08:13 -03:00
Kim Gardner 6d8133b1b5 Merge pull request #655 from coralproject/move-config
move edit comment config
2017-06-06 16:57:48 -04:00
Riley Davis 2384ae4baa move edit comment config 2017-06-06 14:49:07 -06:00
Belen Curcio 1c7ad9a8ac Plugin reducers working again with reducer name 2017-06-06 17:29:56 -03:00
Belen Curcio c3360aa020 Plugin reducers working again 2017-06-06 17:23:35 -03:00
Belen Curcio 4aa7900769 Not connecting comment Component to the Store 2017-06-06 17:09:13 -03:00
Riley Davis 0dc61f33f5 Merge pull request #646 from coralproject/bulk-actions
bulk operations on User Detail comments
2017-06-06 13:26:28 -06:00
Riley Davis 8af0d929c2 Merge branch 'master' into bulk-actions 2017-06-06 13:15:04 -06:00
Riley Davis 4e19d5eecf don't have functions re-created every render 2017-06-06 13:11:31 -06:00
Riley Davis 5465563856 reload and clear selections 2017-06-06 12:52:26 -06:00
Belen Curcio b5a64bd2f9 Filter State 2017-06-06 15:45:56 -03:00
Kim Gardner b02782d532 Merge pull request #653 from coralproject/support-wss
Use wss when connection is secure
2017-06-06 14:39:35 -04:00
Chi Vinh Le 7cb05cebef Use wss when connection is secure 2017-06-07 01:32:37 +07:00
Kim Gardner 80d4ffa5bf Merge branch 'master' into bulk-actions 2017-06-06 14:13:47 -04:00
Riley Davis 9963f6d337 Merge pull request #651 from ebirving/active-tab-classnames
add semantic classname to active tab
2017-06-06 12:11:44 -06:00
Kim Gardner eff923424c Merge branch 'master' into bulk-actions 2017-06-06 14:11:16 -04:00
Erica Irving c3e32f37dc add coral- prefix as requested 2017-06-06 13:43:45 -04:00
Riley Davis 18f11a84cf Merge branch 'master' into active-tab-classnames 2017-06-06 11:14:47 -06:00
Riley Davis b0bdfff785 Merge pull request #649 from ebirving/respect-plugin-styles
add classnames to RespectButton component
2017-06-06 11:14:32 -06:00
Riley Davis dc55febac8 Merge branch 'master' into respect-plugin-styles 2017-06-06 11:09:36 -06:00
David Erwin 4d6e02228c Merge pull request #643 from coralproject/subscribe-to-comments
Subscribe to comment updates
2017-06-06 13:00:58 -04:00
Chi Vinh Le c50fe9f91d Merge branch 'master' into subscribe-to-comments
Conflicts:
	client/coral-embed-stream/src/components/Comment.js
2017-06-06 23:44:27 +07:00
Riley Davis 7d5a73bc95 Merge branch 'master' into respect-plugin-styles 2017-06-06 10:40:33 -06:00
Riley Davis 5f3220dba9 Merge pull request #652 from coralproject/css-override-classes
Css override classes + tab bugfix
2017-06-06 10:40:21 -06:00
Kim Gardner 700ab38cb0 Merge branch 'master' into css-override-classes 2017-06-06 12:34:13 -04:00
Riley Davis 6fcd003e76 remove console.log 2017-06-06 10:32:14 -06:00
Kiwi 0069d17672 Merge branch 'master' into subscribe-to-comments 2017-06-06 23:19:22 +07:00
Chi Vinh Le afdcdff51e Handle ignored user case 2017-06-06 23:18:37 +07:00
Erica Irving 3d8478dfb2 add semantic classname to active tab 2017-06-06 12:17:32 -04:00
Kim Gardner 71d2f44d38 Merge pull request #650 from coralproject/copy-updates
Copy changes
2017-06-06 12:07:40 -04:00
Kim Gardner 124ea19b52 Update en.yml 2017-06-06 12:03:12 -04:00
Erica Irving 512a463589 add classnames to RespectButton component 2017-06-06 11:28:28 -04:00
Belen Curcio e9cb61a534 Arrow for filtering 2017-06-06 12:13:26 -03:00
Belen Curcio a6db60096c Fully working, needs docs 2017-06-06 11:58:51 -03:00
Belen Curcio 9364272ffc Ádd classnames based on tags 2017-06-06 11:53:19 -03:00
Chi Vinh Le 1ceb72ca5c Fix error with tombstones 2017-06-06 21:26:43 +07:00
Belen Curcio 9e001f5ce9 addClassNames, removeClassNames, reducers and actions for Comment Component 2017-06-06 11:15:35 -03:00
Chi Vinh Le 0fcdd09f9f Lazily create apollo client 2017-06-06 20:50:07 +07:00
Belen Curcio 3ad3b3af1c Adding filters 2017-06-06 09:43:34 -03:00
Chi Vinh Le d3f7e6210c Simplify transition code 2017-06-06 19:06:42 +07:00
Chi Vinh Le 447ec7e524 Deeply pass commentIsIgnored 2017-06-06 19:05:18 +07:00
Chi Vinh Le f8ca14d3d3 Reset stream after refetching 2017-06-06 18:45:45 +07:00
Belen Curcio 5faa92e8b3 Label style, top level comments, no offtopic replies 2017-06-06 08:09:34 -03:00
Kim Gardner 17783c91c4 Merge branch 'master' into bulk-actions 2017-06-05 23:06:03 -04:00
Riley Davis ad4c4a0a5b fix for active tab not showing. user box classNames 2017-06-05 16:46:58 -06:00
Wyatt Johnson 7956c193c5 Merge branch 'master' into subscribe-to-comments 2017-06-05 16:38:28 -06:00
Wyatt Johnson 2aa3b7a5a0 Merge pull request #641 from coralproject/safari-cookie-time
Use same lifespan for cookies as for tokens
2017-06-05 16:38:13 -06:00
Wyatt Johnson 2f77842072 Merge branch 'master' into safari-cookie-time 2017-06-05 16:32:29 -06:00
Wyatt Johnson a595e94394 Removed parse-duration in favour of ms 2017-06-05 16:31:06 -06:00
riley d03647067d Merge branch 'master' into css-override-classes 2017-06-05 16:19:46 -06:00
Riley Davis fb537c9990 Merge pull request #648 from coralproject/StephanieDClark-master
Stephanie's changes to add classNames to Tab and TabBar
2017-06-05 16:18:19 -06:00
Riley Davis 8ea7ba9b69 Merge branch 'master' into css-override-classes 2017-06-05 16:13:45 -06:00
David Erwin 15297cd674 Update css naming convention 2017-06-05 18:12:38 -04:00
Riley Davis e46802910c Merge branch 'master' into StephanieDClark-master 2017-06-05 16:09:12 -06:00
riley 19b90910ba better tab classNames 2017-06-05 16:08:53 -06:00
David Erwin 291cee3af4 Merge pull request #647 from coralproject/auth-token-hooks
Added new tokenUserNotFound plugin hook
2017-06-05 18:05:56 -04:00
Wyatt Johnson 069c4c5e50 Merge branch 'master' into auth-token-hooks 2017-06-05 16:00:23 -06:00
Riley Davis 7d54fb60a8 use existing className attr instead of custom property 2017-06-05 15:57:34 -06:00
Wyatt Johnson e8143aea14 Added new tokenUserNotFound plugin hook 2017-06-05 15:52:32 -06:00
StephanieDClark 9e1d010db3 Adjusting tab classes 2017-06-05 14:48:50 -07:00
David Erwin 728d523b9e Begin adding css override classes 2017-06-05 17:25:27 -04:00
StephanieDClark 5838f5d486 fix lint errors 2017-06-05 14:00:21 -07:00
Riley Davis 1c8e1cc46c Merge branch 'master' into bulk-actions 2017-06-05 14:42:07 -06:00
Riley Davis c4a41af33b clear selected ids when panel is closed 2017-06-05 14:40:03 -06:00
StephanieDClark 5e463c00fd Merge branch 'master' into master 2017-06-05 13:35:36 -07:00
Kim Gardner 933a9371f3 Merge branch 'master' into safari-cookie-time 2017-06-05 16:35:23 -04:00
Kim Gardner 0be77fa5fd Merge pull request #645 from coralproject/fix-typos
Fix typos
2017-06-05 16:28:08 -04:00
Kim Gardner 49eecaa65b Fix typos 2017-06-05 16:22:17 -04:00
Riley Davis 39d8a5d01b bulk operations on User Detail comments 2017-06-05 14:20:34 -06:00
StephanieDClark 414e5d78bd Fix yarn lint error 2017-06-05 12:58:04 -07:00
Kiwi e9a6c3dfe3 Merge branch 'master' into subscribe-to-comments 2017-06-06 02:42:35 +07:00
StephanieDClark 3b7603ee31 add supplied classes to tabbar 2017-06-05 12:39:35 -07:00
StephanieDClark 8c89296007 add supplied classes to tab 2017-06-05 12:38:36 -07:00
StephanieDClark 32fe291847 Add classes to tabbar, tab 2017-06-05 12:37:13 -07:00
Chi Vinh Le 056ddc7010 Meaningful comment 2017-06-06 02:25:42 +07:00
Chi Vinh Le 2d0eec15c1 Readd falsely deleted part 2017-06-06 02:25:32 +07:00
Chi Vinh Le 2be73d1df2 Unsubscribe on unmount 2017-06-06 02:10:56 +07:00
Chi Vinh Le 3ad17f008f Refactor fetchMore updaters 2017-06-06 01:44:17 +07:00
David Erwin 547547d04c Merge pull request #638 from coralproject/auth-token-via-config
Use config auth_token if present on http headers
2017-06-05 14:22:48 -04:00
David Erwin 90d7e3706c Improve auth token logic. 2017-06-05 13:58:13 -04:00
Chi Vinh Le 9993350b7e Animate flash for new comments 2017-06-06 00:54:12 +07:00
David Erwin 7c66d846c2 Add auth token comment 2017-06-05 13:38:06 -04:00
Chi Vinh Le 6b0cdae183 Subscribe to comments 2017-06-05 20:51:08 +07:00
Chi Vinh Le c20df65ebc Use same lifespan for cookies as for tokens 2017-06-05 18:21:40 +07:00
Kiwi 7efdccb683 Merge branch 'master' into auth-token-via-config 2017-06-05 17:57:13 +07:00
Chi Vinh Le a9796d47cd Check login after loading config 2017-06-05 17:51:06 +07:00
Chi Vinh Le 7c145bbb3c Refactor import order and framework initialization 2017-06-05 17:49:43 +07:00
Chi Vinh Le ac9e70bc67 Add same getAuthToken logic to apollo transport 2017-06-05 17:48:09 +07:00
Chi Vinh Le defd28b20f Remove double login checks 2017-06-05 17:46:57 +07:00
Chi Vinh Le b05b54a991 Add auth_token default config 2017-06-05 17:46:30 +07:00
Kim Gardner 3c96b7eb1e Merge pull request #640 from coralproject/user-comment-history
user detail design pass
2017-06-02 17:27:13 -04:00
Kim Gardner 6e483842f1 Merge branch 'master' into user-comment-history 2017-06-02 17:22:48 -04:00
Kim Gardner 0276c00828 Merge pull request #639 from coralproject/key-trans-missing
Key translation missing
2017-06-02 17:08:48 -04:00
gaba c6efc586ae Merge branch 'master' into key-trans-missing 2017-06-02 14:03:33 -07:00
gaba 6de9c69668 Missing key translation. 2017-06-02 14:02:45 -07:00
Kim Gardner 396abaae22 Merge pull request #637 from coralproject/i18n-emails
I18n emails
2017-06-02 16:59:32 -04:00
gaba e98b9ff9bc Some translations missing and a folder that should not be there. 2017-06-02 13:50:48 -07:00
Gabriela Rodríguez Berón cbf83206b8 Merge branch 'master' into i18n-emails 2017-06-02 13:35:09 -07:00
Kim Gardner 664ace170b Merge pull request #618 from coralproject/user-comment-history
show comments on the UserDetail drawer
2017-06-02 16:10:22 -04:00
Kim Gardner bd60eca6f9 Merge branch 'master' into user-comment-history 2017-06-02 15:58:54 -04:00
Riley Davis e097b8eb21 design pass 2017-06-02 13:50:00 -06:00
Kim Gardner 058cab970b Merge pull request #636 from coralproject/story-146187883
UX Updates
2017-06-02 15:25:40 -04:00
Riley Davis 22c2843ae5 remove moderate link per designs 2017-06-02 13:12:59 -06:00
Riley Davis d8cb560755 add a tab for All/Rejected user detail comments 2017-06-02 13:02:22 -06:00
David Erwin ea3d8e141d Use config auth_token if present on http headers 2017-06-02 14:22:06 -04:00
gaba cf50a0e85c Translations missing. 2017-06-02 08:30:36 -07:00
gaba b9b62e5764 Use accepts library. 2017-06-02 08:24:21 -07:00
gaba eadbb03c15 Fix typo. 2017-06-02 06:54:01 -07:00
gaba b6dbb5975f Adds missing translations. 2017-06-01 17:26:49 -07:00
gaba fd1cf8121e Move start to the left. 2017-06-01 17:21:55 -07:00
gaba dcbd0fc167 Change signed in for logged in. 2017-06-01 17:13:55 -07:00
gaba 44d82f8b13 Merge branch 'master' into i18n-emails 2017-06-01 16:26:44 -07:00
gaba 27e6372fe2 Get language from the header. 2017-06-01 16:24:15 -07:00
gaba 686dc96c5f An attempt to guess language. In process. 2017-06-01 15:44:44 -07:00
Riley Davis 8d25b766ff Merge branch 'master' into user-comment-history 2017-06-01 15:47:54 -06:00
Riley Davis 555d54425a implement kiwi's pagination changes 2017-06-01 15:34:05 -06:00
Kim Gardner f3f7fad68f Merge pull request #635 from StephanieDClark/master
Add isReply to commentInputDetailArea api
2017-06-01 14:38:36 -04:00
Riley Davis 9483922781 Merge branch 'master' into master 2017-06-01 12:31:48 -06:00
Kim Gardner 91d415b434 Merge pull request #632 from coralproject/losowsky-patch-2
Updated description
2017-06-01 14:25:55 -04:00
Kim Gardner 1943b38544 Merge branch 'master' into losowsky-patch-2 2017-06-01 14:20:45 -04:00
Kim Gardner 9344490514 Merge pull request #631 from coralproject/losowsky-patch-1
Added iPhone 7
2017-06-01 14:20:28 -04:00
StephanieDClark 1bf2053cb8 Add isReply to commentInputDetailArea api 2017-06-01 11:01:57 -07:00
Andrew Losowsky 9fe7b961ca Updated description
Added mention of Plugins
2017-06-01 13:43:30 -04:00
Andrew Losowsky ac0db69c27 Added iPhone 7
What about the Android devices? It says Galaxy S5 but that's up to S7, same with Nexus.
2017-06-01 13:41:08 -04:00
Riley Davis d981b5a85b Merge branch 'master' into user-comment-history 2017-06-01 10:52:24 -06:00
Kim Gardner 1fe6c79d88 Merge pull request #627 from coralproject/bump-version-1-8-0
Bump version in prep for 1.8.0 release
2017-06-01 12:42:31 -04:00
Kim Gardner 8b3c1b275d Merge branch 'master' into bump-version-1-8-0 2017-06-01 12:24:34 -04:00
Kim Gardner c76e71ce31 Merge pull request #629 from coralproject/dec-inc-total-comment-count
Increase/Decrease totalCommentCount after mutation
2017-06-01 11:59:11 -04:00
Kim Gardner b8015506df Merge branch 'master' into dec-inc-total-comment-count 2017-06-01 11:54:49 -04:00
Kim Gardner 06e6730f04 Merge pull request #628 from coralproject/fix-translation-comment-count
Fix translation bug in CommentCount
2017-06-01 11:54:01 -04:00
Chi Vinh Le fa08450185 Increase/Decrease totalCommentCount after mutation 2017-06-01 22:39:05 +07:00
Chi Vinh Le d9acbbad96 Fix translation bug in CommentCount 2017-06-01 22:37:25 +07:00
David Erwin 4951527883 Merge pull request #626 from coralproject/pagination-refactor
Pagination refactor
2017-06-01 10:24:26 -04:00
Chi Vinh Le 33eff78a64 Always request comment id 2017-06-01 19:49:53 +07:00
Chi Vinh Le 5245755d49 Add some comments 2017-06-01 19:41:08 +07:00
Chi Vinh Le ebdd586d1e Fix mutation funkiness on permlink view 2017-06-01 18:47:07 +07:00
Chi Vinh Le 9a2b6b3ae1 Merge branch 'master' into pagination-refactor 2017-06-01 17:15:42 +07:00
gaba 276b2c7591 Merge branch 'master' into i18n-emails 2017-05-31 15:31:14 -07:00
gaba a126c94aa4 Adds service for translations in the backend. Clean yaml. 2017-05-31 13:55:27 -07:00
Kim Gardner 9bd75a5670 Bump version in prep for 1.8.0 release 2017-05-31 16:55:14 -04:00
Riley Davis 576cbed8e9 no more errors 2017-05-31 13:44:38 -06:00
Kim Gardner 4ce0431fba Merge pull request #623 from coralproject/add-trust-to-readme
Add trust to readme
2017-05-31 15:43:50 -04:00
Kim Gardner 93d8e0015b Remove space 2017-05-31 15:29:51 -04:00
Kim Gardner ac8e723e75 Copy changes 2017-05-31 15:29:50 -04:00
Kim Gardner c172260d74 Fix link 2017-05-31 15:29:49 -04:00
Kim Gardner 2a3941552d Update README with Trust info 2017-05-31 15:29:41 -04:00
Kim Gardner bf15354aa2 Merge branch 'master' into user-comment-history 2017-05-31 15:17:00 -04:00
Kim Gardner dd9e3247d2 Merge pull request #622 from coralproject/hide-comment-box-perm
Hide comment box during perm view
2017-05-31 15:12:41 -04:00
Chi Vinh Le b402f70f38 Hide comment box during perm view 2017-06-01 01:34:32 +07:00
Chi Vinh Le 30d4562383 Refactor pagination 2017-06-01 01:18:41 +07:00
riley 6e458e0149 resolve missing files 2017-05-31 10:54:34 -06:00
Riley Davis 259ce9c9fb merge master 2017-05-31 10:36:01 -06:00
Kim Gardner 93be1a91be Merge pull request #608 from coralproject/integrate-admin-graphql-framework
Integrate admin into GraphQL framework
2017-05-30 18:04:10 -04:00
Riley Davis 6599ed2b4c Merge branch 'master' into user-comment-history 2017-05-30 15:53:36 -06:00
Riley Davis 29753a958b show comments on the UserDetail drawer 2017-05-30 15:50:56 -06:00
Chi Vinh Le 109f4b2c0f Handle mutation errors 2017-05-31 00:44:16 +07:00
Kiwi 4f8ef88bd0 Merge branch 'master' into integrate-admin-graphql-framework 2017-05-31 00:24:06 +07:00
Kiwi 3160f6ae9e Merge pull request #604 from coralproject/graphql-errors-handling
Support response errors in the graphql framework
2017-05-31 00:21:14 +07:00
Kiwi 15a11dca4d Merge branch 'master' into graphql-errors-handling 2017-05-30 20:48:58 +07:00
Kiwi d28ba0028d Merge branch 'master' into integrate-admin-graphql-framework 2017-05-30 20:48:53 +07:00
Kiwi f42dd884c0 Merge pull request #617 from coralproject/forget-password-ports
Allow domains with ports
2017-05-30 20:48:42 +07:00
Kiwi 4a0785f6fe Merge branch 'master' into graphql-errors-handling 2017-05-30 20:18:32 +07:00
Kiwi 336e0373cd Merge branch 'master' into integrate-admin-graphql-framework 2017-05-30 20:16:09 +07:00
David Erwin bbed3e2a7f Merge branch 'master' into forget-password-ports 2017-05-30 08:56:07 -04:00
David Erwin c894e8eb1d Merge pull request #616 from coralproject/fix-redirect-uri
Use correct redirect url
2017-05-30 08:55:44 -04:00
Chi Vinh Le 68f6c8acb6 No shared store between popup and embed 2017-05-30 01:08:45 +07:00
Chi Vinh Le 1acf58182f Merge branch 'master' into integrate-admin-graphql-framework
Conflicts:
	client/coral-admin/src/containers/Community/Loading.js
	client/coral-admin/src/containers/Community/Table.js
	client/coral-admin/src/containers/Streams/Stories.js
	client/coral-admin/src/index.js
	client/coral-admin/src/routes/Community/components/FlaggedAccounts.js
	client/coral-admin/src/routes/Community/components/People.js
	client/coral-admin/src/routes/Configure/components/Configure.js
	client/coral-admin/src/routes/Configure/components/Domainlist.js
	client/coral-admin/src/routes/Configure/components/EmbedLink.js
	client/coral-admin/src/routes/Configure/components/ModerationSettings.js
	client/coral-admin/src/routes/Configure/components/StreamSettings.js
	client/coral-admin/src/routes/Configure/components/TechSettings.js
	client/coral-admin/src/routes/Configure/components/Wordlist.js
	client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js
	client/coral-admin/src/routes/Install/components/Steps/AddOrganizationName.js
	client/coral-admin/src/routes/Install/components/Steps/CreateYourAccount.js
	client/coral-admin/src/routes/Install/components/Steps/FinalStep.js
	client/coral-admin/src/routes/Install/components/Steps/InitialStep.js
	client/coral-admin/src/routes/Install/components/Steps/PermittedDomainsStep.js
	client/coral-admin/src/routes/Install/components/translations.json
	client/coral-admin/src/routes/Moderation/components/Moderation.js
	client/coral-admin/src/routes/Moderation/components/ModerationQueue.js
	client/coral-admin/src/routes/Stories/components/Stories.js
	client/coral-embed-stream/src/index.js
2017-05-29 23:06:40 +07:00
Chi Vinh Le 04ef1c5be5 Use new translation service 2017-05-29 22:45:56 +07:00
Chi Vinh Le a598bca12b Allow domains with ports 2017-05-29 22:10:16 +07:00
Kiwi 810f337d8d Merge branch 'master' into fix-redirect-uri 2017-05-29 22:06:33 +07:00
Kiwi c314054143 Merge branch 'master' into graphql-errors-handling 2017-05-29 22:00:49 +07:00
Chi Vinh Le bd5e272f00 Use correct redirect url 2017-05-29 22:00:27 +07:00
Kiwi bdbc7c5ff2 Merge pull request #586 from coralproject/i18n-refactor
Refactor i18n
2017-05-29 21:59:52 +07:00
Chi Vinh Le 2d398fe452 Change not authorized error message 2017-05-29 20:36:21 +07:00
Chi Vinh Le 0b7432b75e Merge branch 'master' into i18n-refactor
Conflicts:
	client/coral-sign-in/components/FakeComment.js
	client/coral-sign-in/components/UserBox.js
2017-05-29 20:23:27 +07:00
Chi Vinh Le 9a6532ecea Merge branch 'master' into integrate-admin-graphql-framework
Conflicts:
	client/coral-embed-stream/src/index.js
2017-05-29 20:09:03 +07:00
Kiwi f3f6633e99 Merge branch 'master' into graphql-errors-handling 2017-05-29 19:42:26 +07:00
David Erwin 93f8a81678 Merge pull request #607 from coralproject/unified-docs
Unified docs
2017-05-27 09:59:08 -04:00
Kim Gardner b13d3adce0 Merge branch 'master' into unified-docs 2017-05-26 17:00:53 -04:00
Kim Gardner d213a4a26e Merge pull request #613 from coralproject/minor-stream-style-changes
Stream and mod queue style fixes
2017-05-26 17:00:09 -04:00
Kim Gardner 82d442d95b Merge branch 'master' into minor-stream-style-changes 2017-05-26 16:53:55 -04:00
Kim Gardner 1dcd254519 Merge branch 'master' into unified-docs 2017-05-26 16:53:36 -04:00
Kim Gardner 37262ad9f6 Merge branch 'master' into graphql-errors-handling 2017-05-26 16:53:13 -04:00
IAmSamHankins 05e5200e79 moderation panel design cleanup PT 144120491 2017-05-26 12:48:15 -04:00
David Erwin 69a9afe084 Remove old docs 2017-05-26 11:40:56 -04:00
David Erwin 1d3a75e425 Update doc menu title 2017-05-26 11:39:24 -04:00
David Erwin 5f83584e48 Update tooling and client architecture docs 2017-05-26 11:37:33 -04:00
David Erwin 1dbecab13e Add swagger user docs and tools page 2017-05-26 11:27:10 -04:00
David Erwin deea24d0c5 Add docs to .eslintignore 2017-05-26 11:12:16 -04:00
David Erwin ce46329e27 Create FAQ page 2017-05-26 11:07:10 -04:00
IAmSamHankins 32715d3903 initial style updates on comment stream for PT 142803055 ~ copy translations still needed 2017-05-25 17:21:32 -04:00
Chi Vinh Le a39fcee3f8 More error handling fixes 2017-05-26 03:04:06 +07:00
Chi Vinh Le 841735b787 Restore error functionality 2017-05-26 02:55:09 +07:00
David Erwin f5bd2a9db3 Update top nav 2017-05-25 15:17:50 -04:00
Chi Vinh Le 209005aff5 More key misspellings 2017-05-26 02:10:00 +07:00
Chi Vinh Le 0107aa67cd Correct keys and translations 2017-05-26 01:59:34 +07:00
gaba 77bfd55af3 Merge branch 'i18n-refactor' into i18n-emails 2017-05-25 14:35:49 -04:00
gaba 96985b4780 More strings. 2017-05-25 14:05:17 -04:00
gaba 492f4a674c OMG. Strings. 2017-05-25 12:02:26 -04:00
Chi Vinh Le 4fe89257c2 Support store in withMutation 2017-05-25 22:19:16 +07:00
gaba 09bd0a6735 Move templates to a folder for translations. 2017-05-25 10:43:34 -04:00
Chi Vinh Le 246ca117be Little cleanup for admin services 2017-05-25 19:22:59 +07:00
Chi Vinh Le 93e6c0af32 Use default response fragments 2 2017-05-25 18:57:42 +07:00
Chi Vinh Le b43a0f533b Harmonize naming in admin and embed-stream 2017-05-25 18:37:23 +07:00
Chi Vinh Le 512767e023 Use default response fragments 2017-05-25 17:49:30 +07:00
David Erwin 69b19d2c91 Jekyll Initial Commit 2017-05-24 16:48:00 -04:00
gaba 4ff7da864e Still more strings. 2017-05-24 14:54:52 -04:00
Chi Vinh Le 48a8e394ef Use framework for mutations 2017-05-25 01:18:33 +07:00
Chi Vinh Le d9ee215d66 Remove obsolete fragment 2017-05-24 23:22:03 +07:00
gaba 846ac50b19 Clean out the spanish one. 2017-05-24 11:59:22 -04:00
gaba 03f70bcab9 Adds more missing translation. 2017-05-24 11:59:21 -04:00
Chi Vinh Le a3755b7505 Use unique names 2017-05-24 22:24:01 +07:00
Chi Vinh Le 5e2fa72466 Support extendable queries 2017-05-24 22:05:41 +07:00
Chi Vinh Le a9b4de1f5c Wrap queries with hocs 2017-05-24 21:00:17 +07:00
Chi Vinh Le 0231358075 Correctly set roles 2017-05-24 21:00:05 +07:00
Chi Vinh Le c35ce979f5 Move query logic to containers 2017-05-24 19:04:05 +07:00
Chi Vinh Le 3cde313ab4 Pass correct props to install step 2017-05-24 17:38:19 +07:00
Chi Vinh Le 26d6a97b20 Merge branch 'master' into extendable-queries-admin 2017-05-24 17:17:52 +07:00
Chi Vinh Le 8ab7047cfa Refactor admin 2017-05-24 17:09:21 +07:00
Chi Vinh Le db3f91cbcc Change to declarative API for plugins 2017-05-24 16:44:33 +07:00
Chi Vinh Le 1b3fe13e9b Fallback to default language for timeago 2017-05-24 16:33:39 +07:00
gaba 86258ba0c3 One more string to translate. 2017-05-23 16:06:07 -04:00
gaba 8da7df353c Adds a bunch of missing strings. 2017-05-23 16:06:04 -04:00
Chi Vinh Le b2798fd35f Merge branch 'i18n-refactor' of ssh://github.com/coralproject/talk into i18n-refactor 2017-05-24 01:39:09 +07:00
Chi Vinh Le 37b8fd8fe3 Simplify i18n + fallback to default language 2017-05-24 01:38:50 +07:00
gaba 2991af67f4 Adds more translation strings. 2017-05-23 14:03:34 -04:00
David Erwin a1a78334ea First pass docs restructure 2017-05-23 13:58:02 -04:00
Chi Vinh Le 5c66009d84 Change to import 2017-05-24 00:30:34 +07:00
Chi Vinh Le 8ae497366b Move locales to top level 2017-05-24 00:28:47 +07:00
Chi Vinh Le 48bef95a4d Simplify yaml loading 2017-05-24 00:26:55 +07:00
Kim Gardner 0007773b4d Merge branch 'master' into 142993479-tags 2017-05-23 13:02:29 -04:00
gaba adacafb586 Removed extra string. 2017-05-23 12:59:06 -04:00
Kim Gardner ac9e193c45 Merge branch 'master' into graphql-errors-handling 2017-05-23 12:57:53 -04:00
gaba 259b24faaa A few strings where using -. 2017-05-23 12:53:48 -04:00
gaba a184837ff8 Goes back to load all translations... 2017-05-23 12:46:53 -04:00
gaba 5757682732 Removes files that came from the merge and do not exist in master. 2017-05-23 11:57:51 -04:00
Gabriela Rodríguez Berón 6f7b8522c8 Merge branch 'master' into i18n-refactor 2017-05-23 11:47:58 -04:00
riley ec03c6b48c merge master 2017-05-22 13:10:43 -06:00
Chi Vinh Le 947ea66a77 Remove obsolete errors checking 2017-05-22 20:25:24 +07:00
Chi Vinh Le d1307eb5cd Handle response errors in the graphql framework 2017-05-22 20:11:16 +07:00
gaba f525af4c6e Added a few more translations that were missing. 2017-05-19 15:42:56 -07:00
gaba 97e9419f02 Missing translations. 2017-05-19 13:34:56 -07:00
gaba 67fb829717 A few more issues from the merge. 2017-05-19 13:29:23 -07:00
gaba 625362d51e Merge branch 'master' into i18n-refactor 2017-05-19 12:44:34 -07:00
gaba 9be443eda8 Moves i18n into coral-framework. 2017-05-19 12:28:44 -07:00
Wyatt Johnson 8f219c47a7 Tag server impl 2017-05-19 12:23:53 -06:00
gaba 88b5a4169d Remove promise 2017-05-19 10:48:47 -07:00
gaba 8dddff25f9 Update es translation and add link to transifex. 2017-05-18 16:01:12 -07:00
gaba 12cb59ce30 Merge branch 'master' into i18n-refactor 2017-05-18 15:44:14 -07:00
gaba 1a7ad410f5 Loads timeago translation when loading translations. 2017-05-18 15:38:53 -07:00
gaba 3ca22bd101 Fix translation keys. 2017-05-18 15:14:37 -07:00
Belen Curcio 132b0a854d linting 2017-05-18 00:22:39 -03:00
Belen Curcio 17e7b28688 Fully working best tag 2017-05-18 00:21:08 -03:00
Belen Curcio b795368674 Merge branch '142993479-tags' of github.com:coralproject/talk into 142993479-tags 2017-05-17 23:54:38 -03:00
Belen Curcio f57fa7cb7b Update Cache 2017-05-17 23:53:07 -03:00
gaba 9e954f0ae3 Fix timeago with lang. 2017-05-17 18:05:41 -07:00
gaba 8d492ca4e9 Adds a few missing keys. 2017-05-17 13:18:04 -07:00
gaba 3aceb0dc38 Merge branch 'master' into i18n-refactor 2017-05-17 12:59:24 -07:00
gaba 5bd9bc992b Move i18n into a service 2017-05-17 12:53:36 -07:00
Wyatt Johnson 7a256bdd2b Merge branch 'master' into 142993479-tags 2017-05-17 10:01:58 -06:00
Belen Curcio 6664f73d2d Best tag working, only Optimistic Missing 2017-05-17 11:57:51 -03:00
Belen Curcio fb0e89485c Merge branch '142993479-tags' of github.com:coralproject/talk into 142993479-tags 2017-05-17 11:20:00 -03:00
Belen Curcio b2f1707f17 Fully working offtopic 2017-05-17 11:19:06 -03:00
Wyatt Johnson 9db77360b4 removed unused errors 2017-05-17 08:16:48 -06:00
Belen Curcio 9dc0693530 Linting 2017-05-17 09:43:04 -03:00
Belen Curcio 54eb078a13 Working offtopic tag 2017-05-17 09:37:26 -03:00
Belen Curcio 793f634959 Working tags 2017-05-17 09:01:41 -03:00
gaba 35c941d4d0 Add missing translations from commentbox. 2017-05-15 16:17:53 -07:00
gaba 61856c3cf6 Use timeago. 2017-05-15 16:04:10 -07:00
gaba c0c6dd6252 The merge got back some code to the coral-framework. 2017-05-15 14:23:56 -07:00
Belen Curcio c59346eb0a tags, and assignedby nul 2017-05-15 18:13:45 -03:00
Belen Curcio d02412a734 Merge branch '142993479-tags' of github.com:coralproject/talk into 142993479-tags 2017-05-15 18:11:30 -03:00
Belen Curcio c3d02a14a0 Updated queries and hasRoles 2017-05-15 18:11:14 -03:00
gaba 423cb3cade Merge branch 'master' into i18n-refactor 2017-05-15 14:02:34 -07:00
gaba 54e103b55a Change a few translations that had wrong key. 2017-05-15 13:44:46 -07:00
gaba 0ead2cb610 Adds sign in translations. 2017-05-15 13:25:09 -07:00
Belén Curcio 820ffb14c1 Merge branch 'master' into 142993479-tags 2017-05-15 16:55:47 -03:00
gaba f32eac4eab Lint YAML and notations 2017-05-15 12:53:28 -07:00
Belén Curcio 3eea5e1a8a Merge branch 'master' into 142993479-tags 2017-05-15 14:53:54 -03:00
gaba b31212f847 Second run of notation change for translations. 2017-05-15 08:35:55 -07:00
gaba 633086cfa3 Fix a few more strings and translations. 2017-05-15 07:11:11 -07:00
David Erwin ff6a9cf0d5 Merge branch 'master' into 142993479-tags 2017-05-12 15:44:38 -04:00
David Erwin b60ed166d0 Allow MODERATORs to access tags 2017-05-12 15:38:38 -04:00
Wyatt Johnson 0a8bfe8a38 Merge branch 'master' into 142993479-tags 2017-05-12 07:54:46 -06:00
Wyatt Johnson e6b2ff19b8 fixing graph merge error 2017-05-12 07:51:53 -06:00
Wyatt Johnson e2f6d5989a Merge branch 'master' into 142993479-tags 2017-05-12 07:49:02 -06:00
gaba 3b80ee8b13 Add YAML loader. Fix i18n module to get YAML files. 2017-05-11 15:05:09 -07:00
Wyatt Johnson 0d957bcfb9 Code cleanup related to old tag services 2017-05-11 14:56:36 -06:00
Wyatt Johnson 899805b956 Merge branch '142993479-tags' of github.com:coralproject/talk into 142993479-tags 2017-05-11 14:42:37 -06:00
Wyatt Johnson aeaf809a0f Made queries more generic related to tags 2017-05-11 14:42:06 -06:00
gaba 653091fdf9 Merge branch 'master' into i18n-refactor 2017-05-11 09:02:21 -07:00
Gabriela Rodríguez Berón d815d9dc02 Merge branch 'master' into 142993479-tags 2017-05-11 08:30:51 -07:00
Gabriela Rodríguez Berón f1e4f98055 Merge branch 'master' into 142993479-tags 2017-05-10 08:50:28 -07:00
Wyatt Johnson 0d47ee895c removed unused service 2017-05-09 15:02:31 -06:00
Wyatt Johnson 2e1bc8d75a Adjusted query/tag flow 2017-05-09 15:01:01 -06:00
Gabriela Rodríguez Berón b8e530ca1f Merge branch 'master' into 142993479-tags 2017-05-08 21:08:28 -07:00
gaba da0cebbda7 Fix the plugin TagLabel to show the staff tag. 2017-05-08 17:06:49 -07:00
gaba 7c09902294 The name for the tag is called ID. 2017-05-08 16:55:11 -07:00
gaba 96cd5197e3 Test was failing as I was not returning an error. 2017-05-08 16:05:40 -07:00
gaba 743a96cd21 Removes debug statements. 2017-05-08 13:12:24 -07:00
gaba c4c0ee79cd Fix tests. 2017-05-08 13:12:24 -07:00
gaba 6310528804 Merge branch 'master' into 142993479-tags 2017-05-08 13:12:23 -07:00
Kim Gardner a90e1e132b Merge pull request #569 from coralproject/ui-overlaps
Apply button overlapped text
2017-05-08 13:12:22 -07:00
Wyatt Johnson d262663e3f Merge pull request #568 from coralproject/user-fix
Added privacy fix
2017-05-08 13:12:21 -07:00
Riley Davis 85fae3104e float: right; in css is just fine. 2017-05-08 13:12:21 -07:00
Wyatt Johnson 607ab6c0d6 Added privacy fix 2017-05-08 13:12:20 -07:00
gaba c72cb2428e Fixed errors on tags creation 2017-05-08 13:12:19 -07:00
gaba d7e72bb138 Fix tests. 2017-05-08 13:12:18 -07:00
gaba cb09c50820 Error on duplicate. 2017-05-08 13:11:48 -07:00
Gabriela Rodríguez Berón 28920eb357 Merge branch 'master' into 142993479-tags 2017-05-05 08:44:09 -07:00
Kim Gardner 3fcf21f949 Merge branch 'master' into 142993479-tags 2017-05-02 13:41:39 -04:00
gaba c0fc87ea21 Merge branch 'master' into i18n-refactor 2017-05-01 11:28:06 -07:00
Kim Gardner 058aa3f907 Merge branch 'master' into 142993479-tags 2017-04-27 11:59:48 -04:00
gaba 9e41f0bd65 Merge branch 'master' into i18n-refactor 2017-04-25 16:10:45 -05:00
gaba 527cbc7d63 Recover the scroll. 2017-04-25 16:10:32 -05:00
gaba 0f7b4ddaf6 Removes timeago from i18n module. 2017-04-25 11:43:04 -05:00
gaba 59e23ffd60 Moves into YAML and own module. 2017-04-24 18:09:13 -05:00
gaba 5aacdc3a74 Merge branch 'master' into i18n-refactor 2017-04-24 12:56:32 -05:00
gaba 05bf21fe09 Refactoring 2017-04-24 10:08:33 -05:00
gaba c4ffe58f58 Adds privacy type to the methods to add tags. 2017-04-21 14:30:41 -07:00
gaba 0a4fd15fa2 Merge branch 'master' into 142993479-tags 2017-04-21 12:51:05 -07:00
gaba b52325f8e9 Merge branch 'master' into 142993479-tags 2017-04-21 10:36:28 -07:00
gaba 91af8c2ece Index on Tag for name, item id, item type. 2017-04-21 10:34:57 -07:00
gaba 962a9bd6c9 That was so so so silly... 2017-04-20 14:42:57 -07:00
gaba 183dab9826 Merge branch 'master' into 142993479-tags 2017-04-20 12:34:32 -07:00
gaba 8458a9aecc fixed lint error. 2017-04-20 12:29:21 -07:00
gaba bd72bfc6ec Fix tests related to tags on comments. 2017-04-20 12:24:08 -07:00
gaba 1137fca3b4 Adds Tag Model that will be apply to different models. 2017-04-18 14:03:26 -07:00
602 changed files with 27295 additions and 9802 deletions
+4
View File
@@ -1,4 +1,5 @@
dist
docs
client/lib
**/*.html
plugins/*
@@ -9,4 +10,7 @@ plugins/*
!plugins/coral-plugin-like
!plugins/coral-plugin-mod
!plugins/coral-plugin-love
!plugins/coral-plugin-viewing-options
!plugins/coral-plugin-comment-content
!plugins/talk-plugin-permalink
node_modules
+2 -1
View File
@@ -53,7 +53,8 @@
"no-lonely-if": [2],
"curly": [2],
"no-unused-vars": ["error", {
"argsIgnorePattern": "next"
"argsIgnorePattern": "^_|next",
"varsIgnorePattern": "^_"
}],
"no-multiple-empty-lines": ["error", {
"max": 1
+3
View File
@@ -23,5 +23,8 @@ plugins/*
!plugins/coral-plugin-like
!plugins/coral-plugin-mod
!plugins/coral-plugin-love
!plugins/coral-plugin-viewing-options
!plugins/coral-plugin-comment-content
!plugins/talk-plugin-permalink
**/node_modules/*
+51 -42
View File
@@ -6,16 +6,46 @@ This document is a companion to help you approach contributing. If it does not d
By contributing to this project you agree to the [Code of Conduct](https://coralproject.net/code-of-conduct.html).
## Product Roadmap
## What should I Contribute?
You can view what the Coral Team is working on next here https://www.pivotaltracker.com/n/projects/1863625.
There are at least three ways to contribute to Talk:
If you'd like to see our longer term roadmap, [get in touch.](https://coralproject.net/contact.html)
* Writing Code
* Writing Docs
* Providing Translations
## Writing Code
## Contribute to the documentation
Conversation surrounding contributions begins in [issues](https://github.com/coralproject/talk/issues).
Clear docs are a prerequisite for a successful open source project. We value non-code and code contributions equally.
### When should I Create an Issue?
File an issue as soon as you have an idea of something you'd like to contribute. We would love to hear what you're thinking and help refine the idea to make it into the Talk ecosystem.
Please file issues if:
* you would like to contribute to Talk Core.
* you are building a Plugin that the current Plugin API doesn't support.
* you are building a Plugin and want advice.
### What should I include?
Coral has adopted an iterative, agile development philosophy. All contributions that make it into the Talk repo should start with a story or this form:
`As a [type of person] I'd like to be able to [do something] so that I can [get some result].`
This exercise does two things:
* allows us to ground our technical choices in a clear, simple product need.
* expresses that product need in a way that doesn't imply a specific technical solution allowing for debate as to the best way to solve the problem.
Please feel free to provide as much detail as possible when filing the issue but please do keep the initial issue specific to one need and try to avoid including technical or design solutions.
If you have a specific technical or design solution in mind, please submit it as the first comment on the thread.
## Contributing Documentation
Clear docs are a prerequisite for a successful open source project. Contributing to the documentation is often more important than contributing to the code!
We are looking for _documentarians_ to:
@@ -23,55 +53,34 @@ We are looking for _documentarians_ to:
* create new / missing sections, and
* take the lead in making sections, or the over all structure better.
### But how?
Information about how to update docs can be found in our [FAQ](faq.html#how-do-i-contribute-to-these-docs).
* Our public docs site can be updated [here](https://github.com/coralproject/docs).
* [Let us know](https://github.com/coralproject/talk/wiki/Contact-Us) if you'd like permission to update our wiki.
* Update any of our .md docs files by following [this guide](https://github.com/coralproject/talk/wiki/Forking,-Branching-and-Merging).
* Our API docs need to be kept honest. [Update them here](https://github.com/coralproject/talk/blob/master/docs/swagger.yaml).
If you'd like to discuss a contribution, please [file an issue](https://github.com/coralproject/talk/issues) describing the changes you would like to see.
## Integrate into your environment
## Contributing Translations
First, [set up a dev environment](https://github.com/coralproject/talk/blob/master/INSTALL.md). Please let us know how this goes!
Talk's tranlations are stored in `.yml` files [here](https://github.com/coralproject/talk/tree/master/locales).
Talk is designed to integrate into existing environments in a variety of ways:
Translations can be submitted via pull request. If you do not use github, you can use 'en.yml' as a template and [email](https://coralproject.net/contact.html) the translations to us. We can import it into the repo.
* [Auth integrations](https://github.com/coralproject/talk/wiki/Security#authentication-strategies)
* [Push assets into Talk](https://github.com/coralproject/talk/blob/master/routes/api/assets/index.js)
* Monitoring Hooks (coming in 2017)
## I want to contribute but I'm not sure what to do!
If you're considering deploying Talk, [please let us know](https://github.com/coralproject/talk/wiki/Contact-Us)! We are quite literally doing this for you and want to help you succeed any way we can.
If you want to contribute but don't have a clear idea of exactly what that may be, here are some resources that may help:
If you are writing custom integration code in your fork of Talk, please consider keeping it generic and filing a Pull Request to contribute it back to the project! See our [forking and merging guidelines](https://github.com/coralproject/talk/wiki/Forking,-Branching-and-Merging) for more info.
### Product Roadmap
## Write some code
Please visit our product roadmap here: https://www.pivotaltracker.com/n/projects/1863625. If you'd like to take on any of our scheduled tasks we'd be forever grateful!
First, [set up a dev environment](https://github.com/coralproject/talk/blob/master/INSTALL.md). Please let us know how this goes!
### Discussion Forum
### Build a New Feature / Plugin
If you'd like to discuss what we're up to, please visit or [community](https://community.coralproject.net/) or [contact us](https://coralproject.net/contact.html).
Talk is beginning life as a Commenting Platform, but is architected to support many varieties of community engagement.
### Integrations
Please [contact us](https://github.com/coralproject/talk/wiki/Contact-Us) early and often if you'd like to help. We would love to hear your ideas for features and plugins and help you find a way to productively engage the project.
Have a favorite analytics engine? Data science service? CMS? Auth platform? Deployment platform or pipeline? Pet project? Consider building a plugin to integrate them!
To get an idea of where the Coral Team is going, see:
### Favorite Features?
* our [Slack channel](https://coralprojectslackin.herokuapp.com/), where you can talk directly with the Team and other community members,
* our [current stories](https://www.pivotaltracker.com/n/projects/1863625), and
* our [issues](https://github.com/coralproject/talk/issues).
Do you have a favorite feature of an existing platform that's not yet been done in Talk? Sounds like Talk needs that feature.
Examples:
* [Add An Emoji Button to Comments](https://github.com/coralproject/talk/wiki/Add-An-Emoji-Button-to-Comments)
### Work on the Core
There is always more work to be done to make an application more stable, scaleable and secure.
If you see issues in the code or have ideas on how we may improve Talk, please consider:
* [contributing a fix](https://github.com/coralproject/talk/wiki/Forking,-Branching-and-Merging),
* [filing an issue](https://github.com/coralproject/talk/issues), or
* or otherwise [letting us know](https://github.com/coralproject/talk/wiki/Contact-Us).
## Thanks!
+1 -466
View File
@@ -1,468 +1,3 @@
# Talk Plugins
Plugins for Talk can take various forms, currently we are only supporting server
side plugins.
## Plugin Registration
The parsing order for the plugin registration is as follows:
- `TALK_PLUGINS_JSON` environment variable
- `plugins.json` file
- `plugins.default.json` file
If you need to "disable all plugins", you can simply provide `{}` as the
contents of `process.env.TALK_PLUGINS_JSON` or the `plugins.json`.
The format for this is thus:
```json
{
"server": [
"people"
]
}
```
Where we have a `server` key with an array of plugins that match the folder
name in the `plugins/` folder. For example, the above config would
require a plugin from `plugins/people`, which must provide a `index.js` file
that returns an object that matches the Plugin Specification.
If the package is external (available on NPM) you can specify the string for
the version by using an object instead, for example:
```json
{
"server": [
{"people": "^1.2.0"}
]
}
```
External plugins can be resolved by running:
```bash
./bin/cli plugins reconcile
```
This achieves two things:
1. It will traverse into local plugin folders and install their dependencies.
_Note that if the plugin is already installed and available in the node_modules folder, it will not be
fetched again unless there is a version mismatch._ This will result in the
project `package.json` and `yarn.lock` files to be modified, this is normal as
this ensures that repeated deployments (with the same config) will have the
same config, these changes should not be committed to source control.
2. It will seek out dependencies that are listed in the object notation and try
to install them from npm.
## Plugin Dependencies
You may also include additional external dependencies in your local packages by
specifying a `package.json` at your plugin root which will result in a
`node_modules` folder being generated at the plugin root with your specific
dependencies.
## Deployment Solutions
Plugins can be deployed with a production instance of Talk.
### Source
Source deployments can just modify the `plugins.json` file and include any
local plugins into the `plugins/` directory. After including the config, you
need to reconcile the plugins and build the static assets:
```bash
# get plugin dependancies and remote plugins
./bin/cli plugins reconcile
# build staic assets (including enabled client side plugins)
yarn build
```
Then the application can be started as is.
### Docker
If you deploy using Docker, you can extend from the `*-onbuild` image, an
example `Dockerfile` for your project could be:
```Dockerfile
FROM coralproject/talk:latest-onbuild
```
Where the directory for your instance would contain a `plugins.json` file
describing the plugin requirements and a `plugins` directory containing any
other local plugins that should be included.
Onbuild triggers will execute when the image is building with your custom
configuration and will ensure that the image is ready to use by building all
assets inside the image as well.
## Server Plugins
### API
You can access any API available inside the talk directory in a plugin by simply
importing the file relative to the talk project root. An example would be if you
wanted to import the `MetadataService`, you would simply write:
```javascript
const MetadataService = require('services/metadata');
```
### Specification
Each plugin should export a single object with all hooks available on it.
_**Note: You will have access to the whole core and other plugin's typeDefs,
context, loaders, mutators, resolvers, hooks. This is intentional, as it
encourages composing plugins to merge functionality, like a Slack plugin which
provides a Slack notify context function as well as having the loader for
comments.**_
The following are the hooks available:
#### Field: `typeDefs`
```graphql
enum COLOUR {
RED
BLUE
}
type Person {
name: String!
colour: COLOUR!
}
type RootMutation {
createPerson(name: String!): Person
}
type RootQuery {
people: [Person!]
}
type Subscription {
leader: Person
}
```
Thanks to [gql-merge](https://www.npmjs.com/package/gql-merge) the contents of
`typeDefs` should be a string that will be _merged_ with the existing type
definitions. `enum`'s will be appended to, types will be appended, and new types
will be added.
#### Field: `context`
```js
{
Slack: (context) => ({
notify: (message) => {
// return a promise after we're done sending notifications.
}
})
}
```
Any property provided here will be added to the context parameter available
inside all resolvers, loaders, mutators, and of course, other context based
plugins.
The top level item must accept a context for the request which it should use to
configure the context plugin before it would be mounted at `context.plugins`.
This plugin above would mount at: `context.plugins.Slack`, or, if you're using
[object destructuring](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), `{plugins: {Slack}}`.
#### Field: `loaders`
```js
(context) => ({
People: {
load: () => db.people.find({user: context.user})
}
})
```
Loaders should be provided as a function which returns a map which is used in
the resolvers function. These must return a promise or a value.
#### Field: `mutators`
```js
(context) => ({
People: {
create: (name) => {
return db.people.insert({user: context.user, name});
}
}
})
```
Mutators should be provided as a function which returns a map which is used in
the resolvers function. These must return a promise or a value.
#### Field: `resolvers`
```js
{
Person: {
name(obj, args, context) {
return obj.name;
},
colour(obj, args, context) {
// Bill likes the colour red, everyone else likes blue.
return obj.name === 'bill' ? 'RED' : 'BLUE';
}
},
RootQuery: {
people(obj, args, {loaders: {People}}) {
return People.load();
}
},
RootMutation: {
createPerson(obj, {name}, {mutators: {People}}) {
return People.create(name);
}
}
}
```
Should return a resolver map as described in the
[Apollo Docs](http://dev.apollodata.com/tools/graphql-tools/resolvers.html#Resolver-map).
This will merge with the existing resolvers in core and from previous plugins.
#### Field: `hooks`
```js
{
RootMutation: {
createPerson: {
post: async (obj, args, {plugins: {Slack}}, info, person) {
if (!person) {
return person;
}
await Slack.notify(`A new person just was created with name ${person.name}`);
return person;
}
}
}
}
```
Hooks here are pretty special, for each resolver field, you can specify a
pre/post hook that will execute pre and post field resolution.
If your post function accepts four parameters, then it can modify the field
result. It is *required* that the function resolves a promise (or returns) with
the modified value or simply the original if you didn't modify it.
#### Field: `setupFunctions`
```js
setupFunctions: {
leader: (options, args) => ({
leader: {
filter: (person) => person.place === 1
},
}),
}
```
Setup functions allow you to create filters that control which pubsub.publish() events
send data to the client. If the type in question contains args, clients may subscribe using those arguments to further filter their subscription.
For more information, see the [Apollo Docs](https://github.com/apollographql/graphql-subscriptions).
#### Field: `router`
```js
(router) => {
router.get('/api/v1/people', (req, res) => {
res.json({people: [{name: 'Bob'}]});
});
}
```
The Router hook allows you to create a function that accepts the base express
router where you can mount any amount of middleware/routes to do any form of
action needed by external applications. We also provide the authorization
middleware via:
```js
const authorization = require('middleware/authorization');
module.exports = {
router(router) {
router.get('/api/v1/people', authorization.needed('ADMIN'), (req, res) => {
res.json({people: [{name: 'SECRET PEOPLE'}]});
});
}
}
```
#### Field: `passport`
```js
const FacebookStrategy = require('passport-facebook').Strategy;
const UsersService = require('services/users');
const {ValidateUserLogin, HandleAuthPopupCallback} = require('services/passport');
module.exports = {
passport(passport) {
passport.use(new FacebookStrategy({
clientID: process.env.TALK_FACEBOOK_APP_ID,
clientSecret: process.env.TALK_FACEBOOK_APP_SECRET,
callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`,
passReqToCallback: true,
profileFields: ['id', 'displayName', 'picture.type(large)']
}, async (req, accessToken, refreshToken, profile, done) => {
let user;
try {
user = await UsersService.findOrCreateExternalUser(profile);
} catch (err) {
return done(err);
}
return ValidateUserLogin(profile, user, done);
}));
},
router(router) {
// Note that we have to import the passport instance here, it is
// instantiated after all the strategies have been mounted.
const {passport} = require('services/passport');
/**
* Facebook auth endpoint, this will redirect the user immediatly to facebook
* for authorization.
*/
router.get('/facebook', passport.authenticate('facebook', {display: 'popup', authType: 'rerequest', scope: ['public_profile']}));
/**
* Facebook callback endpoint, this will send the user a html page designed to
* send back the user credentials upon sucesfull login.
*/
router.get('/facebook/callback', (req, res, next) => {
// Perform the facebook login flow and pass the data back through the opener.
passport.authenticate('facebook', HandleAuthPopupCallback(req, res, next))(req, res, next);
});
}
};
```
This is a full example including the routes hook to add the required components
to the application router to support a different auth strategy.
### Full Example
Contents of `plugins.json`:
```json
{
"server": [
"people"
]
}
```
Located in `plugins/people/index.js`:
```js
module.exports = {
typeDefs: `
enum COLOUR {
RED
BLUE
}
type Person {
name: String!
colour: COLOUR!
}
type RootMutation {
createPerson(name: String!): Person
}
type RootQuery {
people: [Person!]
}
type Subscription {
leader: Person
}
`,
context: {
Slack: () => ({
notify: (message) => {
// return a promise after we're done sending notifications.
}
})
},
loaders: ({user}) => ({
People: {
load: () => db.people.find({user})
}
}),
mutators: ({user}) => ({
People: {
create: (name) => {
return db.people.insert({user, name});
}
}
}),
resolvers: {
Person: {
name(obj, args, context) {
return obj.name;
},
colour(obj, args, context) {
// Bill likes the colour red, everyone else likes blue.
return obj.name === 'bill' ? 'RED' : 'BLUE';
}
},
RootQuery: {
people(obj, args, {loaders: {People}}) {
return People.load();
}
},
RootMutation: {
createPerson(obj, {name}, {mutators: {People}}) {
return People.create(name);
}
}
},
hooks: {
RootMutation: {
createPerson: {
post: async (obj, args, {plugins: {Slack}}, info, person) => {
if (!person) {
return person;
}
await Slack.notify(`A new person just was created with name ${person.name}`);
return person;
}
}
}
},
setupFunctions: {
leader: (options, args) => ({
leader: {
filter: (person) => person.place === 1
}
}
}
};
```
Our documentation for Talk has moved! Utilize [this hyperlink](https://coralproject.github.io/talk/plugins.html) via click or tap to navigate your browser to the new location!
+22 -2
View File
@@ -7,7 +7,17 @@ endpoint when the server is running with built assets.
## Contributing to Talk
See our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md)!
See our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md).
## Documentation
### General
See our [Talk Documentation & Guides](https://coralproject.github.io/talk/index.html).
### Plugins
See our guide to using and building [Talk Plugins](https://github.com/coralproject/talk/blob/master/PLUGINS.md).
## Usage
@@ -17,7 +27,6 @@ To set up a development environment or build from source, see [INSTALL.md](https
To launch a Talk server of your own from your browser without any need to muck about in a terminal or think about engineering concepts, stay tuned. We will launch [our installer](https://github.com/coralproject/talk-install) shortly!
### Configuration
The Talk application looks for the following configuration values either as environment variables:
@@ -41,10 +50,21 @@ sign and verify tokens via a `HS256` algorithm.
- `TALK_RECAPTCHA_SECRET` (*required for reCAPTCHA support*) - server secret used for enabling reCAPTCHA powered logins. If not provided it will instead default to providing only a time based lockout.
- `TALK_RECAPTCHA_PUBLIC` (*required for reCAPTCHA support*) - client secret used for enabling reCAPTCHA powered logins. If not provided it will instead default to providing only a time based lockout.
- `TALK_PLUGINS_JSON` (_optional_) - used to specify the plugin config via the environment
- `TALK_KEEP_ALIVE` (_optional_) - The keepalive timeout that should be used to send keep alive messages through the websocket to keep the socket alive. (Default `30s`)
Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for
alternative methods of loading configuration during development.
### Running Migrations
We have a migration tool that can be run using `bin/cli migration run`. This will detect new migrations available and prompt you to backup your database before proceeding with the migration. Migrations are required with major version releases.
### Using Trust
Talk ships with core components we call "Trust". This allows Talk to automate certain actions based on previous user behavior.
Our first feature is the notion of Karma. Talk will automatically pre-moderate comments of users who have a negative karma score. You can [see more how karma works here](/services/karma.js).
## Supported Browsers
### Web
+3
View File
@@ -6,6 +6,7 @@ const helmet = require('helmet');
const authentication = require('./middleware/authentication');
const {passport} = require('./services/passport');
const plugins = require('./services/plugins');
const i18n = require('./services/i18n');
const enabled = require('debug').enabled;
const errors = require('./errors');
const {createGraphOptions} = require('./graph');
@@ -157,6 +158,8 @@ app.use('/', (err, req, res, next) => {
console.error(err);
}
i18n.init(req);
if (err instanceof errors.APIError) {
res.status(err.status);
res.render('error', {
+11 -2
View File
@@ -9,11 +9,17 @@ const program = require('./commander');
program
.command('serve', 'serve the application')
.command('settings', 'interact with the application settings')
.command('assets', 'interact with assets')
.command('setup', 'setup the application')
.command('jobs', 'work with the job queues')
.command('token', 'work with the access tokens')
.command('users', 'work with the application auth')
.command('plugins', 'provides utilities for interacting with the plugin system')
.command('migration', 'provides utilities for migrating the database')
.command(
'plugins',
'provides utilities for interacting with the plugin system'
)
.parse(process.argv);
/**
@@ -23,7 +29,10 @@ program
* labled with the PID written out by the parent process.
*/
process.once('exit', () => {
if ((program.runningCommand.killed === false) && (program.runningCommand.exitCode === null)) {
if (
program.runningCommand.killed === false &&
program.runningCommand.exitCode === null
) {
program.runningCommand.kill('SIGINT');
}
});
+109 -49
View File
@@ -5,12 +5,15 @@
*/
const program = require('./commander');
const parseDuration = require('parse-duration');
const parseDuration = require('ms');
const Table = require('cli-table');
const AssetModel = require('../models/asset');
const CommentModel = require('../models/comment');
const AssetsService = require('../services/assets');
const mongoose = require('../services/mongoose');
const scraper = require('../services/scraper');
const util = require('./util');
const inquirer = require('inquirer');
// Register the shutdown criteria.
util.onshutdown([
@@ -20,65 +23,112 @@ util.onshutdown([
/**
* Lists all the assets registered in the database.
*/
function listAssets() {
AssetModel
.find({})
.sort({'created_at': 1})
.then((asset) => {
let table = new Table({
head: [
'ID',
'Title',
'URL'
]
});
async function listAssets() {
try {
let assets = await AssetModel.find({}).sort({'created_at': 1});
asset.forEach((asset) => {
table.push([
asset.id,
asset.title ? asset.title : '',
asset.url ? asset.url : ''
]);
});
console.log(table.toString());
util.shutdown();
})
.catch((err) => {
console.error(err);
util.shutdown(1);
let table = new Table({
head: [
'ID',
'Title',
'URL'
]
});
assets.forEach((asset) => {
table.push([
asset.id,
asset.title ? asset.title : '',
asset.url ? asset.url : ''
]);
});
console.log(table.toString());
util.shutdown();
} catch (e) {
console.error(e);
util.shutdown(1);
}
}
function refreshAssets(ageString) {
const now = new Date().getTime();
const ageMs = parseDuration(ageString);
const age = new Date(now - ageMs);
async function refreshAssets(ageString) {
try {
const now = new Date().getTime();
const ageMs = parseDuration(ageString);
const age = new Date(now - ageMs);
AssetModel.find({
$or: [
{
scraped: {
$lte: age
let assets = await AssetModel.find({
$or: [
{
scraped: {
$lte: age
}
},
{
scraped: null
}
},
{
scraped: null
}
]
})
]
});
// Queue all the assets for scraping.
.then((assets) => Promise.all(assets.map(scraper.create)))
// Queue all the assets for scraping.
await Promise.all(assets.map(scraper.create));
.then(() => {
console.log('Assets were queued to be scraped');
util.shutdown();
})
.catch((err) => {
console.error(err);
} catch (e) {
console.error(e);
util.shutdown(1);
});
}
}
async function updateURL(assetID, assetURL) {
try {
await AssetsService.updateURL(assetID, assetURL);
console.log(`Asset ${assetID} was updated to have url ${assetURL}.`);
util.shutdown();
} catch (e) {
console.error(e);
util.shutdown(1);
}
}
async function merge(srcID, dstID) {
try {
// Grab the assets...
let [srcAsset, dstAsset] = await AssetsService.findByIDs([srcID, dstID]);
if (!srcAsset || !dstAsset) {
throw new Error('Not all assets indicated by id exist, cannot merge');
}
// Count the affected resources...
let srcCommentCount = await CommentModel.find({asset_id: srcID}).count();
console.log(`Now going to update ${srcCommentCount} comments and delete the source Asset[${srcID}].`);
let {confirm} = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: 'Proceed with merge',
default: false
}
]);
if (confirm) {
// Perform the merge!
await AssetsService.merge(srcID, dstID);
} else {
console.warn('Aborting merge');
}
util.shutdown(0);
} catch (e) {
console.error(e);
util.shutdown(1);
}
}
//==============================================================================
@@ -95,6 +145,16 @@ program
.description('queues the assets that exceed the age requested')
.action(refreshAssets);
program
.command('update-url <assetID> <url>')
.description('update the URL of an asset')
.action(updateURL);
program
.command('merge <srcID> <dstID>')
.description('merges two assets together by moving comments from src to dst and deleting the src asset')
.action(merge);
program.parse(process.argv);
// If there is no command listed, output help.
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
const program = require('./commander');
const util = require('./util');
const inquirer = require('inquirer');
const mongoose = require('../services/mongoose');
const MigrationService = require('../services/migration');
// Register shutdown hooks.
util.onshutdown([
() => mongoose.disconnect()
]);
async function createMigration(name) {
try {
// Create the migration.
await MigrationService.create(name);
util.shutdown();
} catch (e) {
console.error(e);
util.shutdown(1);
}
}
async function runMigrations() {
try {
let {backedUp} = await inquirer.prompt([
{
type: 'confirm',
name: 'backedUp',
message: 'Did you perform a database backup',
default: false
}
]);
if (!backedUp) {
throw new Error('Please backup your databases prior to migrations occuring');
}
// Get the migrations to run.
let migrations = await MigrationService.listPending();
console.log('Now going to run the following migrations:\n');
for (let {filename} of migrations) {
console.log(`\tmigrations/${filename}`);
}
let {confirm} = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: 'Proceed with migrations',
default: false
}
]);
if (confirm) {
// Run the migrations.
await MigrationService.run(migrations);
} else {
console.warn('Skipping migrations');
}
util.shutdown();
} catch (e) {
console.error(e);
util.shutdown(1);
}
}
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
program
.command('create <name>')
.description('creates a new migration')
.action(createMigration);
program
.command('run')
.description('runs all pending migrations')
.action(runMigrations);
program.parse(process.argv);
// If there is no command listed, output help.
if (process.argv.length <= 2) {
program.outputHelp();
util.shutdown();
}
+44 -1
View File
@@ -2,9 +2,13 @@
const program = require('./commander');
const app = require('../app');
const debug = require('debug')('talk:cli:serve');
const errors = require('../errors');
const {createServer} = require('http');
const scraper = require('../services/scraper');
const mailer = require('../services/mailer');
const MigrationService = require('../services/migration');
const SetupService = require('../services/setup');
const kue = require('../services/kue');
const mongoose = require('../services/mongoose');
const util = require('./util');
@@ -87,7 +91,46 @@ function onListening() {
/**
* Start the app.
*/
function startApp(program) {
async function startApp(program) {
try {
// Check to see if the application is installed. If the application
// has been installed, then it will throw errors.ErrSettingsNotInit, this
// just means we don't have to check that the migrations have run.
await SetupService.isAvailable();
debug('setup is currently available, migrations not being checked');
} catch (e) {
// Check the error.
switch (e) {
case errors.ErrInstallLock, errors.ErrSettingsInit:
debug('setup is not currently available, migrations now being checked');
// The error was expected, just continue.
break;
default:
// The error was not expected, throw the error!
throw e;
}
// Now try and check the migration status.
try {
// Verify that the minimum migration version is met.
await MigrationService.verify();
} catch (e) {
console.error(e);
process.exit(1);
}
debug('migrations do not have to be run');
}
/**
* Listen on provided port, on all network interfaces.
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env node
const program = require('./commander');
const inquirer = require('inquirer');
const mongoose = require('../services/mongoose');
const SettingsService = require('../services/settings');
const util = require('./util');
// Register the shutdown criteria.
util.onshutdown([() => mongoose.disconnect()]);
/**
* Change the organization name
*/
async function changeOrgName() {
try {
let settings = await SettingsService.retrieve();
let {organizationName} = await inquirer.prompt([
{
name: 'organizationName',
message: 'Organization Name',
default: settings.organizationName
}
]);
if (settings.organizationName !== organizationName) {
settings.organizationName = organizationName;
await SettingsService.update(settings);
console.log('Settings were updated.');
} else {
console.log('No update needed, no change was made.');
}
} catch (err) {
console.error(err);
util.shutdown(1);
}
util.shutdown();
}
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
program
.command('change-org-name')
.description('change the organization name')
.action(changeOrgName);
program.parse(process.argv);
// If there is no command listed, output help.
if (!process.argv.slice(2).length) {
program.outputHelp();
util.shutdown();
}
+139 -149
View File
@@ -8,6 +8,7 @@ const program = require('./commander');
const inquirer = require('inquirer');
const mongoose = require('../services/mongoose');
const SettingModel = require('../models/setting');
const MODERATION_OPTIONS = require('../models/enum/moderation_options');
const SettingsService = require('../services/settings');
const SetupService = require('../services/setup');
const UsersService = require('../services/users');
@@ -32,166 +33,155 @@ program
// Setup the application
//==============================================================================
const performSetup = () => {
if (program.defaults) {
return SettingsService
.init()
.then(() => {
console.log('Settings created.');
console.log('\nTalk is now installed!');
util.shutdown();
})
.catch((err) => {
console.error(err);
util.shutdown(1);
});
}
const performSetup = async () => {
// Get the current settings, we are expecing an error here.
return SettingsService
.retrieve()
.then(() => {
try {
// We should NOT have gotten a settings object, this means that the
// application is already setup. Error out here.
throw errors.ErrSettingsInit;
// Try to get the settings.
await SettingsService.retrieve();
})
.catch((err) => {
// We should NOT have gotten a settings object, this means that the
// application is already setup. Error out here.
throw errors.ErrSettingsInit;
// If the error is `not init`, then we're good, otherwise, it's something
// else.
if (err !== errors.ErrSettingsNotInit) {
throw err;
} catch (e) {
// If the error is `not init`, then we're good, otherwise, it's something
// else.
if (e !== errors.ErrSettingsNotInit) {
throw e;
}
}
if (program.defaults) {
await SettingsService.init();
console.log('Settings created.');
console.log('\nTalk is now installed!');
return;
}
// Create the base settings model.
let settings = new SettingModel();
console.log('\nWe\'ll ask you some questions in order to setup your installation of Talk.\n');
let answers = await inquirer.prompt([
{
type: 'input',
name: 'organizationName',
message: 'Organization Name',
default: settings.organizationName,
validate: (input) => {
if (input && input.length > 0) {
return true;
}
return 'Organization Name is required.';
}
},
{
type: 'list',
choices: MODERATION_OPTIONS,
name: 'moderation',
default: settings.moderation,
message: 'Select a moderation mode'
},
{
type: 'confirm',
name: 'requireEmailConfirmation',
default: settings.requireEmailConfirmation,
message: 'Should emails always be confirmed'
}
]);
})
.then(() => {
// Update the settings that were changed.
Object.keys(answers).forEach((key) => {
if (answers[key] !== undefined) {
settings[key] = answers[key];
}
});
// Create the base settings model.
let settings = new SettingModel();
console.log('\nWe\'ll ask you some questions about your first admin user.\n');
console.log('We\'ll ask you some questions in order to setup your installation of Talk.\n');
return inquirer.prompt([
{
type: 'input',
name: 'organizationName',
message: 'Organization Name',
default: settings.organizationName,
validate: (input) => {
if (input && input.length > 0) {
return true;
}
return 'Organization Name is required.';
}
},
{
type: 'list',
choices: SettingModel.MODERATION_OPTIONS,
name: 'moderation',
default: settings.moderation,
message: 'Select a moderation mode'
},
{
type: 'confirm',
name: 'requireEmailConfirmation',
default: settings.requireEmailConfirmation,
message: 'Should emails always be confirmed'
}
])
.then((answers) => {
// Update the settings that were changed.
Object.keys(answers).forEach((key) => {
if (answers[key] !== undefined) {
settings[key] = answers[key];
}
});
console.log('\nWe\'ll ask you some questions about your first admin user.\n');
return inquirer.prompt([
{
type: 'input',
name: 'username',
message: 'Username',
filter: (username) => {
return UsersService
.isValidUsername(username, false)
.catch((err) => {
throw err.message;
});
}
},
{
name: 'email',
message: 'Email',
format: 'email',
validate: (value) => {
if (value && value.length >= 3) {
return true;
}
return 'Email is required';
}
},
{
name: 'password',
message: 'Password',
type: 'password',
filter: (password) => {
return UsersService
.isValidPassword(password)
.catch((err) => {
throw err.message;
});
}
},
{
name: 'confirmPassword',
message: 'Confirm Password',
type: 'password',
filter: (confirmPassword) => {
return UsersService
.isValidPassword(confirmPassword)
.catch((err) => {
throw err.message;
});
}
},
]);
})
.then((user) => {
if (user.password !== user.confirmPassword) {
return Promise.reject(new Error('Passwords do not match'));
let user = await inquirer.prompt([
{
type: 'input',
name: 'username',
message: 'Username',
filter: (username) => {
return UsersService
.isValidUsername(username, false)
.catch((err) => {
throw err.message;
});
}
},
{
name: 'email',
message: 'Email',
format: 'email',
validate: (value) => {
if (value && value.length >= 3) {
return true;
}
return SetupService.setup({
settings: settings.toObject(),
user: {
email: user.email,
username: user.username,
password: user.password
}
});
});
})
.then(({user}) => {
console.log('Settings created.');
console.log(`User ${user.id} created.`);
console.log('\nTalk is now installed!');
console.log('\nWe recommend adding TALK_INSTALL_LOCK=TRUE to your environment to turn off the dynamic setup.');
util.shutdown();
})
.catch((err) => {
console.error(err);
util.shutdown(1);
});
return 'Email is required';
}
},
{
name: 'password',
message: 'Password',
type: 'password',
filter: (password) => {
return UsersService
.isValidPassword(password)
.catch((err) => {
throw err.message;
});
}
},
{
name: 'confirmPassword',
message: 'Confirm Password',
type: 'password',
filter: (confirmPassword) => {
return UsersService
.isValidPassword(confirmPassword)
.catch((err) => {
throw err.message;
});
}
},
]);
if (user.password !== user.confirmPassword) {
return Promise.reject(new Error('Passwords do not match'));
}
let {user: newUser} = await SetupService.setup({
settings: settings.toObject(),
user: {
email: user.email,
username: user.username,
password: user.password
}
});
console.log('Settings created.');
console.log(`User ${newUser.id} created.`);
console.log('\nTalk is now installed!');
console.log('\nWe recommend adding TALK_INSTALL_LOCK=TRUE to your environment to turn off the dynamic setup.');
};
// Start tthe setup process.
performSetup();
performSetup()
.then(() => {
util.shutdown();
})
.catch((e) => {
console.error(e);
util.shutdown(1);
});
Executable
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
const program = require('./commander');
const mongoose = require('../services/mongoose');
const TokensService = require('../services/tokens');
const util = require('./util');
const Table = require('cli-table');
// Register the shutdown criteria.
util.onshutdown([
() => mongoose.disconnect()
]);
async function listTokens(userID) {
try {
let tokens = await TokensService.list(userID);
let table = new Table({
head: [
'ID',
'Name',
'Status'
]
});
tokens.forEach((token) => {
table.push([
token.id,
token.name,
token.active ? 'Active' : 'Revoked'
]);
});
console.log(table.toString());
util.shutdown();
} catch (e) {
console.error(e);
util.shutdown(1);
}
}
async function revokeToken(tokenID) {
try {
await TokensService.revoke(null, tokenID);
console.log(`Revoked Token[${tokenID}]`);
util.shutdown();
} catch (e) {
console.error(e);
util.shutdown(1);
}
}
async function createToken(userID, tokenName) {
try {
let {pat: {id}, jwt} = await TokensService.create(userID, tokenName);
console.log(`Created Token[${id}] for User[${userID}] = ${jwt}`);
util.shutdown();
} catch (e) {
console.error(e);
util.shutdown(1);
}
}
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
program
.command('list <userID>')
.description('list tokens for a user')
.action(listTokens);
program
.command('revoke <tokenID>')
.description('revokes a token with a given id')
.action(revokeToken);
program
.command('create <userID> <tokenName>')
.description('create a token for a user with a given name')
.action(createToken);
program.parse(process.argv);
// If there is no command listed, output help.
if (!process.argv.slice(2).length) {
program.outputHelp();
util.shutdown();
}
+7 -6
View File
@@ -8,6 +8,7 @@ const program = require('./commander');
const inquirer = require('inquirer');
const UsersService = require('../services/users');
const UserModel = require('../models/user');
const USER_ROLES = require('../models/enum/user_roles');
const mongoose = require('../services/mongoose');
const util = require('./util');
const Table = require('cli-table');
@@ -36,7 +37,7 @@ function getUserCreateAnswers(options) {
roles: []
};
if (options.role && UserModel.USER_ROLES.indexOf(options.role) > -1) {
if (options.role && USER_ROLES.indexOf(options.role) > -1) {
user.roles = [options.role];
}
@@ -89,7 +90,7 @@ function getUserCreateAnswers(options) {
name: 'roles',
message: 'User Role',
type: 'checkbox',
choices: UserModel.USER_ROLES
choices: USER_ROLES
}
]);
}
@@ -291,8 +292,8 @@ function mergeUsers(dstUserID, srcUserID) {
*/
function addRole(userID, role) {
if (UserModel.USER_ROLES.indexOf(role) === -1) {
console.error(`Role '${role}' is not supported. Supported roles are ${UserModel.USER_ROLES.join(', ')}.`);
if (USER_ROLES.indexOf(role) === -1) {
console.error(`Role '${role}' is not supported. Supported roles are ${USER_ROLES.join(', ')}.`);
util.shutdown(1);
return;
}
@@ -316,8 +317,8 @@ function addRole(userID, role) {
*/
function removeRole(userID, role) {
if (UserModel.USER_ROLES.indexOf(role) === -1) {
console.error(`Role '${role}' is not supported. Supported roles are ${UserModel.USER_ROLES.join(', ')}.`);
if (USER_ROLES.indexOf(role) === -1) {
console.error(`Role '${role}' is not supported. Supported roles are ${USER_ROLES.join(', ')}.`);
util.shutdown(1);
return;
}
+1
View File
@@ -52,5 +52,6 @@ deployment:
latest:
branch: master
owner: coralproject
commands:
- bash ./scripts/docker.sh deploy
+5 -6
View File
@@ -4,21 +4,20 @@
"es6": true,
"mocha": true
},
"extends": "../.eslintrc.json",
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
},
"sourceType": "module"
}
},
"parser": "babel-eslint",
"plugins": [
"react"
],
"rules": {
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
}
}
-3
View File
@@ -1,3 +0,0 @@
{
"basePath": "admin"
}
+26 -31
View File
@@ -1,25 +1,20 @@
import React from 'react';
import {Router, Route, IndexRedirect, browserHistory} from 'react-router';
import {Router, Route, IndexRedirect, browserHistory, Redirect} from 'react-router';
import Stories from 'containers/Stories/Stories';
import Configure from 'containers/Configure/Configure';
import LayoutContainer from 'containers/LayoutContainer';
import InstallContainer from 'containers/Install/InstallContainer';
import Configure from 'routes/Configure';
import Dashboard from 'routes/Dashboard';
import Install from 'routes/Install';
import Stories from 'routes/Stories';
import {CommunityLayout, Community} from 'routes/Community';
import {ModerationLayout, Moderation} from 'routes/Moderation';
import CommunityLayout from 'containers/Community/CommunityLayout';
import CommunityContainer from 'containers/Community/CommunityContainer';
import ModerationLayout from 'containers/ModerationQueue/ModerationLayout';
import ModerationContainer from 'containers/ModerationQueue/ModerationContainer';
import Dashboard from 'containers/Dashboard/Dashboard';
import Layout from 'containers/Layout';
const routes = (
<div>
<Route exact path="/admin/install" component={InstallContainer}/>
<Route path='/admin' component={LayoutContainer}>
<Route exact path="/admin/install" component={Install}/>
<Route path='/admin' component={Layout}>
<IndexRedirect to='/admin/moderate/all' />
<Route path='community' component={CommunityContainer} />
<Route path='configure' component={Configure} />
<Route path='stories' component={Stories} />
<Route path='dashboard' component={Dashboard} />
@@ -27,11 +22,11 @@ const routes = (
{/* Community Routes */}
<Route path='community' component={CommunityLayout}>
<Route path='flagged' components={CommunityContainer}>
<Route path=':id' components={CommunityContainer} />
<Route path='flagged' components={Community}>
<Route path=':id' components={Community} />
</Route>
<Route path='people' components={CommunityContainer}>
<Route path=':id' components={CommunityContainer} />
<Route path='people' components={Community}>
<Route path=':id' components={Community} />
</Route>
<IndexRedirect to='flagged' />
</Route>
@@ -39,23 +34,23 @@ const routes = (
{/* Moderation Routes */}
<Route path='moderate' component={ModerationLayout}>
<Route path='all' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='all' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='accepted' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='accepted' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='premod' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='premod' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='rejected' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='rejected' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='flagged' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='flagged' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path=':id' components={ModerationContainer} />
<IndexRedirect to='premod' />
<Redirect from=':id' to='all/:id' />
<IndexRedirect to='all' />
</Route>
</Route>
</div>
+17 -9
View File
@@ -9,6 +9,7 @@ import {
} from '../constants/assets';
import coralApi from '../../../coral-framework/helpers/request';
import t from 'coral-framework/services/i18n';
/**
* Action disptacher related to assets
@@ -19,12 +20,16 @@ import coralApi from '../../../coral-framework/helpers/request';
export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filter = '') => (dispatch) => {
dispatch({type: FETCH_ASSETS_REQUEST});
return coralApi(`/assets?skip=${skip}&limit=${limit}&sort=${sort}&search=${search}&filter=${filter}`)
.then(({result, count}) =>
dispatch({type: FETCH_ASSETS_SUCCESS,
assets: result,
count
}))
.catch((error) => dispatch({type: FETCH_ASSETS_FAILURE, error}));
.then(({result, count}) =>
dispatch({type: FETCH_ASSETS_SUCCESS,
assets: result,
count
}))
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch({type: FETCH_ASSETS_FAILURE, error: errorMessage});
});
};
// Update an asset state
@@ -32,9 +37,12 @@ export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filte
export const updateAssetState = (id, closedAt) => (dispatch) => {
dispatch({type: UPDATE_ASSET_STATE_REQUEST});
return coralApi(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}})
.then(() =>
dispatch({type: UPDATE_ASSET_STATE_SUCCESS}))
.catch((error) => dispatch({type: UPDATE_ASSET_STATE_FAILURE, error}));
.then(() => dispatch({type: UPDATE_ASSET_STATE_SUCCESS}))
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch({type: UPDATE_ASSET_STATE_FAILURE, error: errorMessage});
});
};
export const updateAssets = (assets) => (dispatch) => {
+35 -11
View File
@@ -3,6 +3,8 @@ import * as actions from '../constants/auth';
import coralApi from 'coral-framework/helpers/request';
import * as Storage from 'coral-framework/helpers/storage';
import {handleAuthToken} from 'coral-framework/actions/auth';
import {resetWebsocket} from 'coral-framework/services/client';
import t from 'coral-framework/services/i18n';
//==============================================================================
// SIGN IN
@@ -36,16 +38,31 @@ export const handleLogin = (email, password, recaptchaResponse) => (dispatch) =>
}
dispatch(handleAuthToken(token));
resetWebsocket();
dispatch(checkLoginSuccess(user));
})
.catch((error) => {
if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
if (error.translation_key === 'NOT_AUTHORIZED') {
// invalid credentials
dispatch({
type: actions.LOGIN_FAILURE,
message: t('error.email_password')
});
}
else if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') {
dispatch({
type: actions.LOGIN_MAXIMUM_EXCEEDED,
message: error.translation_key
message: t(`error.${error.translation_key}`),
});
} else {
dispatch({type: actions.LOGIN_FAILURE, message: error.translation_key});
dispatch({
type: actions.LOGIN_FAILURE,
message: errorMessage,
});
}
});
};
@@ -54,25 +71,30 @@ export const handleLogin = (email, password, recaptchaResponse) => (dispatch) =>
// FORGOT PASSWORD
//==============================================================================
const forgotPassowordRequest = () => ({
const forgotPasswordRequest = () => ({
type: actions.FETCH_FORGOT_PASSWORD_REQUEST
});
const forgotPassowordSuccess = () => ({
const forgotPasswordSuccess = () => ({
type: actions.FETCH_FORGOT_PASSWORD_SUCCESS
});
const forgotPassowordFailure = () => ({
type: actions.FETCH_FORGOT_PASSWORD_FAILURE
const forgotPasswordFailure = (error) => ({
type: actions.FETCH_FORGOT_PASSWORD_FAILURE,
error,
});
export const requestPasswordReset = (email) => (dispatch) => {
dispatch(forgotPassowordRequest(email));
dispatch(forgotPasswordRequest(email));
const redirectUri = location.href;
return coralApi('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}})
.then(() => dispatch(forgotPassowordSuccess()))
.catch((error) => dispatch(forgotPassowordFailure(error)));
.then(() => dispatch(forgotPasswordSuccess()))
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch(forgotPasswordFailure(errorMessage));
});
};
//==============================================================================
@@ -105,10 +127,12 @@ export const checkLogin = () => (dispatch) => {
return dispatch(checkLoginFailure('not logged in'));
}
resetWebsocket();
dispatch(checkLoginSuccess(user));
})
.catch((error) => {
console.error(error);
dispatch(checkLoginFailure(`${error.translation_key}`));
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch(checkLoginFailure(errorMessage));
});
};
@@ -0,0 +1,7 @@
import {SHOW_BAN_USER_DIALOG, HIDE_BAN_USER_DIALOG} from '../constants/banUserDialog';
export const showBanUserDialog = ({userId, username, commentId, commentStatus}) =>
({type: SHOW_BAN_USER_DIALOG, userId, username, commentId, commentStatus});
export const hideBanUserDialog = () => ({type: HIDE_BAN_USER_DIALOG});
+11 -7
View File
@@ -10,11 +10,12 @@ import {
SET_COMMENTER_STATUS,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG,
SHOW_SUSPENDUSER_DIALOG,
HIDE_SUSPENDUSER_DIALOG
SHOW_REJECT_USERNAME_DIALOG,
HIDE_REJECT_USERNAME_DIALOG
} from '../constants/community';
import coralApi from '../../../coral-framework/helpers/request';
import t from 'coral-framework/services/i18n';
export const fetchAccounts = (query = {}) => (dispatch) => {
@@ -30,7 +31,11 @@ export const fetchAccounts = (query = {}) => (dispatch) => {
totalPages
});
})
.catch((error) => dispatch({type: FETCH_COMMENTERS_FAILURE, error}));
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch({type: FETCH_COMMENTERS_FAILURE, error: errorMessage});
});
};
const requestFetchAccounts = () => ({
@@ -47,7 +52,6 @@ export const newPage = () => ({
});
export const setRole = (id, role) => (dispatch) => {
return coralApi(`/users/${id}/role`, {method: 'POST', body: {role}})
.then(() => {
return dispatch({type: SET_ROLE, id, role});
@@ -65,6 +69,6 @@ export const setCommenterStatus = (id, status) => (dispatch) => {
export const showBanUserDialog = (user) => ({type: SHOW_BANUSER_DIALOG, user});
export const hideBanUserDialog = () => ({type: HIDE_BANUSER_DIALOG});
// Suspend User Dialog
export const showSuspendUserDialog = (user) => ({type: SHOW_SUSPENDUSER_DIALOG, user});
export const hideSuspendUserDialog = () => ({type: HIDE_SUSPENDUSER_DIALOG});
// Reject Username Dialog
export const showRejectUsernameDialog = (user) => ({type: SHOW_REJECT_USERNAME_DIALOG, user});
export const hideRejectUsernameDialog = () => ({type: HIDE_REJECT_USERNAME_DIALOG});
+33 -5
View File
@@ -2,6 +2,7 @@ import coralApi from 'coral-framework/helpers/request';
import * as actions from '../constants/install';
import validate from 'coral-framework/helpers/validate';
import errorMsj from 'coral-framework/helpers/error';
import t from 'coral-framework/services/i18n';
export const nextStep = () => ({type: actions.NEXT_STEP});
export const previousStep = () => ({type: actions.PREVIOUS_STEP});
@@ -17,7 +18,8 @@ const clearErrors = () => ({type: actions.CLEAR_ERRORS});
const validation = (formData, dispatch, next) => {
if (!(formData != null)) {
return dispatch(hasError());
dispatch(hasError());
return;
}
const validKeys = Object.keys(formData)
@@ -40,7 +42,8 @@ const validation = (formData, dispatch, next) => {
});
if (empty.length) {
return dispatch(hasError());
dispatch(hasError());
return;
}
// RegExp Validation
@@ -59,7 +62,30 @@ const validation = (formData, dispatch, next) => {
});
if (validation.length) {
return dispatch(hasError());
dispatch(hasError());
return;
}
// Confirm Validation
const prefixLength = 'confirm'.length;
const confirm = validKeys
.filter((name) => {
if (!name.startsWith('confirm')) {
return false;
}
// Check that 'confirmX' equals 'X'.
const other = name.substr(prefixLength, 1).toLowerCase() + name.substr(prefixLength + 1);
const cond = formData[other] !== formData[name];
if (cond) {
dispatch(addError(name, errorMsj[name]));
}
return cond;
});
if (confirm.length) {
dispatch(hasError());
return;
}
dispatch(clearErrors());
@@ -90,7 +116,8 @@ export const finishInstall = () => (dispatch, getState) => {
})
.catch((error) => {
console.error(error);
dispatch(installFailure(`${error.translation_key}`));
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch(installFailure(errorMessage));
});
};
@@ -113,6 +140,7 @@ export const checkInstall = (next) => (dispatch) => {
})
.catch((error) => {
console.error(error);
dispatch(checkInstallFailure(`${error.translation_key}`));
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch(checkInstallFailure(errorMessage));
});
};
+37 -10
View File
@@ -3,16 +3,6 @@ import * as actions from 'constants/moderation';
export const toggleModal = (open) => ({type: actions.TOGGLE_MODAL, open});
export const singleView = () => ({type: actions.SINGLE_VIEW});
// Ban User Dialog
export const showBanUserDialog = (user, commentId, commentStatus, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, commentStatus, showRejectedNote});
export const hideBanUserDialog = (showDialog) => ({type: actions.HIDE_BANUSER_DIALOG, showDialog});
// Suspend User Dialog
export const showSuspendUserDialog = (userId, username, commentId, commentStatus) =>
({type: actions.SHOW_SUSPEND_USER_DIALOG, userId, username, commentId, commentStatus});
export const hideSuspendUserDialog = () => ({type: actions.HIDE_SUSPEND_USER_DIALOG});
// hide shortcuts note
export const hideShortcutsNote = () => {
try {
@@ -27,3 +17,40 @@ export const hideShortcutsNote = () => {
export const viewUserDetail = (userId) => ({type: actions.VIEW_USER_DETAIL, userId});
export const hideUserDetail = () => ({type: actions.HIDE_USER_DETAIL});
export const setSortOrder = (order) => ({
type: actions.SET_SORT_ORDER,
order
});
export const changeUserDetailStatuses = (tab) => {
let statuses;
if (tab === 'all') {
statuses = ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'];
} else if (tab === 'rejected') {
statuses = ['REJECTED'];
}
return {type: actions.CHANGE_USER_DETAIL_STATUSES, tab, statuses};
};
export const clearUserDetailSelections = () => ({type: actions.CLEAR_USER_DETAIL_SELECTIONS});
export const toggleSelectCommentInUserDetail = (id, active) => {
return {
type: active ? actions.SELECT_USER_DETAIL_COMMENT : actions.UNSELECT_USER_DETAIL_COMMENT,
id
};
};
export const toggleStorySearch = (active) => ({
type: active ? actions.SHOW_STORY_SEARCH : actions.HIDE_STORY_SEARCH
});
export const storySearchChange = (value) => ({
type: actions.STORY_SEARCH_CHANGE_VALUE,
value
});
export const clearState = () => ({
type: actions.MODERATION_CLEAR_STATE
});
+7 -2
View File
@@ -1,4 +1,5 @@
import coralApi from '../../../coral-framework/helpers/request';
import t from 'coral-framework/services/i18n';
export const SETTINGS_LOADING = 'SETTINGS_LOADING';
export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED';
@@ -20,7 +21,9 @@ export const fetchSettings = () => (dispatch) => {
dispatch({type: SETTINGS_RECEIVED, settings});
})
.catch((error) => {
dispatch({type: SETTINGS_FETCH_ERROR, error});
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch({type: SETTINGS_FETCH_ERROR, error: errorMessage});
});
};
@@ -49,6 +52,8 @@ export const saveSettingsToServer = () => (dispatch, getState) => {
dispatch({type: SAVE_SETTINGS_SUCCESS, settings});
})
.catch((error) => {
dispatch({type: SAVE_SETTINGS_FAILED, error});
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch({type: SAVE_SETTINGS_FAILED, error: errorMessage});
});
};
@@ -0,0 +1,7 @@
import {SHOW_SUSPEND_USER_DIALOG, HIDE_SUSPEND_USER_DIALOG} from '../constants/suspendUserDialog.js';
export const showSuspendUserDialog = ({userId, username, commentId, commentStatus}) =>
({type: SHOW_SUSPEND_USER_DIALOG, userId, username, commentId, commentStatus});
export const hideSuspendUserDialog = () => ({type: HIDE_SUSPEND_USER_DIALOG});
+16 -3
View File
@@ -1,5 +1,6 @@
import coralApi from '../../../coral-framework/helpers/request';
import * as userTypes from '../constants/users';
import t from 'coral-framework/services/i18n';
/**
* Action disptacher related to users
@@ -10,7 +11,11 @@ export const userStatusUpdate = (status, userId, commentId) => {
dispatch({type: userTypes.UPDATE_STATUS_REQUEST});
return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
.then((res) => dispatch({type: userTypes.UPDATE_STATUS_SUCCESS, res}))
.catch((error) => dispatch({type: userTypes.UPDATE_STATUS_FAILURE, error}));
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch({type: userTypes.UPDATE_STATUS_FAILURE, error: errorMessage});
});
};
};
@@ -18,7 +23,11 @@ export const userStatusUpdate = (status, userId, commentId) => {
export const sendNotificationEmail = (userId, subject, body) => {
return (dispatch) => {
return coralApi(`/users/${userId}/email`, {method: 'POST', body: {subject, body}})
.catch((error) => dispatch({type: userTypes.USER_EMAIL_FAILURE, error}));
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch({type: userTypes.USER_EMAIL_FAILURE, error: errorMessage});
});
};
};
@@ -26,6 +35,10 @@ export const sendNotificationEmail = (userId, subject, body) => {
export const enableUsernameEdit = (userId) => {
return (dispatch) => {
return coralApi(`/users/${userId}/username-enable`, {method: 'POST'})
.catch((error) => dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error}));
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error: errorMessage});
});
};
};
@@ -1,25 +1,27 @@
import React, {PropTypes} from 'react';
import styles from './ModerationList.css';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap';
import {menuActionsMap} from '../routes/Moderation/helpers/moderationQueueActionsMap';
import t from 'coral-framework/services/i18n';
const ActionButton = ({type = '', active, ...props}) => {
const typeName = type.toLowerCase();
let text = menuActionsMap[type].text;
if (text === 'Approve' && active) {
text = 'Approved';
} else if (text === 'Reject' && active) {
text = 'Rejected';
if (text === 'approve' && active) {
text = 'approved';
} else if (text === 'reject' && active) {
text = 'rejected';
}
return (
<Button
className={`${typeName} ${styles.actionButton} ${active ? styles[`${typeName}__active`] : ''}`}
className={`${typeName} ${styles.actionButton} ${props.minimal ? styles.minimal : ''} ${active ? styles[`${typeName}__active`] : ''}`}
cStyle={typeName}
icon={menuActionsMap[type].icon}
onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment}
>{text}</Button>
>{props.minimal ? '' : t(`modqueue.${text}`)}</Button>
);
};
@@ -5,9 +5,7 @@ import cn from 'classnames';
import {findDOMNode} from 'react-dom';
import styles from './ActionsMenu.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
let count = 0;
@@ -43,7 +41,7 @@ class ActionsMenu extends React.Component {
onClick={this.syncOpenState}
icon={this.props.icon}
raised>
{lang.t('modqueue.actions')}
{t('modqueue.actions')}
<Icon
name={this.state.open ? 'keyboard_arrow_up' : 'keyboard_arrow_down'}
className={styles.arrowIcon}
@@ -2,10 +2,7 @@ import React, {PropTypes} from 'react';
import Layout from 'coral-admin/src/components/ui/Layout';
import styles from './NotFound.css';
import {Button, TextField, Alert, Success} from 'coral-ui';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
import Recaptcha from 'react-recaptcha';
const lang = new I18n(translations);
class AdminLogin extends React.Component {
@@ -37,7 +34,7 @@ class AdminLogin extends React.Component {
const {errorMessage, loginMaxExceeded, recaptchaPublic} = this.props;
const signInForm = (
<form onSubmit={this.handleSignIn}>
{errorMessage && <Alert>{lang.t(`errors.${errorMessage}`)}</Alert>}
{errorMessage && <Alert>{errorMessage}</Alert>}
<TextField
label='Email Address'
value={this.state.email}
@@ -0,0 +1,44 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import t from 'coral-framework/services/i18n';
const BanUserDialog = ({open, onCancel, onPerform, username, info}) => (
<Dialog
className={styles.dialog}
id="banUserDialog"
open={open}
onCancel={onCancel}
title={t('bandialog.ban_user')}>
<span className={styles.close} onClick={onCancel}>×</span>
<div>
<div className={styles.header}>
<h2>{t('bandialog.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{t('bandialog.are_you_sure', username)}</h3>
<i>{info}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={onCancel} raised>
{t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={onPerform} raised>
{t('bandialog.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
BanUserDialog.propTypes = {
open: PropTypes.bool,
onPerform: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
username: PropTypes.string,
info: PropTypes.string,
};
export default BanUserDialog;
@@ -1,17 +1,14 @@
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
import React, {PropTypes} from 'react';
import Modal from 'components/Modal';
import styles from './ModerationKeysModal.css';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const shortcuts = [
{
title: 'modqueue.navigation',
shortcuts: {
'j': 'modqueue.nextcomment',
'k': 'modqueue.prevcomment',
'j': 'modqueue.next_comment',
'k': 'modqueue.prev_comment',
's': 'modqueue.singleview',
'?': 'modqueue.thismenu'
}
@@ -19,8 +16,8 @@ const shortcuts = [
{
title: 'modqueue.actions',
shortcuts: {
't': 'modqueue.approve',
'r': 'modqueue.reject'
'd': 'modqueue.approve',
'f': 'modqueue.reject'
}
}
];
@@ -40,29 +37,29 @@ export default class ModerationKeysModal extends React.Component {
<div>
<div className={styles.callToAction} style={{display: shortcutsNoteVisible === 'show' ? 'block' : 'none'}}>
<div onClick={hideShortcutsNote} className={styles.closeButton}>×</div>
<p className={styles.ctaHeader}>{lang.t('modqueue.mod-faster')}</p>
<p><strong>{lang.t('modqueue.try-these')}:</strong></p>
<p className={styles.ctaHeader}>{t('modqueue.mod_faster')}</p>
<p><strong>{t('modqueue.try_these')}:</strong></p>
<ul>
<li><span>{lang.t('modqueue.approve')}</span> <span className={styles.smallKey}>t</span></li>
<li><span>{lang.t('modqueue.reject')}</span> <span className={styles.smallKey}>r</span></li>
<li><span>{t('modqueue.approve')}</span> <span className={styles.smallKey}>d</span></li>
<li><span>{t('modqueue.reject')}</span> <span className={styles.smallKey}>f</span></li>
</ul>
<p><span>{lang.t('modqueue.view-more-shortcuts')}</span> <span className={styles.smallKey}>{lang.t('modqueue.shift-key')}</span> + <span className={styles.smallKey}>/</span></p>
<p><span>{t('modqueue.view_more_shortcuts')}</span> <span className={styles.smallKey}>{t('modqueue.shift_key')}</span> + <span className={styles.smallKey}>/</span></p>
</div>
<Modal open={open} onClose={onClose}>
<h3>{lang.t('modqueue.shortcuts')}</h3>
<h3>{t('modqueue.shortcuts')}</h3>
<div className={styles.container}>
{shortcuts.map((shortcut, i) => (
<table className={styles.table} key={i}>
<thead>
<tr>
<th>{lang.t(shortcut.title)}</th>
<th>{t(shortcut.title)}</th>
</tr>
</thead>
<tbody>
{Object.keys(shortcut.shortcuts).map((key) => (
<tr key={`${key}tr`}>
<td className={styles.shortcut}><span className={styles.key}>{key}</span></td>
<td>{lang.t(shortcut.shortcuts[key])}</td>
<td>{t(shortcut.shortcuts[key])}</td>
</tr>
))}
</tbody>
@@ -189,6 +189,12 @@
width: 140px;
}
.minimal {
width: 45px;
min-width: 0;
float: left;
}
.approve__active {
box-shadow: none;
color: white;
@@ -8,8 +8,8 @@ import SuspendUserModal from './SuspendUserModal';
// Each action has different meaning and configuration
const menuOptionsMap = {
'reject': {status: 'REJECTED', icon: 'close', key: 'r'},
'approve': {status: 'ACCEPTED', icon: 'done', key: 't'},
'reject': {status: 'REJECTED', icon: 'close', key: 'f'},
'approve': {status: 'ACCEPTED', icon: 'done', key: 'd'},
'flag': {status: 'FLAGGED', icon: 'flag', filter: 'Untouched'},
'ban': {status: 'BANNED', icon: 'not interested'}
};
@@ -5,10 +5,8 @@ import styles from './SuspendUserDialog.css';
import Button from 'coral-ui/components/Button';
import I18n from 'coral-framework/modules/i18n/i18n';
import t, {timeago} from 'coral-framework/services/i18n';
import {dateAdd} from 'coral-framework/utils';
import translations from '../../../translations';
const lang = new I18n(translations);
const initialState = {step: 0, duration: '3'};
@@ -39,11 +37,11 @@ class SuspendUserDialog extends React.Component {
goToStep1 = () => {
this.setState({
step: 1,
message: lang.t(
message: t(
'suspenduser.email_message_suspend',
this.props.username,
this.props.organizationName,
lang.timeago(durationsToDate(this.state.duration)),
timeago(durationsToDate(this.state.duration)),
),
});
}
@@ -51,7 +49,6 @@ class SuspendUserDialog extends React.Component {
handlePerform = () => {
this.props.onPerform({
id: this.props.userId,
message: this.state.message,
// Add 1 minute more to help `timeago.js` to display the correct duration.
@@ -65,13 +62,13 @@ class SuspendUserDialog extends React.Component {
return (
<section>
<h1 className={styles.header}>
{lang.t('suspenduser.title_suspend')}
{t('suspenduser.title_suspend')}
</h1>
<p className={styles.description}>
{lang.t('suspenduser.description_suspend', username)}
{t('suspenduser.description_suspend', username)}
</p>
<fieldset>
<legend className={styles.legend}>{lang.t('suspenduser.select_duration')}</legend>
<legend className={styles.legend}>{t('suspenduser.select_duration')}</legend>
<RadioGroup
name='status filter'
value={duration}
@@ -79,18 +76,18 @@ class SuspendUserDialog extends React.Component {
onChange={this.handleDurationChange}
className={styles.radioGroup}
>
<Radio value='1'>{lang.t('suspenduser.one_hour')}</Radio>
<Radio value='3'>{lang.t('suspenduser.hours', 3)}</Radio>
<Radio value='24'>{lang.t('suspenduser.hours', 24)}</Radio>
<Radio value='168'>{lang.t('suspenduser.days', 7)}</Radio>
<Radio value='1'>{t('suspenduser.one_hour')}</Radio>
<Radio value='3'>{t('suspenduser.hours', 3)}</Radio>
<Radio value='24'>{t('suspenduser.hours', 24)}</Radio>
<Radio value='168'>{t('suspenduser.days', 7)}</Radio>
</RadioGroup>
</fieldset>
<div className={styles.buttons}>
<Button cStyle="white" className={styles.cancel} onClick={onCancel} raised>
{lang.t('suspenduser.cancel')}
{t('suspenduser.cancel')}
</Button>
<Button cStyle="black" className={styles.perform} onClick={this.goToStep1} raised>
{lang.t('suspenduser.suspend_user')}
{t('suspenduser.suspend_user')}
</Button>
</div>
</section>
@@ -103,13 +100,13 @@ class SuspendUserDialog extends React.Component {
return (
<section>
<h1 className={styles.header}>
{lang.t('suspenduser.title_notify')}
{t('suspenduser.title_notify')}
</h1>
<p className={styles.description}>
{lang.t('suspenduser.description_notify', username)}
{t('suspenduser.description_notify', username)}
</p>
<fieldset>
<legend className={styles.legend}>{lang.t('suspenduser.write_message')}</legend>
<legend className={styles.legend}>{t('suspenduser.write_message')}</legend>
<textarea
rows={5}
className={styles.messageInput}
@@ -118,7 +115,7 @@ class SuspendUserDialog extends React.Component {
</fieldset>
<div className={styles.buttons}>
<Button cStyle="white" className={styles.cancel} onClick={onCancel} raised>
{lang.t('suspenduser.cancel')}
{t('suspenduser.cancel')}
</Button>
<Button
cStyle="black"
@@ -127,7 +124,7 @@ class SuspendUserDialog extends React.Component {
disabled={this.state.message.length === 0}
raised
>
{lang.t('suspenduser.send')}
{t('suspenduser.send')}
</Button>
</div>
</section>
@@ -155,11 +152,10 @@ class SuspendUserDialog extends React.Component {
SuspendUserDialog.propTypes = {
open: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onPerform: PropTypes.func.isRequired,
username: PropTypes.string,
userId: PropTypes.string,
onCancel: PropTypes.func.isRequired,
organizationName: PropTypes.string,
username: PropTypes.string,
};
export default SuspendUserDialog;
@@ -0,0 +1,51 @@
.root {
background-color: #fff;
border: 1px solid #ccc;
overflow: hidden;
padding-left: 5px;
padding-top: 5px;
}
.rootFocuesd {
border-color: rgb(142, 76, 65);
}
.tag {
background-color: rgb(255, 220, 214);
border-radius: 2px;
border: 1px solid rgb(244, 126, 107);
color: rgb(244, 126, 107);
display: inline-block;
font-family: sans-serif;
font-size: 13px;
font-weight: 400;
margin-bottom: 5px;
margin-right: 5px;
padding: 5px;
}
.tagRemove {
cursor: pointer;
font-weight: bold;
color: rgb(101, 24, 23);
&::before {
content: " ×";
}
}
.input > input {
background: transparent;
border: 0;
color: #777;
font-family: sans-serif;
font-size: 13px;
font-weight: 400;
margin-bottom: 6px;
margin-top: 1px;
outline: none;
padding: 5px;
}
@@ -0,0 +1,31 @@
import React from 'react';
import TagsInput from 'react-tagsinput';
import styles from './TagsInput.css';
import AutosizeInput from 'react-input-autosize';
const autosizingRenderInput = ({onChange, value, addTag: _, ...other}) =>
<AutosizeInput type='text' onChange={onChange} value={value} {...other} />;
export default (props) => {
return (
<TagsInput
addOnBlur={true}
addOnPaste={true}
pasteSplit={(data) => data.split(',').map((d) => d.trim())}
className={styles.root}
focusedClassName={styles.rootFocus}
renderInput={autosizingRenderInput}
{...props}
tagProps={{
className: styles.tag,
classNameRemove: styles.tagRemove,
...props.tagProps,
}}
inputProps={{
className: styles.input,
...props.inputProps,
}}
/>
);
};
@@ -133,7 +133,7 @@
animation-fill-mode: both; }
.toastify {
z-index: 999;
z-index: 99999;
position: fixed;
padding: 4px;
width: 350px;
@@ -197,7 +197,7 @@
border-radius: 2px;
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1), 0 3px 20px 0 rgba(0, 0, 0, 0.05); }
.toastify-content--info {
background: #2488cb; }
background: #404040; }
.toastify-content--success {
background: #008577; }
.toastify-content--warning {
@@ -207,6 +207,7 @@
.toastify__body {
color: white;
overflow-x: scroll;
font-size: 15px;
font-weight: 400;
}
@@ -2,8 +2,7 @@ import React, {PropTypes} from 'react';
import {Navigation, Drawer} from 'react-mdl';
import {IndexLink, Link} from 'react-router';
import styles from './Drawer.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import t from 'coral-framework/services/i18n';
import {can} from 'coral-framework/services/perms';
const CoralDrawer = ({handleLogout, auth}) => (
@@ -15,7 +14,7 @@ const CoralDrawer = ({handleLogout, auth}) => (
className={styles.navLink}
to="/admin/dashboard"
activeClassName={styles.active}>
{lang.t('configure.dashboard')}
{t('configure.dashboard')}
</IndexLink>
{
can(auth.user, 'MODERATE_COMMENTS') && (
@@ -23,19 +22,19 @@ const CoralDrawer = ({handleLogout, auth}) => (
className={styles.navLink}
to="/admin/moderate"
activeClassName={styles.active}>
{lang.t('configure.moderate')}
{t('configure.moderate')}
</Link>
)
}
<Link className={styles.navLink}
to="/admin/stories"
activeClassName={styles.active}>
{lang.t('configure.stories')}
{t('configure.stories')}
</Link>
<Link className={styles.navLink}
to="/admin/community"
activeClassName={styles.active}>
{lang.t('configure.community')}
{t('configure.community')}
</Link>
{
can(auth.user, 'UPDATE_CONFIG') &&
@@ -44,7 +43,7 @@ const CoralDrawer = ({handleLogout, auth}) => (
className={styles.navLink}
to="/admin/configure"
activeClassName={styles.active}>
{lang.t('configure.configure')}
{t('configure.configure')}
</Link>
)
}
@@ -60,6 +59,4 @@ CoralDrawer.propTypes = {
restricted: PropTypes.bool // hide app elements from a logged out user
};
const lang = new I18n(translations);
export default CoralDrawer;
@@ -40,12 +40,12 @@
height: 100%;
}
.rightPanel ul {
.rightPanel > ul {
list-style: none;
margin-right: 20px;
}
.rightPanel li {
.rightPanel > ul > li {
display: inline-block;
float: right;
margin-left: 15px;
@@ -54,6 +54,12 @@
line-height: 33px;
}
.rightPanel a {
color: black;
text-decoration: none;
font-weight: 400;
}
.rightPanel .settings {
line-height: 0;
}
@@ -73,13 +79,16 @@
font-weight: 500;
background-color: transparent;
transition: background-color 200ms;
font-weight: 100;
letter-spacing: .8;
&:hover {
background-color: #232323;
background-color: #404040;
}
&.active {
background-color: #232323;
font-weight: 400;
}
}
+22 -13
View File
@@ -2,8 +2,7 @@ import React, {PropTypes} from 'react';
import {Navigation, Header, IconButton, MenuItem, Menu} from 'react-mdl';
import {Link, IndexLink} from 'react-router';
import styles from './Header.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import t from 'coral-framework/services/i18n';
import {Logo} from './Logo';
import {can} from 'coral-framework/services/perms';
@@ -23,7 +22,7 @@ const CoralHeader = ({
className={styles.navLink}
to="/admin/dashboard"
activeClassName={styles.active}>
{lang.t('configure.dashboard')}
{t('configure.dashboard')}
</IndexLink>
{
can(auth.user, 'MODERATE_COMMENTS') && (
@@ -32,7 +31,7 @@ const CoralHeader = ({
className={styles.navLink}
to="/admin/moderate"
activeClassName={styles.active}>
{lang.t('configure.moderate')}
{t('configure.moderate')}
</Link>
)
}
@@ -41,14 +40,14 @@ const CoralHeader = ({
className={styles.navLink}
to="/admin/stories"
activeClassName={styles.active}>
{lang.t('configure.stories')}
{t('configure.stories')}
</Link>
<Link
id='communityNav'
className={styles.navLink}
to="/admin/community"
activeClassName={styles.active}>
{lang.t('configure.community')}
{t('configure.community')}
</Link>
{
can(auth.user, 'UPDATE_CONFIG') && (
@@ -57,7 +56,7 @@ const CoralHeader = ({
className={styles.navLink}
to="/admin/configure"
activeClassName={styles.active}>
{lang.t('configure.configure')}
{t('configure.configure')}
</Link>
)
}
@@ -71,13 +70,25 @@ const CoralHeader = ({
<div>
<IconButton name="settings" id="menu-settings"/>
<Menu target="menu-settings" align="right">
<MenuItem onClick={() => showShortcuts(true)}>{lang.t('configure.shortcuts')}</MenuItem>
<MenuItem onClick={handleLogout}>{lang.t('configure.sign-out')}</MenuItem>
<MenuItem onClick={() => showShortcuts(true)}>{t('configure.shortcuts')}</MenuItem>
<MenuItem>
<a href="https://github.com/coralproject/talk/releases" target="_blank">
View latest version
</a>
</MenuItem>
<MenuItem>
<a href="https://coralproject.net/contribute.html#other-ideas-and-bug-reports" target="_blank">
Report a bug or give feedback
</a>
</MenuItem>
<MenuItem onClick={handleLogout}>
{t('configure.sign_out')}
</MenuItem>
</Menu>
</div>
</li>
<li>
{`v${process.env.VERSION}`}
<li>
{`v${process.env.VERSION}`}
</li>
</ul>
</div>
@@ -91,6 +102,4 @@ CoralHeader.propTypes = {
handleLogout: PropTypes.func.isRequired
};
const lang = new I18n(translations);
export default CoralHeader;
@@ -1,6 +1,5 @@
.layout {
max-width: 1280px;
margin: 0 auto;
overflow: hidden;
background-color: #FAFAFA;
}
@@ -10,7 +10,7 @@ const Layout = ({
toggleShortcutModal,
restricted = false,
...props}) => (
<LayoutMDL fixedDrawer>
<LayoutMDL className={styles.layout} fixedDrawer>
<Header
handleLogout={handleLogout}
showShortcuts={toggleShortcutModal}
@@ -0,0 +1,2 @@
export const SHOW_BAN_USER_DIALOG = 'SHOW_BAN_USER_DIALOG';
export const HIDE_BAN_USER_DIALOG = 'HIDE_BAN_USER_DIALOG';
@@ -13,5 +13,5 @@ export const FETCH_FLAGGED_COMMENTERS_FAILURE = 'FETCH_FLAGGED_COMMENTERS_FAILUR
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
export const SHOW_SUSPENDUSER_DIALOG = 'SHOW_SUSPENDUSER_DIALOG';
export const HIDE_SUSPENDUSER_DIALOG = 'HIDE_SUSPENDUSER_DIALOG';
export const SHOW_REJECT_USERNAME_DIALOG = 'SHOW_REJECT_USERNAME_DIALOG';
export const HIDE_REJECT_USERNAME_DIALOG = 'HIDE_REJECT_USERNAME_DIALOG';
@@ -1,9 +1,14 @@
export const TOGGLE_MODAL = 'TOGGLE_MODAL';
export const SINGLE_VIEW = 'SINGLE_VIEW';
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
export const HIDE_SHORTCUTS_NOTE = 'HIDE_SHORTCUTS_NOTE';
export const SHOW_SUSPEND_USER_DIALOG = 'SHOW_SUSPEND_USER_DIALOG';
export const HIDE_SUSPEND_USER_DIALOG = 'HIDE_SUSPEND_USER_DIALOG';
export const VIEW_USER_DETAIL = 'VIEW_USER_DETAIL';
export const HIDE_USER_DETAIL = 'HIDE_USER_DETAIL';
export const SET_SORT_ORDER = 'MODERATION_SET_SORT_ORDER';
export const CHANGE_USER_DETAIL_STATUSES = 'CHANGE_USER_DETAIL_STATUSES';
export const SELECT_USER_DETAIL_COMMENT = 'SELECT_USER_DETAIL_COMMENT';
export const UNSELECT_USER_DETAIL_COMMENT = 'UNSELECT_USER_DETAIL_COMMENT';
export const CLEAR_USER_DETAIL_SELECTIONS = 'CLEAR_USER_DETAIL_SELECTIONS';
export const SHOW_STORY_SEARCH = 'SHOW_STORY_SEARCH';
export const HIDE_STORY_SEARCH = 'HIDE_STORY_SEARCH';
export const STORY_SEARCH_CHANGE_VALUE = 'STORY_SEARCH_CHANGE_VALUE';
export const MODERATION_CLEAR_STATE = 'MODERATION_CLEAR_STATE';
@@ -0,0 +1,2 @@
export const SHOW_SUSPEND_USER_DIALOG = 'SHOW_SUSPEND_USER_DIALOG';
export const HIDE_SUSPEND_USER_DIALOG = 'HIDE_SUSPEND_USER_DIALOG';
@@ -0,0 +1,63 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import BanUserDialog from '../components/BanUserDialog';
import {hideBanUserDialog} from '../actions/banUserDialog';
import {withSetUserStatus, withSetCommentStatus} from 'coral-framework/graphql/mutations';
import {compose} from 'react-apollo';
import t from 'coral-framework/services/i18n';
class BanUserDialogContainer extends Component {
banUser = async () => {
const {userId, commentId, commentStatus, setUserStatus, setCommentStatus, hideBanUserDialog} = this.props;
await setUserStatus({userId, status: 'BANNED'});
hideBanUserDialog();
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
await setCommentStatus({commentId, status: 'REJECTED'});
}
}
getInfo() {
let note = t('bandialog.note_ban_user');
if (this.props.commentStatus && this.props.commentStatus !== 'REJECTED') {
note = t('bandialog.note_reject_comment');
}
return t('bandialog.note', note);
}
render() {
return (
<BanUserDialog
open={this.props.open}
onPerform={this.banUser}
onCancel={this.props.hideBanUserDialog}
username={this.props.username}
info={this.getInfo()}
/>
);
}
}
const mapStateToProps = ({banUserDialog: {open, userId, username, commentId, commentStatus}}) => ({
open,
userId,
username,
commentId,
commentStatus,
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
hideBanUserDialog,
}, dispatch),
});
export default compose(
withSetUserStatus,
withSetCommentStatus,
connect(
mapStateToProps,
mapDispatchToProps,
),
)(BanUserDialogContainer);
@@ -1,169 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {compose} from 'react-apollo';
import {modUserFlaggedQuery} from 'coral-admin/src/graphql/queries';
import {banUser, setUserStatus, rejectUsername} from 'coral-admin/src/graphql/mutations';
import {
fetchAccounts,
updateSorting,
newPage,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog
} from '../../actions/community';
import CommunityMenu from './components/CommunityMenu';
import BanUserDialog from './components/BanUserDialog';
import SuspendUserDialog from './components/SuspendUserDialog';
import People from './People';
import FlaggedAccounts from './FlaggedAccounts';
class CommunityContainer extends Component {
constructor(props) {
super(props);
this.state = {
searchValue: '',
timer: null
};
this.onKeyDownHandler = this.onKeyDownHandler.bind(this);
this.onSearchChange = this.onSearchChange.bind(this);
this.onHeaderClickHandler = this.onHeaderClickHandler.bind(this);
this.onNewPageHandler = this.onNewPageHandler.bind(this);
}
componentWillMount() {
this.props.fetchAccounts({});
}
onKeyDownHandler(e) {
if (e.key === 'Enter') {
e.preventDefault();
this.search();
}
}
onSearchChange(e) {
const value = e.target.value;
this.setState((prevState) => {
prevState.searchValue = value;
clearTimeout(prevState.timer);
const fetchAccounts = this.props.fetchAccounts;
prevState.timer = setTimeout(() => {
fetchAccounts({value});
}, 350);
return prevState;
});
}
search(query = {}) {
const {community} = this.props;
this.props.fetchAccounts({
value: this.state.searchValue,
field: community.fieldPeople,
asc: community.ascPeople,
...query
});
}
onHeaderClickHandler(sort) {
this.props.dispatch(updateSorting(sort));
this.search();
}
onNewPageHandler(page) {
this.props.dispatch(newPage(page));
this.search({page});
}
getTabContent(searchValue, props) {
const {community, data} = props;
const activeTab = props.route.path === ':id' ? 'flagged' : props.route.path;
if (activeTab === 'people') {
return (
<People
isFetching={community.isFetchingPeople}
commenters={community.accounts}
searchValue={searchValue}
onSearchChange={this.onSearchChange}
error={community.errorPeople}
totalPages={community.totalPagesPeople}
page={community.pagePeople}
onKeyDown={this.onKeyDownHandler}
onHeaderClickHandler={this.onHeaderClickHandler}
onNewPageHandler={this.onNewPageHandler}
/>
);
}
return (
<div>
<FlaggedAccounts
commenters={data.users}
isFetching={data.loading}
error={data.error}
showBanUserDialog={props.showBanUserDialog}
approveUser={props.approveUser}
rejectUsername={props.rejectUsername}
showSuspendUserDialog={props.showSuspendUserDialog}
/>
<BanUserDialog
open={community.banDialog}
user={community.user}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
/>
<SuspendUserDialog
open={community.suspendDialog}
handleClose={props.hideSuspendUserDialog}
user={community.user}
rejectUsername={props.rejectUsername}
/>
</div>
);
}
render() {
const {searchValue} = this.state;
const tab = this.getTabContent(searchValue, this.props);
return (
<div>
<CommunityMenu />
<div>
{ tab }
</div>
</div>
);
}
}
const mapStateToProps = (state) => ({
community: state.community.toJS()
});
const mapDispatchToProps = (dispatch) => ({
fetchAccounts: (query) => dispatch(fetchAccounts(query)),
showBanUserDialog: (user) => dispatch(showBanUserDialog(user)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
showSuspendUserDialog: (user) => dispatch(showSuspendUserDialog(user)),
hideSuspendUserDialog: () => dispatch(hideSuspendUserDialog())
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
modUserFlaggedQuery,
banUser,
setUserStatus,
rejectUsername
)(CommunityContainer);
@@ -1,7 +0,0 @@
import React from 'react';
const Loading = () => (
<h1> Loading results </h1>
);
export default Loading;
@@ -1,81 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {SelectField, Option} from 'react-mdl-selectfield';
import styles from './Community.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations';
import {setRole, setCommenterStatus} from '../../actions/community';
const lang = new I18n(translations);
class Table extends Component {
constructor (props) {
super(props);
this.onRoleChange = this.onRoleChange.bind(this);
}
onRoleChange (id, role) {
this.props.dispatch(setRole(id, role));
}
onCommenterStatusChange (id, status) {
this.props.dispatch(setCommenterStatus(id, status));
}
render () {
const {headers, commenters, onHeaderClickHandler} = this.props;
return (
<table className={`mdl-data-table ${styles.dataTable}`}>
<thead>
<tr>
{headers.map((header, i) =>(
<th
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
{header.title}
</th>
))}
</tr>
</thead>
<tbody>
{commenters.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.username}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
{row.created_at}
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.status || ''}
className={styles.selectField}
label={lang.t('community.status')}
onChange={(status) => this.onCommenterStatusChange(row.id, status)}>
<Option value={'ACTIVE'}>{lang.t('community.active')}</Option>
<Option value={'BANNED'}>{lang.t('community.banned')}</Option>
</SelectField>
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.roles[0] || ''}
className={styles.selectField}
label={lang.t('community.role')}
onChange={(role) => this.onRoleChange(row.id, role)}>
<Option value={''}>.</Option>
<Option value={'STAFF'}>{lang.t('community.staff')}</Option>
<Option value={'MODERATOR'}>{lang.t('community.moderator')}</Option>
<Option value={'ADMIN'}>{lang.t('community.admin')}</Option>
</SelectField>
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default connect((state) => ({commenters: state.community.get('accounts')}))(Table);
@@ -1,11 +0,0 @@
.banButton {
-webkit-transform: scale(.8);
transform: scale(.8);
margin: 0;
i {
vertical-align: middle;
margin-right: 5px;
font-size: 14px;
}
}
@@ -1,26 +0,0 @@
import React, {PropTypes} from 'react';
import {Button, Icon} from 'coral-ui';
import styles from './BanUserButton.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
const BanUserButton = ({user, ...props}) => (
<div className={styles.ban}>
<Button cStyle='ban'
className={`ban ${styles.banButton}`}
disabled={user.status === 'BANNED' ? 'disabled' : ''}
onClick={props.onClick}
raised>
<Icon name='not_interested' />
{lang.t('comment.ban_user')}
</Button>
</div>
);
BanUserButton.propTypes = {
onClick: PropTypes.func.isRequired
};
export default BanUserButton;
@@ -1,164 +0,0 @@
.dialog {
border: none;
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
width: 500px;
top: 50%;
transform: translateY(-50%);
height: 184px;
padding: 20px;
h2 {
color: black;
font-size: 1.76em;
font-weight: 500;
margin: 0;
}
h3 {
color: black;
font-size: 1.4em;
font-weight: 500;
margin: 0;
}
}
.textField {
margin-top: 15px;
}
.textField label {
font-size: 1.08em;
font-weight: bold;
margin-bottom: 5px;
}
.textField input {
width: 100%;
display: block;
border: none;
outline: none;
border: 1px solid rgba(0,0,0,.12);
padding: 10px 6px;
box-sizing: border-box;
border-radius: 2px;
margin: 5px auto;
}
.footer {
margin: 20px auto 10px;
text-align: center;
}
.footer span {
display: block;
margin-bottom: 5px;
}
.footer a {
color: #2c69b6;
cursor: pointer;
margin: 0 5px;
}
.socialConnections {
margin-bottom: 20px;
}
.signInButton {
margin-top: 10px;
}
.close {
font-size: 20px;
line-height: 14px;
top: 10px;
right: 10px;
position: absolute;
display: block;
font-weight: bold;
color: #363636;
cursor: pointer;
}
.close:hover {
color: #6b6b6b;
}
input.error{
border: solid 2px #f44336;
}
.errorMsg, .hint {
color: grey;
font-weight: 600;
padding: 3px 0 16px;
}
.alert {
padding: 10px;
margin-bottom: 20px;
border-radius: 2px;
}
.alert--success {
border: solid 1px #1ec00e;
background: #cbf1b8;
color: #006900;
}
.alert--error {
background: #FFEBEE;
color: #B71C1C;
}
.userBox a {
color: #2c69b6;
cursor: pointer;
margin: 0px;
}
.attention {
display: inline-block;
width: 15px;
height: 15px;
background: #B71C1C;
color: #FFEBEE;
font-weight: bolder;
padding: 4px;
vertical-align: middle;
border-radius: 20px;
box-sizing: border-box;
font-size: 9px;
line-height: 7px;
text-align: center;
margin-right: 5px;
}
.action {
margin-top: 15px;
}
.passwordRequestSuccess {
border: 1px solid green;
background-color: lightgreen;
padding: 10px;
}
.passwordRequestFailure {
border: 1px solid orange;
background-color: 1px solid coral;
padding: 10px;
}
.cancel {
margin-right: 10px;
width: 47%;
}
.ban {
width: 47%;
}
.buttons {
margin: 20px 0;
}
@@ -1,54 +0,0 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={lang.t('community.ban_user')}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h2>{lang.t('community.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{lang.t('community.are_you_sure', user.username)}</h3>
<i>{lang.t('community.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{lang.t('community.cancel')}
</Button>
<Button
cStyle="black" className={styles.ban}
onClick={() => {
handleBanUser({userId: user.id}).then(() => {
handleClose();
});
}}
raised>
{lang.t('community.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
BanUserDialog.propTypes = {
handleBanUser: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
};
export default BanUserDialog;
@@ -1,48 +0,0 @@
import React from 'react';
import styles from './Dashboard.css';
import {compose} from 'react-apollo';
import {connect} from 'react-redux';
import {getMetrics} from 'coral-admin/src/graphql/queries';
import FlagWidget from './FlagWidget';
import ActivityWidget from './ActivityWidget';
import CountdownTimer from 'coral-admin/src/components/CountdownTimer';
import {Spinner} from 'coral-ui';
class Dashboard extends React.Component {
reloadData = () => {
this.props.data.refetch();
}
render () {
if (this.props.data && this.props.data.loading) {
return <Spinner />;
}
const {data: {assetsByActivity, assetsByFlag}} = this.props;
return (
<div>
<CountdownTimer handleTimeout={this.reloadData} />
<div className={styles.Dashboard}>
<FlagWidget assets={assetsByFlag} />
<ActivityWidget assets={assetsByActivity} />
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
settings: state.settings.toJS(),
moderation: state.moderation.toJS()
};
};
export default compose(
connect(mapStateToProps),
getMetrics
)(Dashboard);
@@ -1,101 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import styles from './style.css';
import {Wizard, WizardNav} from 'coral-ui';
import Layout from 'coral-admin/src/components/ui/Layout';
import {
goToStep,
nextStep,
submitUser,
checkInstall,
previousStep,
finishInstall,
submitSettings,
updateUserFormData,
updateSettingsFormData,
updatePermittedDomains
} from '../../actions/install';
import InitialStep from './components/Steps/InitialStep';
import AddOrganizationName from './components/Steps/AddOrganizationName';
import CreateYourAccount from './components/Steps/CreateYourAccount';
import PermittedDomainsStep from './components/Steps/PermittedDomainsStep';
import FinalStep from './components/Steps/FinalStep';
class InstallContainer extends Component {
componentDidMount() {
const {checkInstall} = this.props;
checkInstall(() => {
this.context.router.push('/admin');
});
}
render() {
const {install} = this.props;
return (
<Layout restricted={true}>
<div className={styles.Install}>
{
!install.alreadyInstalled ? (
<div>
<h2>Welcome to the Coral Project</h2>
{ install.step !== 0 ? <WizardNav items={install.navItems} currentStep={install.step} icon='check'/> : null }
<Wizard currentStep={install.step} {...this.props}>
<InitialStep/>
<AddOrganizationName/>
<CreateYourAccount/>
<PermittedDomainsStep/>
<FinalStep/>
</Wizard>
</div>
) : (
<div>Talk is already installed</div>
)
}
</div>
</Layout>
);
}
}
InstallContainer.contextTypes = {
router: React.PropTypes.object
};
const mapStateToProps = (state) => ({
install: state.install.toJS()
});
const mapDispatchToProps = (dispatch) => ({
nextStep: () => dispatch(nextStep()),
goToStep: (step) => dispatch(goToStep(step)),
previousStep: () => dispatch(previousStep()),
finishInstall: () => dispatch(finishInstall()),
checkInstall: (next) => dispatch(checkInstall(next)),
handleDomainsChange: (value) => {
dispatch(updatePermittedDomains(value));
},
handleSettingsChange: (e) => {
const {name, value} = e.currentTarget;
dispatch(updateSettingsFormData(name, value));
},
handleUserChange: (e) => {
const {name, value} = e.currentTarget;
dispatch(updateUserFormData(name, value));
},
handleSettingsSubmit: (e) => {
e.preventDefault();
dispatch(submitSettings());
},
handleUserSubmit: (e) => {
e.preventDefault();
dispatch(submitUser());
}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(InstallContainer);
@@ -1,19 +0,0 @@
import React from 'react';
import styles from './style.css';
import {Button} from 'coral-ui';
const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const InitialStep = (props) => {
const {nextStep} = props;
return (
<div className={styles.step}>
<p>{lang.t('INITIAL.DESCRIPTION')}</p>
<Button cStyle='green' onClick={nextStep} raised>{lang.t('INITIAL.SUBMIT')}</Button>
</div>
);
};
export default InitialStep;
@@ -1,58 +0,0 @@
{
"en": {
"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",
"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"
}
},
"es": {
"INITIAL" : {
"DESCRIPTION": "Configuremos tu comunidad de Talk en sólo algunos pasos.",
"SUBMIT": "Empezá!"
},
"ADD_ORGANIZATION": {
"DESCRIPTION": "Por favor, dinos el nombre de tu organización. Este aparecerá en los emails cuando invites nuevos miembros.",
"LABEL": "Nombre de la Organización",
"SAVE": "Guardar"
},
"CREATE": {
"EMAIL": "Dirección de E-Mail",
"USERNAME": "Usuario",
"PASSWORD": "Contraseña",
"CONFIRM_PASSWORD": "Confirmar contraseña",
"SAVE": "Guardar"
},
"PERMITTED_DOMAINS": {
"TITLE": "Dominios Permitidos",
"DESCRIPTION": "Agrega dominios permitidos a Talk, e.g. tu localhost, staging y ambientes de production (ex. localhost:3000, staging.domain.com, domain.com).",
"SUBMIT": "Finalizar instalación"
},
"FINAL": {
"DESCRIPTION": "Gracias por instalar Talk! Te enviamos un email para verificar tu identidad. Mientras se termina de configurar la cuenta, ya puedes empezar a interactuar con tus lectores",
"LAUNCH": "Lanzar Talk",
"CLOSE": "Cerrar este instalador"
}
}
}
@@ -5,6 +5,8 @@ import {fetchConfig} from '../actions/config';
import AdminLogin from '../components/AdminLogin';
import {logout} from 'coral-framework/actions/auth';
import {FullLoading} from '../components/FullLoading';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import {toggleModal as toggleShortcutModal} from '../actions/moderation';
import {checkLogin, handleLogin, requestPasswordReset} from '../actions/auth';
import {can} from 'coral-framework/services/perms';
@@ -52,7 +54,11 @@ class LayoutContainer extends Component {
handleLogout={handleLogout}
toggleShortcutModal={toggleShortcutModal}
{...this.props}
/>
>
<BanUserDialog />
<SuspendUserDialog />
{this.props.children}
</Layout>
);
} else if (loggedIn) {
return (
@@ -1,298 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import * as notification from 'coral-admin/src/services/notification';
import key from 'keymaster';
import isEqual from 'lodash/isEqual';
import styles from './components/styles.css';
import translations from 'coral-admin/src/translations';
import I18n from 'coral-framework/modules/i18n/i18n';
import {modQueueQuery, getQueueCounts} from '../../graphql/queries';
import {banUser, setCommentStatus, suspendUser} from '../../graphql/mutations';
import {fetchSettings} from 'actions/settings';
import {updateAssets} from 'actions/assets';
import {
toggleModal,
singleView,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog,
hideShortcutsNote,
viewUserDetail,
hideUserDetail
} from 'actions/moderation';
import {Spinner} from 'coral-ui';
import BanUserDialog from './components/BanUserDialog';
import SuspendUserDialog from './components/SuspendUserDialog';
import ModerationQueue from './ModerationQueue';
import ModerationMenu from './components/ModerationMenu';
import ModerationHeader from './components/ModerationHeader';
import NotFoundAsset from './components/NotFoundAsset';
import ModerationKeysModal from '../../components/ModerationKeysModal';
import UserDetail from './UserDetail';
const lang = new I18n(translations);
class ModerationContainer extends Component {
state = {
selectedIndex: 0,
sort: 'REVERSE_CHRONOLOGICAL'
}
componentWillMount() {
const {toggleModal, singleView} = this.props;
this.props.fetchSettings();
key('s', () => singleView());
key('shift+/', () => toggleModal(true));
key('esc', () => toggleModal(false));
key('j', this.select(true));
key('k', this.select(false));
key('r', this.moderate(false));
key('t', this.moderate(true));
}
moderate = (accept) => () => {
const {acceptComment, rejectComment} = this.props;
const {selectedIndex} = this.state;
const comments = this.getComments();
const comment = comments[selectedIndex];
const commentId = {commentId: comment.id};
if (accept) {
comment.status !== 'ACCEPTED' && acceptComment(commentId);
} else {
comment.status !== 'REJECTED' && rejectComment(commentId);
}
}
getComments = () => {
const {data, route} = this.props;
const activeTab = route.path === ':id' ? 'premod' : route.path;
return data[activeTab];
}
select = (next) => () => {
if (next) {
this.setState((prevState) =>
({
...prevState,
selectedIndex: prevState.selectedIndex < this.getComments().length - 1
? prevState.selectedIndex + 1 : prevState.selectedIndex
}));
} else {
this.setState((prevState) =>
({
...prevState,
selectedIndex: prevState.selectedIndex > 0 ?
prevState.selectedIndex - 1 : prevState.selectedIndex
}));
}
}
selectSort = (sort) => {
this.setState({sort});
this.props.modQueueResort(sort);
}
suspendUser = async (args) => {
this.props.hideSuspendUserDialog();
try {
const result = await this.props.suspendUser(args);
if (result.data.suspendUser.errors) {
throw result.data.suspendUser.errors;
}
notification.success(
lang.t('suspenduser.notify_suspend_until',
this.props.moderation.suspendUserDialog.username,
lang.timeago(args.until)),
);
const {commentStatus, commentId} = this.props.moderation.suspendUserDialog;
if (commentStatus !== 'REJECTED') {
return this.props.rejectComment({commentId})
.then((result) => {
if (result.data.setCommentStatus.errors) {
throw result.data.setCommentStatus.errors;
}
});
}
}
catch(err) {
notification.showMutationErrors(err);
}
};
componentWillUnmount() {
key.unbind('s');
key.unbind('shift+/');
key.unbind('esc');
key.unbind('j');
key.unbind('k');
key.unbind('r');
key.unbind('t');
}
componentDidUpdate(_, prevState) {
// If paging through using keybaord shortcuts, scroll the page to keep the selected
// comment in view.
if (prevState.selectedIndex !== this.state.selectedIndex) {
// the 'smooth' flag only works in FF as of March 2017
document.querySelector(`.${styles.selected}`).scrollIntoView({behavior: 'smooth'});
}
}
componentWillReceiveProps(nextProps) {
const {updateAssets} = this.props;
if(!isEqual(nextProps.data.assets, this.props.data.assets)) {
updateAssets(nextProps.data.assets);
}
}
render () {
const {data, moderation, settings, assets, onClose, viewUserDetail, hideUserDetail, ...props} = this.props;
const providedAssetId = this.props.params.id;
const activeTab = this.props.route.path === ':id' ? 'premod' : this.props.route.path;
let asset;
if (!('premodCount' in data)) {
return <div><Spinner/></div>;
}
if (data.error) {
return <div>Error</div>;
}
if (providedAssetId) {
asset = assets.find((asset) => asset.id === this.props.params.id);
if (!asset) {
return <NotFoundAsset assetId={providedAssetId} />;
}
}
const comments = data[activeTab];
let activeTabCount;
switch(activeTab) {
case 'all':
activeTabCount = data.allCount;
break;
case 'accepted':
activeTabCount = data.acceptedCount;
break;
case 'premod':
activeTabCount = data.premodCount;
break;
case 'flagged':
activeTabCount = data.flaggedCount;
break;
case 'rejected':
activeTabCount = data.rejectedCount;
break;
}
return (
<div>
<ModerationHeader asset={asset} />
<ModerationMenu
asset={asset}
allCount={data.allCount}
acceptedCount={data.acceptedCount}
premodCount={data.premodCount}
rejectedCount={data.rejectedCount}
flaggedCount={data.flaggedCount}
selectSort={this.selectSort}
sort={this.state.sort}
/>
<ModerationQueue
currentAsset={asset}
comments={comments}
activeTab={activeTab}
singleView={moderation.singleView}
selectedIndex={this.state.selectedIndex}
bannedWords={settings.wordlist.banned}
suspectWords={settings.wordlist.suspect}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
loadMore={props.loadMore}
assetId={providedAssetId}
sort={this.state.sort}
commentCount={activeTabCount}
currentUserId={this.props.auth.user.id}
viewUserDetail={viewUserDetail}
hideUserDetail={hideUserDetail}
/>
<BanUserDialog
open={moderation.banDialog}
user={moderation.user}
commentId={moderation.commentId}
commentStatus={moderation.commentStatus}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
showRejectedNote={moderation.showRejectedNote}
rejectComment={props.rejectComment}
/>
<SuspendUserDialog
open={moderation.suspendUserDialog.show}
username={moderation.suspendUserDialog.username}
userId={moderation.suspendUserDialog.userId}
organizationName={data.settings.organizationName}
onCancel={props.hideSuspendUserDialog}
onPerform={this.suspendUser}
/>
<ModerationKeysModal
hideShortcutsNote={props.hideShortcutsNote}
shortcutsNoteVisible={moderation.shortcutsNoteVisible}
open={moderation.modalOpen}
onClose={onClose}/>
{moderation.userDetailId && (
<UserDetail
id={moderation.userDetailId}
hideUserDetail={hideUserDetail} />
)}
</div>
);
}
}
const mapStateToProps = (state) => ({
moderation: state.moderation.toJS(),
settings: state.settings.toJS(),
auth: state.auth.toJS(),
assets: state.assets.get('assets')
});
const mapDispatchToProps = (dispatch) => ({
onClose: () => dispatch(toggleModal(false)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
...bindActionCreators({
toggleModal,
singleView,
updateAssets,
fetchSettings,
showBanUserDialog,
hideShortcutsNote,
showSuspendUserDialog,
hideSuspendUserDialog,
viewUserDetail,
hideUserDetail,
}, dispatch),
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
setCommentStatus,
getQueueCounts,
banUser,
suspendUser,
modQueueQuery,
)(ModerationContainer);
@@ -1,41 +0,0 @@
.copyButton {
float: right;
top: -10px;
}
.memberSince {
clear: both;
}
.small {
color: #aaa;
}
.stats {
display: flex;
.stat {
margin: 0 4px 12px;
}
.stat:last-child {
margin-right: 0;
}
p {
margin: 0;
}
.stat p:first-child {
font-weight: bold;
}
}
.profileEmail {
border: none;
background-color: transparent;
font-size: 16px;
position: absolute;
width: 100%;
outline: none;
}
@@ -1,74 +0,0 @@
import React, {PropTypes} from 'react';
import {Button, Drawer} from 'coral-ui';
import styles from './UserDetail.css';
import {compose} from 'react-apollo';
import {getUserDetail} from 'coral-admin/src/graphql/queries';
import Slot from 'coral-framework/components/Slot';
class UserDetail extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
hideUserDetail: PropTypes.func.isRequired
}
copyPermalink = () => {
this.profile.select();
try {
document.execCommand('copy');
} catch (e) {
/* nothing */
}
}
render () {
const {data, hideUserDetail} = this.props;
if (!('user' in data)) {
return null;
}
const {user, totalComments, rejectedComments} = data;
const localProfile = user.profiles.find((p) => p.provider === 'local');
let profile;
if (localProfile) {
profile = localProfile.id;
}
let rejectedPercent = rejectedComments / totalComments;
if (rejectedPercent === Infinity || isNaN(rejectedPercent)) {
// if totalComments is 0, you're dividing by zero, which is naughty
rejectedPercent = 0;
}
return (
<Drawer handleClickOutside={hideUserDetail}>
<h3>{user.username}</h3>
<Button className={styles.copyButton} onClick={this.copyPermalink}>Copy</Button>
{profile && <input className={styles.profileEmail} readOnly type="text" ref={(ref) => this.profile = ref} value={profile} />}
<Slot fill="userProfile" user={user} />
<p className={styles.memberSince}><strong>Member since</strong> {new Date(user.created_at).toLocaleString()}</p>
<hr/>
<p>
<strong>Account summary</strong>
<br/><small className={styles.small}>Data represents the last six months of activity</small>
</p>
<div className={styles.stats}>
<div className={styles.stat}>
<p>Total Comments</p>
<p>{totalComments}</p>
</div>
<div className={styles.stat}>
<p>Reject Rate</p>
<p>{`${(rejectedPercent).toFixed(1)}%`}</p>
</div>
</div>
</Drawer>
);
}
}
export default compose(
getUserDetail
)(UserDetail);
@@ -1,55 +0,0 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../../translations';
const lang = new I18n(translations);
const onBanClick = (userId, commentId, commentStatus, handleBanUser, rejectComment, handleClose) => (e) => {
e.preventDefault();
handleBanUser({userId})
.then(handleClose)
.then(() => commentStatus === 'REJECTED' ? null : rejectComment({commentId}));
};
const BanUserDialog = ({open, handleClose, handleBanUser, rejectComment, user, commentId, commentStatus, showRejectedNote}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={lang.t('bandialog.ban_user')}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h2>{lang.t('bandialog.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{lang.t('bandialog.are_you_sure', user.name)}</h3>
<i>{showRejectedNote && lang.t('bandialog.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{lang.t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={onBanClick(user.id, commentId, commentStatus, handleBanUser, rejectComment, handleClose)} raised>
{lang.t('bandialog.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
BanUserDialog.propTypes = {
handleBanUser: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
commentId: PropTypes.string,
user: PropTypes.object.isRequired,
};
export default BanUserDialog;
@@ -1,188 +0,0 @@
import React, {PropTypes} from 'react';
import timeago from 'timeago.js';
import {Link} from 'react-router';
import Linkify from 'react-linkify';
import {Icon} from 'coral-ui';
import FlagBox from './FlagBox';
import styles from './styles.css';
import CommentType from './CommentType';
import Highlighter from 'react-highlight-words';
import Slot from 'coral-framework/components/Slot';
import {getActionSummary} from 'coral-framework/utils';
import ActionButton from 'coral-admin/src/components/ActionButton';
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
const linkify = new Linkify();
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
const Comment = ({
actions = [],
comment,
viewUserDetail,
suspectWords,
bannedWords,
...props
}) => {
const links = linkify.getMatches(comment.body);
const linkText = links ? links.map((link) => link.raw) : [];
const flagActionSummaries = getActionSummary('FlagActionSummary', comment);
const flagActions =
comment.actions &&
comment.actions.filter((a) => a.__typename === 'FlagAction');
let commentType = '';
if (comment.status === 'PREMOD') {
commentType = 'premod';
} else if (flagActions && flagActions.length) {
commentType = 'flagged';
}
// since words are checked against word boundaries on the backend,
// this should be the behavior on the front end as well.
// currently the highlighter plugin does not support this out of the box.
const searchWords = [...suspectWords, ...bannedWords]
.filter((w) => {
return new RegExp(`(^|\\s)${w}(\\s|$)`).test(comment.body);
})
.concat(linkText);
return (
<li
tabIndex={props.index}
className={`mdl-card ${props.selected ? 'mdl-shadow--16dp' : 'mdl-shadow--2dp'} ${styles.Comment} ${styles.listItem} ${props.selected ? styles.selected : ''}`}
>
<div className={styles.container}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span className={styles.username} onClick={() => viewUserDetail(comment.user.id)}>
{comment.user.name}
</span>
<span className={styles.created}>
{timeago().format(
comment.created_at || Date.now() - props.index * 60 * 1000,
lang.getLocale().replace('-', '_')
)}
</span>
{props.currentUserId !== comment.user.id &&
<ActionsMenu icon="not_interested">
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showSuspendUserDialog(comment.user.id, comment.user.name, comment.id, comment.status)}>
Suspend User</ActionsMenuItem>
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showBanUserDialog(comment.user, comment.id, comment.status, comment.status !== 'REJECTED')}>
Ban User
</ActionsMenuItem>
</ActionsMenu>
}
<CommentType type={commentType} />
</div>
{comment.user.status === 'banned'
? <span className={styles.banned}>
<Icon name="error_outline" />
{lang.t('comment.banned_user')}
</span>
: null}
<Slot fill="adminCommentInfoBar" comment={comment} />
</div>
<div className={styles.moderateArticle}>
Story: {comment.asset.title}
{!props.currentAsset &&
<Link to={`/admin/moderate/${comment.asset.id}`}>Moderate </Link>}
</div>
<div className={styles.itemBody}>
<p className={styles.body}>
<Highlighter
searchWords={searchWords}
textToHighlight={comment.body}
/>
{' '}
<a
className={styles.external}
href={`${comment.asset.url}#${comment.id}`}
target="_blank"
>
<Icon name="open_in_new" /> {lang.t('comment.view_context')}
</a>
</p>
<Slot fill="adminCommentContent" comment={comment} />
<div className={styles.sideActions}>
{links
? <span className={styles.hasLinks}>
<Icon name="error_outline" /> Contains Link
</span>
: null}
<div className={`actions ${styles.actions}`}>
{actions.map((action, i) => {
const active =
(action === 'REJECT' && comment.status === 'REJECTED') ||
(action === 'APPROVE' && comment.status === 'ACCEPTED');
return (
<ActionButton
key={i}
type={action}
user={comment.user}
status={comment.status}
active={active}
acceptComment={() =>
(comment.status === 'ACCEPTED'
? null
: props.acceptComment({commentId: comment.id}))}
rejectComment={() =>
(comment.status === 'REJECTED'
? null
: props.rejectComment({commentId: comment.id}))}
/>
);
})}
</div>
<Slot fill="adminSideActions" comment={comment} />
</div>
</div>
</div>
<div>
<Slot fill="adminCommentDetailArea" comment={comment} />
</div>
{flagActions && flagActions.length
? <FlagBox
actions={flagActions}
actionSummaries={flagActionSummaries}
/>
: null}
</li>
);
};
Comment.propTypes = {
viewUserDetail: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
showBanUserDialog: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func.isRequired,
currentUserId: PropTypes.string.isRequired,
comment: PropTypes.shape({
body: PropTypes.string.isRequired,
action_summaries: PropTypes.array,
actions: PropTypes.array,
created_at: PropTypes.string.isRequired,
user: PropTypes.shape({
id: PropTypes.string,
status: PropTypes.string
}).isRequired,
asset: PropTypes.shape({
title: PropTypes.string,
url: PropTypes.string,
id: PropTypes.string
})
})
};
export default Comment;
@@ -1,34 +0,0 @@
import React, {PropTypes} from 'react';
import {Button} from 'coral-ui';
import styles from './styles.css';
const LoadMore = ({comments, loadMore, sort, tab, assetId, showLoadMore}) =>
<div className={styles.loadMoreContainer}>
{
showLoadMore && <Button
className={styles.loadMore}
onClick={() => {
const lastComment = comments[comments.length - 1];
const cursor = lastComment ? lastComment.created_at : null;
return loadMore({
cursor,
sort,
tab,
asset_id: assetId
});
}}>
Load More
</Button>
}
</div>;
LoadMore.propTypes = {
comments: PropTypes.array.isRequired,
loadMore: PropTypes.func.isRequired,
sort: PropTypes.oneOf(['CHRONOLOGICAL', 'REVERSE_CHRONOLOGICAL']).isRequired,
tab: PropTypes.oneOf(['rejected', 'premod', 'flagged', 'all', 'accepted']).isRequired,
assetId: PropTypes.string,
showLoadMore: PropTypes.bool.isRequired
};
export default LoadMore;
@@ -1,29 +0,0 @@
import React from 'react';
import {Link} from 'react-router';
import {Icon} from 'coral-ui';
import styles from './styles.css';
const ModerationHeader = (props) => (
<div className=''>
<div className={`mdl-tabs ${styles.header}`}>
{
props.asset ?
<div className={`mdl-tabs__tab-bar ${styles.moderateAsset}`}>
<Link className="mdl-tabs__tab" to="/admin/moderate">All Streams</Link>
<a className="mdl-tabs__tab" href={props.asset.url}>
<span>{props.asset.title}</span>
<Icon className={styles.settingsButton} name="open_in_new"/>
</a>
<Link className="mdl-tabs__tab" to="/admin/stories">Select Stream</Link>
</div>
:
<div className={`mdl-tabs__tab-bar ${styles.moderateAsset}`}>
<a className="mdl-tabs__tab" />
<a className="mdl-tabs__tab">All Streams</a>
<Link className="mdl-tabs__tab" to="/admin/stories">Select Stream</Link>
</div>
}
</div>
</div>
);
export default ModerationHeader;
@@ -1,14 +0,0 @@
export const actionsMap = {
PREMOD: ['APPROVE', 'REJECT'],
FLAGGED: ['APPROVE', 'REJECT'],
REJECTED: ['APPROVE', 'REJECTED']
};
export const menuActionsMap = {
'REJECT': {status: 'REJECTED', text: 'Reject', icon: 'close', key: 'r'},
'REJECTED': {status: 'REJECTED', text: 'Rejected', icon: 'close'},
'APPROVE': {status: 'ACCEPTED', text: 'Approve', icon: 'done', key: 't'},
'FLAGGED': {status: 'FLAGGED', text: 'Flag', icon: 'flag', filter: 'Untouched'},
'BAN': {status: 'BANNED', text: 'Ban User', icon: 'not interested'},
'': {icon: 'done'}
};
@@ -1,187 +0,0 @@
import React, {Component} from 'react';
import styles from './Stories.css';
import {connect} from 'react-redux';
import I18n from 'coral-framework/modules/i18n/i18n';
import {fetchAssets, updateAssetState} from '../../actions/assets';
import translations from '../../translations.json';
import {Link} from 'react-router';
import {Pager, Icon} from 'coral-ui';
import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl';
import EmptyCard from 'coral-admin/src/components/EmptyCard';
const limit = 25;
class Stories extends Component {
state = {
search: '',
sort: 'desc',
filter: 'all',
statusMenus: {},
timer: null,
page: 0
}
componentDidMount () {
this.props.fetchAssets(0, limit, '', this.state.sortBy);
}
onSettingChange = (setting) => (e) => {
let options = this.state;
this.setState({[setting]: e.target.value});
options[setting] = e.target.value;
this.props.fetchAssets(0, limit, options.search, options.sort, options.filter);
}
onSearchChange = (e) => {
const search = e.target.value;
this.setState((prevState) => {
prevState.search = search;
clearTimeout(prevState.timer);
const fetchAssets = this.props.fetchAssets;
prevState.timer = setTimeout(() => {
fetchAssets(0, limit, search, this.state.sort, this.state.filter);
}, 350);
return prevState;
});
}
renderDate = (date) => {
const d = new Date(date);
return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
}
onStatusClick = (closeStream, id, statusMenuOpen) => () => {
if (statusMenuOpen) {
this.setState((prev) => {
prev.statusMenus[id] = false;
return prev;
});
this.props.updateAssetState(id, closeStream ? Date.now() : null)
.then(() => {
const {search, sort, filter, page} = this.state;
this.props.fetchAssets(page, limit, search, sort, filter);
});
} else {
this.setState((prev) => {
prev.statusMenus[id] = true;
return prev;
});
}
}
renderTitle = (title, {id}) => <Link to={`/admin/moderate/${id}`}>{title}</Link>
renderStatus = (closedAt, {id}) => {
const closed = closedAt && new Date(closedAt).getTime() < Date.now();
const statusMenuOpen = this.state.statusMenus[id];
return <div className={styles.statusMenu}>
<div
className={closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(closed, id, statusMenuOpen)}>
{!statusMenuOpen && <Icon className={styles.statusMenuIcon} name='keyboard_arrow_down'/>}
{closed ? lang.t('streams.closed') : lang.t('streams.open')}
</div>
{
statusMenuOpen &&
<div
className={!closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(!closed, id, statusMenuOpen)}>
{!closed ? lang.t('streams.closed') : lang.t('streams.open')}
</div>
}
</div>;
}
onPageClick = (page) => {
this.setState({page});
const {search, sort, filter} = this.state;
this.props.fetchAssets((page - 1) * limit, limit, search, sort, filter);
}
render () {
const {search, sort, filter} = this.state;
const {assets} = this.props;
const assetsIds = assets.ids.map((id) => assets.byId[id]);
return (
<div className={styles.container}>
<div className={styles.leftColumn}>
<div className={styles.searchBox}>
<Icon name='search' className={styles.searchIcon}/>
<input
type='text'
value={search}
className={styles.searchBoxInput}
onChange={this.onSearchChange}
placeholder={lang.t('streams.search')}/>
</div>
<div className={styles.optionHeader}>{lang.t('streams.filter-streams')}</div>
<div className={styles.optionDetail}>{lang.t('streams.stream-status')}</div>
<RadioGroup
name='status filter'
value={filter}
childContainer='div'
onChange={this.onSettingChange('filter')}
className={styles.radioGroup}
>
<Radio value='all'>{lang.t('streams.all')}</Radio>
<Radio value='open'>{lang.t('streams.open')}</Radio>
<Radio value='closed'>{lang.t('streams.closed')}</Radio>
</RadioGroup>
<div className={styles.optionHeader}>{lang.t('streams.sort-by')}</div>
<RadioGroup
name='sort by'
value={sort}
childContainer='div'
onChange={this.onSettingChange('sort')}
className={styles.radioGroup}
>
<Radio value='desc'>{lang.t('streams.newest')}</Radio>
<Radio value='asc'>{lang.t('streams.oldest')}</Radio>
</RadioGroup>
</div>
{
assetsIds.length
? <div className={styles.mainContent}>
<DataTable className={styles.streamsTable} rows={assetsIds} onClick={this.goToModeration}>
<TableHeader name="title" cellFormatter={this.renderTitle}>{lang.t('streams.article')}</TableHeader>
<TableHeader name="publication_date" cellFormatter={this.renderDate}>
{lang.t('streams.pubdate')}
</TableHeader>
<TableHeader name="closedAt" cellFormatter={this.renderStatus} className={styles.status}>
{lang.t('streams.status')}
</TableHeader>
</DataTable>
<Pager
totalPages={Math.ceil((assets.count || 0) / limit)}
page={this.state.page}
onNewPageHandler={this.onPageClick} />
</div>
: <EmptyCard>{lang.t('streams.empty_result')}</EmptyCard>
}
</div>
);
}
}
const mapStateToProps = ({assets}) => {
return {
assets: assets.toJS()
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchAssets: (...args) => {
dispatch(fetchAssets.apply(this, args));
},
updateAssetState: (...args) => dispatch(updateAssetState.apply(this, args))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Stories);
const lang = new I18n(translations);
@@ -0,0 +1,83 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import SuspendUserDialog from '../components/SuspendUserDialog';
import {hideSuspendUserDialog} from '../actions/suspendUserDialog';
import {withSetCommentStatus, withSuspendUser} from 'coral-framework/graphql/mutations';
import {compose, gql} from 'react-apollo';
import * as notification from 'coral-admin/src/services/notification';
import t, {timeago} from 'coral-framework/services/i18n';
import withQuery from 'coral-framework/hocs/withQuery';
import get from 'lodash/get';
class SuspendUserDialogContainer extends Component {
suspendUser = async ({message, until}) => {
const {userId, username, commentStatus, commentId, hideSuspendUserDialog, setCommentStatus, suspendUser} = this.props;
hideSuspendUserDialog();
try {
const result = await suspendUser({id: userId, message, until});
if (result.data.suspendUser.errors) {
throw result.data.suspendUser.errors;
}
notification.success(
t('suspenduser.notify_suspend_until', username, timeago(until)),
);
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
return setCommentStatus({commentId, status: 'REJECTED'})
.then((result) => {
if (result.data.setCommentStatus.errors) {
throw result.data.setCommentStatus.errors;
}
});
}
}
catch(err) {
notification.showMutationErrors(err);
}
};
render() {
return (
<SuspendUserDialog
open={this.props.open}
onPerform={this.suspendUser}
onCancel={this.props.hideSuspendUserDialog}
organizationName={get(this.props, 'root.settings.organizationName')}
username={this.props.username}
/>
);
}
}
const withOrganizationName = withQuery(gql`
query CoralAdmin_SuspendUserDialog {
settings {
organizationName
}
}
`);
const mapStateToProps = ({suspendUserDialog: {open, userId, username, commentId, commentStatus}}) => ({
open,
userId,
username,
commentId,
commentStatus,
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
hideSuspendUserDialog,
}, dispatch),
});
export default compose(
connect(
mapStateToProps,
mapDispatchToProps,
),
withSuspendUser,
withSetCommentStatus,
withOrganizationName,
)(SuspendUserDialogContainer);
@@ -1,12 +0,0 @@
fragment metrics on Asset {
id
title
url
author
created_at
commentCount
action_summaries {
actionCount
actionableItemCount
}
}
@@ -1,31 +0,0 @@
fragment commentView on Comment {
id
body
created_at
status
user {
id
name: username
status
}
asset {
id
title
url
}
action_summaries {
count
... on FlagActionSummary {
reason
}
}
actions {
... on FlagAction {
reason
message
user {
username
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
import {add} from 'coral-framework/services/graphqlRegistry';
const extension = {
mutations: {
SetUserStatus: () => ({
refetchQueries: ['CoralAdmin_Community'],
}),
RejectUsername: () => ({
refetchQueries: ['CoralAdmin_Community'],
}),
},
};
add(extension);
@@ -1,153 +0,0 @@
import {graphql} from 'react-apollo';
import SET_USER_STATUS from './setUserStatus.graphql';
import SET_COMMENT_STATUS from './setCommentStatus.graphql';
import SUSPEND_USER from './suspendUser.graphql';
import REJECT_USERNAME from './rejectUsername.graphql';
export const banUser = graphql(SET_USER_STATUS, {
props: ({mutate}) => ({
banUser: ({userId}) => {
return mutate({
variables: {
userId,
status: 'BANNED'
},
refetchQueries: ['Users']
});
}}),
});
export const setUserStatus = graphql(SET_USER_STATUS, {
props: ({mutate}) => ({
approveUser: ({userId}) => {
return mutate({
variables: {
userId,
status: 'APPROVED'
},
refetchQueries: ['Users']
});
}
})
});
export const suspendUser = graphql(SUSPEND_USER, {
props: ({mutate}) => ({
suspendUser: (input) => {
return mutate({
variables: {
input,
},
});
}
})
});
export const rejectUsername = graphql(REJECT_USERNAME, {
props: ({mutate}) => ({
rejectUsername: (input) => {
return mutate({
variables: {
input,
},
refetchQueries: ['Users']
});
}
})
});
const views = ['all', 'premod', 'flagged', 'accepted', 'rejected'];
export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
props: ({mutate}) => ({
acceptComment: ({commentId}) => {
return mutate({
variables: {
commentId,
status: 'ACCEPTED'
},
updateQueries: {
ModQueue: (oldData) => {
const comment = views.reduce((comment, view) => {
return comment ? comment : oldData[view].find((c) => c.id === commentId);
}, null);
let accepted;
let acceptedCount = oldData.acceptedCount;
// if the comment was already in the Approved queue, don't re-add it
if (comment.status === 'ACCEPTED') {
accepted = [...oldData.accepted];
} else {
comment.status = 'ACCEPTED';
acceptedCount++;
accepted = [comment, ...oldData.accepted];
}
const premod = oldData.premod.filter((c) => c.id !== commentId);
const flagged = oldData.flagged.filter((c) => c.id !== commentId);
const rejected = oldData.rejected.filter((c) => c.id !== commentId);
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
const rejectedCount = rejected.length < oldData.rejected.length ? oldData.rejectedCount - 1 : oldData.rejectedCount;
return {
...oldData,
premodCount: Math.max(0, premodCount),
flaggedCount: Math.max(0, flaggedCount),
acceptedCount: Math.max(0, acceptedCount),
rejectedCount: Math.max(0, rejectedCount),
premod,
flagged,
accepted,
rejected,
};
}
}
});
},
rejectComment: ({commentId}) => {
return mutate({
variables: {
commentId,
status: 'REJECTED'
},
updateQueries: {
ModQueue: (oldData) => {
const comment = views.reduce((comment, view) => {
return comment ? comment : oldData[view].find((c) => c.id === commentId);
}, null);
let rejected;
let rejectedCount = oldData.rejectedCount;
// if the item was already in the Rejected queue, don't put it in again
if (comment.status === 'REJECTED') {
rejected = oldData.rejected;
} else {
comment.status = 'REJECTED';
rejectedCount++;
rejected = [comment, ...oldData.rejected];
}
const premod = oldData.premod.filter((c) => c.id !== commentId);
const flagged = oldData.flagged.filter((c) => c.id !== commentId);
const accepted = oldData.accepted.filter((c) => c.id !== commentId);
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
const acceptedCount = accepted.length < oldData.accepted.length ? oldData.acceptedCount - 1 : oldData.acceptedCount;
return {
...oldData,
premodCount: Math.max(0, premodCount),
flaggedCount: Math.max(0, flaggedCount),
acceptedCount: Math.max(0, acceptedCount),
rejectedCount: Math.max(0, rejectedCount),
premod,
flagged,
accepted,
rejected
};
}
}
});
}
})
});
@@ -1,7 +0,0 @@
mutation rejectUsername($input: RejectUsernameInput!) {
rejectUsername(input: $input) {
errors {
translation_key
}
}
}
@@ -1,7 +0,0 @@
mutation setCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){
setCommentStatus(id: $commentId, status: $status) {
errors {
translation_key
}
}
}
@@ -1,7 +0,0 @@
mutation setUserStatus($userId: ID!, $status: USER_STATUS!) {
setUserStatus(id: $userId, status: $status) {
errors {
translation_key
}
}
}
@@ -1,7 +0,0 @@
mutation suspendUser($input: SuspendUserInput!) {
suspendUser(input: $input) {
errors {
translation_key
}
}
}
@@ -1,6 +0,0 @@
query Assets {
assets {
id
title
}
}
@@ -1,22 +0,0 @@
query Counts ($asset_id: ID) {
allCount: commentCount(query: {
asset_id: $asset_id
})
acceptedCount: commentCount(query: {
statuses: [ACCEPTED],
asset_id: $asset_id
})
premodCount: commentCount(query: {
statuses: [PREMOD],
asset_id: $asset_id
})
rejectedCount: commentCount(query: {
statuses: [REJECTED],
asset_id: $asset_id
})
flaggedCount: commentCount(query: {
action_type: FLAG,
asset_id: $asset_id,
statuses: [NONE, PREMOD]
})
}
@@ -1,116 +0,0 @@
import {graphql} from 'react-apollo';
import MOD_QUEUE_QUERY from './modQueueQuery.graphql';
import MOD_QUEUE_LOAD_MORE from './loadMore.graphql';
import MOD_USER_FLAGGED_QUERY from './modUserFlaggedQuery.graphql';
import METRICS from './metricsQuery.graphql';
import USER_DETAIL from './userDetail.graphql';
import GET_QUEUE_COUNTS from './getQueueCounts.graphql';
export const modQueueQuery = graphql(MOD_QUEUE_QUERY, {
options: ({params: {id = null}}) => {
return {
variables: {
asset_id: id,
sort: 'REVERSE_CHRONOLOGICAL'
}
};
},
props: ({ownProps: {params: {id = null}}, data}) => ({
data,
modQueueResort: modQueueResort(id, data.fetchMore),
loadMore: loadMore(data.fetchMore)
})
});
export const getMetrics = graphql(METRICS, {
options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => {
return {
variables: {
from: dashboardWindowStart,
to: dashboardWindowEnd
}
};
}
});
export const loadMore = (fetchMore) => ({limit = 10, cursor, sort, tab, asset_id}) => {
let variables = {
limit,
cursor,
sort,
asset_id
};
switch(tab) {
case 'all':
variables.statuses = null;
break;
case 'accepted':
variables.statuses = ['ACCEPTED'];
break;
case 'premod':
variables.statuses = ['PREMOD'];
break;
case 'flagged':
variables.statuses = ['NONE', 'PREMOD'];
variables.action_type = 'FLAG';
break;
case 'rejected':
variables.statuses = ['REJECTED'];
break;
}
return fetchMore({
query: MOD_QUEUE_LOAD_MORE,
variables,
updateQuery: (oldData, {fetchMoreResult:{comments}}) => {
return {
...oldData,
[tab]: [
...oldData[tab],
...comments
]
};
}
});
};
export const modUserFlaggedQuery = graphql(MOD_USER_FLAGGED_QUERY, {
options: ({params: {action_type = 'FLAG'}}) => {
return {
variables: {
action_type: action_type
}
};
}
});
export const modQueueResort = (id, fetchMore) => (sort) => {
return fetchMore({
query: MOD_QUEUE_QUERY,
variables: {
asset_id: id,
sort
},
updateQuery: (oldData, {fetchMoreResult:{data}}) => data
});
};
export const getUserDetail = graphql(USER_DETAIL, {
options: ({id}) => {
return {
variables: {author_id: id}
};
}
});
export const getQueueCounts = graphql(GET_QUEUE_COUNTS, {
options: ({params: {id = null}}) => {
return {
pollInterval: 5000,
variables: {
asset_id: id
}
};
}
});
@@ -1,13 +0,0 @@
#import "../fragments/commentView.graphql"
query LoadMoreModQueue($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) {
comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort, action_type: $action_type}) {
...commentView
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
}
@@ -1,10 +0,0 @@
#import "../fragments/assetMetricsView.graphql"
query Metrics ($from: Date!, $to: Date!) {
assetsByFlag: assetMetrics(from: $from, to: $to, sort: FLAG) {
...metrics
}
assetsByActivity: assetMetrics(from: $from, to: $to, sort: ACTIVITY) {
...metrics
}
}
@@ -1,68 +0,0 @@
#import "../fragments/commentView.graphql"
query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
all: comments(query: {
statuses: [NONE, PREMOD, ACCEPTED, REJECTED],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
accepted: comments(query: {
statuses: [ACCEPTED],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
premod: comments(query: {
statuses: [PREMOD],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
flagged: comments(query: {
action_type: FLAG,
asset_id: $asset_id,
statuses: [NONE, PREMOD],
sort: $sort
}) {
...commentView
}
rejected: comments(query: {
statuses: [REJECTED],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
assets: assets {
id
title
url
}
allCount: commentCount(query: {
asset_id: $asset_id
})
acceptedCount: commentCount(query: {
statuses: [ACCEPTED],
asset_id: $asset_id
})
premodCount: commentCount(query: {
statuses: [PREMOD],
asset_id: $asset_id
})
rejectedCount: commentCount(query: {
statuses: [REJECTED],
asset_id: $asset_id
})
flaggedCount: commentCount(query: {
action_type: FLAG,
asset_id: $asset_id,
statuses: [NONE, PREMOD]
})
settings {
organizationName
}
}
@@ -1,26 +0,0 @@
query Users ($action_type: ACTION_TYPE) {
users (query:{action_type: $action_type}){
id
username
status
roles
actions{
id
created_at
... on FlagAction {
reason
message
user {
id
username
}
}
}
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
}
@@ -1,13 +0,0 @@
query UserDetail ($author_id: ID!) {
user(id: $author_id) {
id
username
created_at
profiles {
id
provider
}
}
totalComments: commentCount(query: {author_id: $author_id})
rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]})
}
+137
View File
@@ -0,0 +1,137 @@
import update from 'immutability-helper';
import * as notification from 'coral-admin/src/services/notification';
const queues = ['all', 'premod', 'flagged', 'accepted', 'rejected'];
const limit = 10;
const ascending = (a, b) => {
const dateA = new Date(a.created_at);
const dateB = new Date(b.created_at);
if (dateA < dateB) { return -1; }
if (dateA > dateB) { return 1; }
return 0;
};
const descending = (a, b) => -ascending(a, b);
function queueHasComment(root, queue, id) {
return root[queue].nodes.find((c) => c.id === id);
}
function removeCommentFromQueue(root, queue, id) {
if (!queueHasComment(root, queue, id)) {
return root;
}
return update(root, {
[`${queue}Count`]: {$set: root[`${queue}Count`] - 1},
[queue]: {
nodes: {$apply: (nodes) => nodes.filter((c) => c.id !== id)},
},
});
}
function shouldCommentBeAdded(root, queue, comment, sort) {
if (root[`${queue}Count`] < limit) {
// Adding all comments until first limit has reached.
return true;
}
const cursor = new Date(root[queue].endCursor);
return sort === 'CHRONOLOGICAL'
? new Date(comment.created_at) <= cursor
: new Date(comment.created_at) >= cursor;
}
function addCommentToQueue(root, queue, comment, sort) {
if (queueHasComment(root, queue, comment.id)) {
return root;
}
const sortAlgo = sort === 'CHRONOLOGICAL' ? ascending : descending;
const changes = {
[`${queue}Count`]: {$set: root[`${queue}Count`] + 1},
};
if (shouldCommentBeAdded(root, queue, comment, sort)) {
const nodes = root[queue].nodes.concat(comment).sort(sortAlgo);
changes[queue] = {
nodes: {$set: nodes},
startCursor: {$set: nodes[0].created_at},
endCursor: {$set: nodes[nodes.length - 1].created_at},
};
}
return update(root, changes);
}
function getCommentQueues(comment) {
const queues = ['all'];
if (comment.status === 'ACCEPTED') {
queues.push('accepted');
}
else if (comment.status === 'REJECTED') {
queues.push('rejected');
}
else if (comment.status === 'PREMOD') {
queues.push('premod');
}
if (
['NONE', 'PREMOD'].indexOf(comment.status) >= 0
&& comment.actions && comment.actions.some((a) => a.__typename === 'FlagAction')
) {
queues.push('flagged');
}
return queues;
}
/**
* Assimilate comment changes into current store.
* @param {Object} root current state of the store
* @param {Object} comment comment that was changed
* @param {string} sort current sort order of the queues
* @param {Object} [notify] show know notifications if set
* @param {string} notify.activeQueue current active queue
* @param {string} notify.text notification text to show
* @param {bool} notify.anyQueue if true show the notification when the comment is shown
* in the current active queue besides the 'all' queue.
* @return {Object} next state of the store
*/
export function handleCommentChange(root, comment, sort, notify) {
let next = root;
const nextQueues = getCommentQueues(comment);
let notificationShown = false;
const showNotificationOnce = () => {
if (notificationShown) {
return;
}
notification.info(notify.text);
notificationShown = true;
};
queues.forEach((queue) => {
if (nextQueues.indexOf(queue) >= 0) {
if (!queueHasComment(next, queue, comment.id)) {
next = addCommentToQueue(next, queue, comment, sort);
if (notify && notify.activeQueue === queue && shouldCommentBeAdded(next, queue, comment, sort)) {
showNotificationOnce(comment);
}
}
} else if(queueHasComment(next, queue, comment.id)){
next = removeCommentFromQueue(next, queue, comment.id);
if (notify && notify.activeQueue === queue) {
showNotificationOnce(comment);
}
}
if (
notify
&& (queue === 'all' || notify.anyQueue)
&& queueHasComment(next, queue, comment.id)
&& notify.activeQueue === queue
) {
showNotificationOnce(comment);
}
});
return next;
}
+9 -2
View File
@@ -1,16 +1,23 @@
import React from 'react';
import {render} from 'react-dom';
import {ApolloProvider} from 'react-apollo';
import smoothscroll from 'smoothscroll-polyfill';
import {client} from './services/client';
import {getClient} from './services/client';
import store from './services/store';
import App from './components/App';
import 'react-mdl/extra/material.js';
import './graphql';
import {loadPluginsTranslations, injectPluginsReducers} from 'coral-framework/helpers/plugins';
loadPluginsTranslations();
injectPluginsReducers();
smoothscroll.polyfill();
render(
<ApolloProvider client={client} store={store}>
<ApolloProvider client={getClient()} store={store}>
<App />
</ApolloProvider>
, document.querySelector('#root')
@@ -0,0 +1,30 @@
import {SHOW_BAN_USER_DIALOG, HIDE_BAN_USER_DIALOG} from '../constants/banUserDialog';
const initialState = {
open: false,
userId: null,
username: '',
commentId: null,
commentStatus: '',
};
export default function banUserDialog(state = initialState, action) {
switch (action.type) {
case SHOW_BAN_USER_DIALOG:
return {
...state,
open: true,
userId: action.userId,
username: action.username,
commentId: action.commentId,
commentStatus: action.commentStatus,
};
case HIDE_BAN_USER_DIALOG:
return {
...state,
open: false,
};
default:
return state;
}
}
+7 -7
View File
@@ -9,8 +9,8 @@ import {
SET_COMMENTER_STATUS,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG,
SHOW_SUSPENDUSER_DIALOG,
HIDE_SUSPENDUSER_DIALOG
SHOW_REJECT_USERNAME_DIALOG,
HIDE_REJECT_USERNAME_DIALOG
} from '../constants/community';
const initialState = Map({
@@ -24,7 +24,7 @@ const initialState = Map({
pagePeople: 0,
user: Map({}),
banDialog: false,
suspendDialog: false
rejectUsernameDialog: false
});
export default function community (state = initialState, action) {
@@ -79,14 +79,14 @@ export default function community (state = initialState, action) {
user: Map(action.user),
banDialog: true
});
case HIDE_SUSPENDUSER_DIALOG:
case HIDE_REJECT_USERNAME_DIALOG:
return state
.set('suspendDialog', false);
case SHOW_SUSPENDUSER_DIALOG:
.set('rejectUsernameDialog', false);
case SHOW_REJECT_USERNAME_DIALOG:
return state
.merge({
user: Map(action.user),
suspendDialog: true
rejectUsernameDialog: true
});
default :
return state;
+5 -1
View File
@@ -5,13 +5,17 @@ import community from './community';
import moderation from './moderation';
import install from './install';
import config from './config';
import banUserDialog from './banUserDialog';
import suspendUserDialog from './suspendUserDialog';
export default {
auth,
banUserDialog,
suspendUserDialog,
assets,
settings,
community,
moderation,
install,
config
config,
};
+30 -40
View File
@@ -1,53 +1,23 @@
import {fromJS, Map} from 'immutable';
import {fromJS, Set} from 'immutable';
import * as actions from '../constants/moderation';
const initialState = fromJS({
singleView: false,
modalOpen: false,
user: {},
commentId: null,
commentStatus: null,
userDetailId: null,
banDialog: false,
userDetailActiveTab: 'all',
userDetailStatuses: ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'],
userDetailSelectedIds: new Set(),
storySearchVisible: false,
storySearchString: '',
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
suspendUserDialog: {
show: false,
userId: null,
username: '',
commentId: null,
commentStatus: '',
},
sortOrder: 'REVERSE_CHRONOLOGICAL',
});
export default function moderation (state = initialState, action) {
switch (action.type) {
case actions.HIDE_BANUSER_DIALOG:
return state
.set('banDialog', false)
.set('commentStatus', null);
case actions.SHOW_BANUSER_DIALOG:
return state
.merge({
user: Map(action.user),
commentId: action.commentId,
commentStatus: action.commentStatus,
showRejectedNote: action.showRejectedNote,
banDialog: true
});
case actions.SHOW_SUSPEND_USER_DIALOG:
return state
.mergeDeep({
suspendUserDialog: {
show: true,
userId: action.userId,
username: action.username,
commentId: action.commentId,
commentStatus: action.commentStatus,
}
});
case actions.HIDE_SUSPEND_USER_DIALOG:
return state
.setIn(['suspendUserDialog', 'show'], false);
case actions.MODERATION_CLEAR_STATE:
return initialState;
case actions.SET_ACTIVE_TAB:
return state
.set('activeTab', action.activeTab);
@@ -63,7 +33,27 @@ export default function moderation (state = initialState, action) {
case actions.VIEW_USER_DETAIL:
return state.set('userDetailId', action.userId);
case actions.HIDE_USER_DETAIL:
return state.set('userDetailId', null);
return state
.set('userDetailId', null)
.update('userDetailSelectedIds', (set) => set.clear());
case actions.CLEAR_USER_DETAIL_SELECTIONS:
return state.update('userDetailSelectedIds', (set) => set.clear());
case actions.CHANGE_USER_DETAIL_STATUSES:
return state
.set('userDetailActiveTab', action.tab)
.set('userDetailStatuses', action.statuses);
case actions.SELECT_USER_DETAIL_COMMENT:
return state.update('userDetailSelectedIds', (set) => set.add(action.id));
case actions.UNSELECT_USER_DETAIL_COMMENT:
return state.update('userDetailSelectedIds', (set) => set.delete(action.id));
case actions.SHOW_STORY_SEARCH:
return state.set('storySearchVisible', true);
case actions.HIDE_STORY_SEARCH:
return state.set('storySearchVisible', false);
case actions.STORY_SEARCH_CHANGE_VALUE:
return state.set('storySearchString', action.value);
case actions.SET_SORT_ORDER:
return state.set('sortOrder', action.order);
default :
return state;
}
@@ -0,0 +1,30 @@
import {SHOW_SUSPEND_USER_DIALOG, HIDE_SUSPEND_USER_DIALOG} from '../constants/suspendUserDialog';
const initialState = {
open: false,
userId: null,
username: '',
commentId: null,
commentStatus: '',
};
export default function suspendUserDialog(state = initialState, action) {
switch (action.type) {
case SHOW_SUSPEND_USER_DIALOG:
return {
...state,
open: true,
userId: action.userId,
username: action.username,
commentId: action.commentId,
commentStatus: action.commentStatus,
};
case HIDE_SUSPEND_USER_DIALOG:
return {
...state,
open: false,
};
default:
return state;
}
}
@@ -1,23 +1,20 @@
import React from 'react';
import styles from '../Community.css';
import BanUserButton from './BanUserButton';
import styles from './Community.css';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../../../containers/ModerationQueue/helpers/moderationQueueActionsMap';
import {menuActionsMap} from '../../Moderation/helpers/moderationQueueActionsMap';
import t from 'coral-framework/services/i18n';
const ActionButton = ({type = '', user, ...props}) => {
if (type === 'BAN') {
return <BanUserButton icon='not interested' user={user} onClick={() => props.showBanUserDialog(user)} />;
}
return (
<Button
className={`${type.toLowerCase()} ${styles.actionButton}`}
cStyle={type.toLowerCase()}
icon={menuActionsMap[type].icon}
onClick={() => {
type === 'APPROVE' ? props.approveUser({userId: user.id}) : props.showSuspendUserDialog({user: user});
type === 'APPROVE' ? props.approveUser({userId: user.id}) : props.showRejectUsernameDialog({user: user});
}}
>{menuActionsMap[type].text}</Button>
>{t(`modqueue.${menuActionsMap[type].text}`)}</Button>
);
};
@@ -175,7 +175,7 @@
right: 0;
height: 100%;
top: 0;
padding: 40px 18px;
padding: 50px 18px;
box-sizing: border-box;
}
@@ -310,24 +310,33 @@
.flaggedByCount {
display: block;
text-align: left;
margin-top: 5px;
}
.flaggedByCount i {
font-size: 14px;
margin-right: 10px;
}
.flaggedBy {
display: inline;
padding: 3px;
font-size: 16px;
font-size: 14px;
margin-left: 5px;
}
.flaggedByLabel {
font-weight: bold;
font-weight: 600;
font-size: 14px;
}
.flaggedReasons {
padding-top: 15px;
margin-left: 24px;
margin-top: 10px;
}
.flaggedByReason {
p.flaggedByReason {
font-size: 1tpx;
margin: 0px;
line-height: 1.4;
}
@@ -0,0 +1,112 @@
import React, {Component} from 'react';
import CommunityMenu from './CommunityMenu';
import People from './People';
import FlaggedAccounts from './FlaggedAccounts';
import RejectUsernameDialog from './RejectUsernameDialog';
export default class Community extends Component {
state = {
searchValue: '',
timer: null
};
onKeyDownHandler = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.search();
}
}
onSearchChange = (e) => {
const value = e.target.value;
this.setState((prevState) => {
prevState.searchValue = value;
clearTimeout(prevState.timer);
const fetchAccounts = this.props.fetchAccounts;
prevState.timer = setTimeout(() => {
fetchAccounts({value});
}, 350);
return prevState;
});
}
onHeaderClickHandler = (sort) => {
this.props.updateSorting(sort);
this.search();
}
onNewPageHandler = (page) => {
this.props.newPage(page);
this.search({page});
}
search(query = {}) {
const {community} = this.props;
this.props.fetchAccounts({
value: this.state.searchValue,
field: community.fieldPeople,
asc: community.ascPeople,
...query
});
}
getTabContent(searchValue, props) {
const {community, root: {users}} = props;
const activeTab = props.route.path === ':id' ? 'flagged' : props.route.path;
if (activeTab === 'people') {
return (
<People
isFetching={community.isFetchingPeople}
commenters={community.accounts}
searchValue={searchValue}
onSearchChange={this.onSearchChange}
error={community.errorPeople}
totalPages={community.totalPagesPeople}
page={community.pagePeople}
onKeyDown={this.onKeyDownHandler}
onHeaderClickHandler={this.onHeaderClickHandler}
onNewPageHandler={this.onNewPageHandler}
/>
);
}
return (
<div>
<FlaggedAccounts
commenters={users}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
showRejectUsernameDialog={props.showRejectUsernameDialog}
approveUser={props.approveUser}
rejectUsername={props.rejectUsername}
currentUser={this.props.currentUser}
/>
<RejectUsernameDialog
open={community.rejectUsernameDialog}
handleClose={props.hideRejectUsernameDialog}
user={community.user}
rejectUsername={props.rejectUsername}
/>
</div>
);
}
render() {
const {searchValue} = this.state;
const tab = this.getTabContent(searchValue, this.props);
return (
<div>
<CommunityMenu />
<div>
{ tab }
</div>
</div>
);
}
}

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