Compare commits

..
496 Commits
Author SHA1 Message Date
Wyatt Johnson e14389c492 Merge pull request #340 from coralproject/heroku-setup-patch
Adjusted route for Heroku app.json
2017-02-24 11:20:01 -07:00
Wyatt Johnson 6566505fc1 Adjusted route 2017-02-24 11:15:46 -07:00
Kim Gardner a8d629d680 Merge pull request #339 from coralproject/comment-design-pass
Comment stream design pass
2017-02-23 17:48:48 -05:00
David Jay b150668d86 Merge branch 'comment-design-pass' of github.com:coralproject/talk into comment-design-pass 2017-02-23 17:11:21 -05:00
David Jay 39536046c9 Fixing permalink icon and adding padding to comment content. 2017-02-23 17:08:35 -05:00
David Jay e735fb800a Updating button and signin style. 2017-02-23 17:02:35 -05:00
David Jay 66ef4e38a8 Merge branch 'master' into comment-design-pass 2017-02-23 16:28:20 -05:00
David Jay 423095360e Merge pull request #336 from coralproject/new-comments
View New Comments
2017-02-23 16:25:16 -05:00
David Jay 2456fe5145 Merge branch 'master' into comment-design-pass 2017-02-23 16:22:35 -05:00
David Jay 761d0aaa3a Merge branch 'master' into new-comments 2017-02-23 16:21:36 -05:00
David Jay c1e3aec181 Updating styles. 2017-02-23 16:20:20 -05:00
Kim Gardner 498eb63e1a Merge pull request #330 from coralproject/empty-states
Empty states for different lists
2017-02-23 16:18:24 -05:00
Kim Gardner 532f2d92ca Merge branch 'master' into empty-states 2017-02-23 16:15:05 -05:00
Wyatt Johnson 58af8ed003 Merge pull request #335 from coralproject/suspect-word-flag-reason
Fixed issue related to action create on suspect word match
2017-02-23 14:14:46 -07:00
Wyatt Johnson b50808d365 Merge branch 'master' into suspect-word-flag-reason 2017-02-23 14:11:21 -07:00
David Jay 3e2634be76 Merge branch 'master' into new-comments 2017-02-23 16:11:01 -05:00
David Jay 4cc3426af4 Adding bolding, adding translations to settings page. 2017-02-23 16:07:41 -05:00
David Jay 471607403c Switching font. 2017-02-23 15:26:02 -05:00
David Jay d73a6a87a4 Adding border radius to tag label. 2017-02-23 15:19:38 -05:00
David Jay da732ece14 Updating button padding and font size. 2017-02-23 15:03:09 -05:00
David Jay 2dda81f257 Merge pull request #338 from coralproject/admin-login
show login page instead of communicorn :'(
2017-02-23 12:12:21 -05:00
Riley Davis e3b9799d4a nested ternaries hurt me 2017-02-23 09:58:31 -07:00
Riley Davis 7ae81fd172 fixing mixed types 2017-02-23 09:43:50 -07:00
Riley Davis 953c749bd2 show login page instead of communicorn :'( 2017-02-22 16:14:14 -07:00
David Jay 9d13a95a7c Merge branch 'master' into new-comments 2017-02-22 15:20:39 -05:00
David Jay 27bd1404b3 Adding es translation. 2017-02-22 15:13:46 -05:00
David Jay bb0b3f461a Moving polling interval to constant. 2017-02-22 15:11:50 -05:00
David Jay 6becb88b56 Adding style to new comments button. 2017-02-22 15:07:32 -05:00
David Jay 096bd12df6 Loading new comments. 2017-02-22 13:45:02 -05:00
Riley Davis 2acd099f3f add empty state for asset search 2017-02-22 11:15:13 -07:00
David Jay dd09ddf968 Adding NewCount component. 2017-02-22 13:13:25 -05:00
Wyatt Johnson 80706d2fdf fixed issue related to action create on suspect word match 2017-02-22 10:33:47 -07:00
Gabriela Rodríguez Berón b29614256b Merge branch 'master' into empty-states 2017-02-22 08:13:44 -08:00
Wyatt Johnson 07a956e582 Merge pull request #333 from coralproject/rest-depreciation
Cleaned up some routes
2017-02-22 08:13:02 -07:00
Riley Davis e7c4acde4d small bugfix 2017-02-21 16:11:56 -07:00
David Jay ca107afb9b Avoiding new comment button when a comment is posted. 2017-02-21 17:29:58 -05:00
Wyatt Johnson d7a6bf9e1f Cleaned up some routes 2017-02-21 15:24:44 -07:00
David Jay 9149b737f5 Retrieving counts and displaying new comments to user. 2017-02-21 17:23:09 -05:00
Riley Davis f76befd6ac no comments in user list 2017-02-21 12:48:01 -07:00
Riley Davis 9000778d8f empty result for users in Community 2017-02-21 12:39:29 -07:00
Riley Davis e24a0caa20 add EmptyCard component. fix typos. more PropTypes. 2017-02-21 12:08:02 -07:00
David Erwin 17813e23a9 Merge pull request #327 from coralproject/error_translation_key
Error translation key
2017-02-17 20:13:11 -05:00
David Erwin 0563b93d39 Merge branch 'master' into error_translation_key 2017-02-17 20:07:49 -05:00
David Erwin df53b101ab Merge pull request #316 from coralproject/update-on-post-comment
Apollo update on post comment
2017-02-17 20:07:40 -05:00
David Erwin 6f17a84097 Merge branch 'master' into update-on-post-comment 2017-02-17 20:02:41 -05:00
Wyatt Johnson 09513c0e83 Merge pull request #329 from coralproject/docker-fixx
Added yarn.lock to Dockerfile
2017-02-17 18:02:26 -07:00
Wyatt Johnson 301c8e829f Added production flag back 2017-02-17 17:56:44 -07:00
Wyatt Johnson afcdacff0d Added yarn.lock to Dockerfile 2017-02-17 17:49:59 -07:00
David Erwin a8396481f9 Merge pull request #328 from coralproject/graph-tests
Initial graph tests
2017-02-17 18:01:17 -05:00
Wyatt Johnson 4cccf09a87 spelling... 2017-02-17 15:53:23 -07:00
Wyatt Johnson 3e4dc9c8b2 Merge branch 'master' into graph-tests 2017-02-17 15:50:21 -07:00
Wyatt Johnson a00befafcb Initial graph tests 2017-02-17 15:48:46 -07:00
Gabriela Rodríguez Berón 31dda920c6 Merge branch 'master' into error_translation_key 2017-02-17 14:30:00 -08:00
gaba 9f168e627a It uses message and not translation key. 2017-02-17 14:19:30 -08:00
Riley Davis 2506442453 toISOString 2017-02-17 14:10:21 -07:00
Riley Davis 6eb4725dd9 remove ternary from return statement 2017-02-17 14:03:37 -07:00
David Erwin 5d1bc5fb5a Merge branch 'master' into update-on-post-comment 2017-02-17 13:52:25 -05:00
David Erwin 62ee81e5a9 Merge pull request #326 from coralproject/stream-settings
Stream Settings
2017-02-17 13:51:26 -05:00
gaba 5a09beb727 It has to be translation_key and not message. 2017-02-17 10:12:37 -08:00
Belen Curcio 2d374fcdab Úsing the db url 2017-02-17 15:08:55 -03:00
Belen Curcio 54f0705f0f Merge branch 'master' of github.com:coralproject/talk into stream-settings 2017-02-17 15:05:17 -03:00
David Erwin c92e336c7f Merge pull request #321 from coralproject/the-answer-is-42
Counts on Mod Queues
2017-02-17 13:00:24 -05:00
Belén Curcio 98a34892ff Merge branch 'master' into the-answer-is-42 2017-02-17 14:42:44 -03:00
David Jay f6a6f443a9 Merge branch 'master' into update-on-post-comment 2017-02-17 12:30:42 -05:00
Gabriela Rodríguez Berón bf387af586 Merge pull request #325 from coralproject/displayNameFORALL
From displayName to username everywhere
2017-02-17 09:24:38 -08:00
Belen Curcio 051978a1ee to the configure 2017-02-17 14:21:47 -03:00
Belen Curcio 761afeaf4f Stream cog, settings 2017-02-17 14:15:16 -03:00
Belen Curcio 25f366c5f1 Comment Count Component 2017-02-17 13:55:35 -03:00
Belen Curcio 7eca9db63c null assetIDs and old typo 2017-02-17 13:53:34 -03:00
gaba dd3435fffe Replace with translation_key to manage error translation. 2017-02-17 08:42:48 -08:00
David Jay a35af36ee1 Handling premod in optimistic comment updates. 2017-02-17 11:26:19 -05:00
Belen Curcio 41d0610985 PropTypes 2017-02-17 13:22:41 -03:00
gaba 3bfac591b3 An N was missing... 2017-02-17 08:20:27 -08:00
gaba 5370a6f81a A few more changes from display to username. 2017-02-17 08:05:32 -08:00
David Erwin 8502b43974 Updating more displayname to username 2017-02-17 10:04:15 -05:00
Belén Curcio e8f711ff9c Merge branch 'master' into update-on-post-comment 2017-02-17 02:38:22 -03:00
gaba a903ceb643 Merge branch 'master' into displayNameFORALL 2017-02-16 16:13:36 -08:00
gaba 4765973a58 Other small changes and fix bug because displayname instead displayName... 2017-02-16 16:07:19 -08:00
Wyatt Johnson 83f2f7b86b Merge branch 'master' into the-answer-is-42 2017-02-16 16:49:46 -07:00
Riley Davis f925e6a8ea fix bug when likes or flags were 0 for an article 2017-02-16 16:49:14 -07:00
David Erwin a267397f06 Standardizing username 2017-02-16 16:49:14 -07:00
Wyatt Johnson 84dcf3b3b6 Added some comments 2017-02-16 16:49:14 -07:00
Wyatt Johnson 1e81ec9f4e Added new commentCount edge, added more docs 2017-02-16 16:49:14 -07:00
gaba 902cd8300d Adds back the timeout to close window. 2017-02-16 16:49:14 -07:00
Gabriela Rodríguez Berón e1383293d5 Merge pull request #324 from coralproject/summary-bugfix
fix bug when likes or flags were 0 for an article
2017-02-16 15:43:36 -08:00
Riley Davis 2b4e16edfa fix bug when likes or flags were 0 for an article 2017-02-16 16:39:02 -07:00
gaba 3166692d4e Rename everywhere to username. 2017-02-16 15:16:54 -08:00
David Erwin 465de98318 Merge pull request #323 from coralproject/more-lingering-displayname-bugs
Standardizing username
2017-02-16 18:02:04 -05:00
David Erwin e5512a0e54 Standardizing username 2017-02-16 17:56:46 -05:00
David Jay 1b2d84f38d Merge branch 'master' into update-on-post-comment 2017-02-16 17:28:40 -05:00
David Jay e3e85c5f3b Fixing error when posting comment. 2017-02-16 17:18:36 -05:00
David Erwin fa597ca04d Merge pull request #322 from coralproject/bug-close-fb-win
Adds back the timeout to close window.
2017-02-16 15:42:21 -05:00
Riley Davis 1bda098045 Merge branch 'master' into the-answer-is-42 2017-02-16 13:41:01 -07:00
David Erwin 24f052024d Merge branch 'master' into update-on-post-comment 2017-02-16 15:40:51 -05:00
David Erwin 82811b8fae Merge branch 'master' into bug-close-fb-win 2017-02-16 15:39:06 -05:00
David Erwin 88214a008c Merge pull request #320 from coralproject/add-icon-qbox
Adds icon and style to the question box.
2017-02-16 15:38:44 -05:00
Riley Davis c771e72ac3 Merge branch 'master' into the-answer-is-42 2017-02-16 13:34:31 -07:00
David Erwin a59296db14 Merge branch 'master' into update-on-post-comment 2017-02-16 15:32:23 -05:00
Wyatt Johnson 7f11efb648 Merge branch 'master' into bug-close-fb-win 2017-02-16 13:29:08 -07:00
David Erwin 4b2cd1731b Merge branch 'master' into add-icon-qbox 2017-02-16 15:28:15 -05:00
David Erwin 29b134af87 Merge pull request #319 from coralproject/story-139830877-bug-local-user
It changes from displayname to username
2017-02-16 15:26:51 -05:00
gaba 06d426b72b Adds back the timeout to close window. 2017-02-16 12:17:20 -08:00
Riley Davis c53aa1d93b add a badge to the queues to show the count. proptypes 2017-02-16 12:48:36 -07:00
Riley Davis 1dcdd4a9c9 make Dashboard first tab per Sam. simplify tabs 2017-02-16 12:04:24 -07:00
gaba 8e14c1986d Adds icon and style to the question box. 2017-02-16 10:33:28 -08:00
David Jay b0e81d97ec Merge branch 'master' into update-on-post-comment 2017-02-16 12:16:31 -05:00
Gabriela Rodríguez Berón c8870f6097 Merge branch 'master' into story-139830877-bug-local-user 2017-02-16 08:54:58 -08:00
David Erwin a482d6c4bf Merge pull request #318 from coralproject/flags-button
Added count clearing for status changes
2017-02-16 11:41:28 -05:00
David Erwin 3c28557736 Merge branch 'master' into flags-button 2017-02-16 11:36:57 -05:00
Belén Curcio 501e65f729 Merge branch 'master' into update-on-post-comment 2017-02-16 13:36:39 -03:00
David Erwin d54f3103f9 Merge pull request #313 from coralproject/load-more-replies
Load more replies
2017-02-16 11:35:55 -05:00
David Erwin ca1d9a04f6 Merge branch 'master' into load-more-replies 2017-02-16 11:31:32 -05:00
David Erwin b6e9e3dcf5 Merge pull request #314 from coralproject/sory-139595043-qbox
Story 139595043 Question Box to Comment Stream
2017-02-16 11:31:17 -05:00
David Erwin 5467d980df Merge branch 'master' into sory-139595043-qbox 2017-02-16 11:26:22 -05:00
David Erwin 04c0d42278 Merge branch 'master' into update-on-post-comment 2017-02-16 11:26:11 -05:00
David Erwin 57d11c63d2 Merge pull request #317 from coralproject/flag-widget
most flagged assets widget
2017-02-16 11:23:08 -05:00
gaba ccb5228a9a Errors when saving the user. 2017-02-15 18:28:49 -08:00
Wyatt Johnson 3be8ae39c4 Removed old TODO's 2017-02-15 17:53:13 -07:00
Wyatt Johnson 9ab2653054 Merge branch 'master' into flags-button 2017-02-15 17:51:56 -07:00
Wyatt Johnson fa152bda3f Added count clearning for status changes 2017-02-15 17:48:40 -07:00
Wyatt Johnson 27819d0d6a Changed aggregate -> find 2017-02-15 17:42:54 -07:00
Wyatt Johnson b75aed8bd8 Another optimization pass 2017-02-15 17:28:47 -07:00
Wyatt Johnson 9eefc0299f Optimization pass 2017-02-15 17:12:17 -07:00
Riley Davis 6c141cbce0 now with likes 2017-02-15 16:52:06 -07:00
Wyatt Johnson 56fe8ef08e Merge branch 'master' into flag-widget 2017-02-15 16:17:30 -07:00
Wyatt Johnson 93636aabac Added like summaries 2017-02-15 16:15:43 -07:00
David Jay 80c60c560a Merge branch 'master' into update-on-post-comment 2017-02-15 17:56:44 -05:00
David Jay 45b419139e Merge branch 'master' of github.com:coralproject/talk into load-more-replies 2017-02-15 17:51:30 -05:00
Riley Davis f91607cff9 show the author and publication date 2017-02-15 15:36:46 -07:00
Riley Davis da1a931a40 remove a comma 2017-02-15 15:13:54 -07:00
Riley Davis b3b36a245a even more translations en Español 2017-02-15 14:23:54 -07:00
riley b26c170a52 add some translations and link to mod queue 2017-02-15 14:11:44 -07:00
David Erwin 8a3309da92 Merge branch 'master' into sory-139595043-qbox 2017-02-15 15:50:42 -05:00
David Erwin a37e103e87 Merge pull request #315 from coralproject/flags-button
Admin actions for Flagged comments
2017-02-15 15:48:32 -05:00
Belen Curcio 1796efd3ba Ádding NONE to the FE 2017-02-15 17:34:29 -03:00
Belen Curcio 0778b7832f Merge branch 'flags-button' of github.com:coralproject/talk into flags-button 2017-02-15 17:33:30 -03:00
Belen Curcio 675f823849 no comments endpoints in /admin 🎉 2017-02-15 17:32:54 -03:00
Wyatt Johnson 57d4f9d757 Changed new comment's status from null to NONE. 2017-02-15 13:21:15 -07:00
Riley Davis efb529d7bd Merge branch 'master'
Conflicts:
	client/coral-admin/src/AppRouter.js
2017-02-15 13:03:19 -07:00
Riley Davis 6eda495663 display most flagged assets in a table 2017-02-15 12:57:51 -07:00
Gabriela Rodríguez Berón a33daea5a3 Merge branch 'master' into sory-139595043-qbox 2017-02-15 11:57:39 -08:00
gaba 68c764fd43 Rename coral-ui/FormField with TextField. 2017-02-15 11:50:21 -08:00
gaba 1d1b1b3391 Move to use FormField the right way. 2017-02-15 11:37:39 -08:00
Belén Curcio 5b16df44ed Merge branch 'master' into flags-button 2017-02-15 16:33:53 -03:00
Belen Curcio ed105f876e not Accepted ones in the Flagged queue 2017-02-15 16:19:31 -03:00
David Erwin 0747219c63 Merge pull request #306 from coralproject/load-more-comments
Load more comments
2017-02-15 14:11:16 -05:00
Belen Curcio e59534bf46 merge confict 2017-02-15 16:09:04 -03:00
David Jay 1cad1d36d5 Updating asset_id to camelcase. 2017-02-15 13:26:56 -05:00
David Jay 88843486f1 Removing log statement. 2017-02-15 13:21:34 -05:00
David Jay 79dbe7dcda Updating commentCount on comment post. 2017-02-15 13:05:31 -05:00
David Jay 904bda86db Fixing error with replies. 2017-02-15 12:36:19 -05:00
David Jay a9104b749c Updating comments and replies using updatequeries w/ optimistic updating. 2017-02-15 12:19:36 -05:00
Belen Curcio 417edc81ca Admin actions for flagged comments 2017-02-15 13:40:01 -03:00
gaba d7469d648d Replace element with name instead of id. Add style to textfield from coral-ui. 2017-02-14 17:48:10 -08:00
gaba 0f074dc034 Changes styles for Emma and Sam's design. 2017-02-14 17:13:08 -08:00
gaba d3f81353a6 Fix TextField for coral-ui and setting configure stream, question box. 2017-02-14 16:51:57 -08:00
Wyatt Johnson e640bc2cc0 Added edge for metrics, fixed client linting errors 2017-02-14 17:02:50 -07:00
David Jay 6bc610d2c6 Merge branch 'load-more-replies' of github.com:coralproject/talk into load-more-replies 2017-02-14 18:59:00 -05:00
David Jay fbf543727b Moving load more limit to constant. 2017-02-14 18:54:51 -05:00
David Jay bab2acc557 Merge branch 'master' into load-more-replies 2017-02-14 18:36:15 -05:00
gaba 52a1ebe82a Merge branch 'master' into sory-139595043-qbox 2017-02-14 15:35:43 -08:00
gaba b1d90dfd87 Fix TextField for the Configure Stream. 2017-02-14 15:31:12 -08:00
David Jay 00c2305ff7 Adding view more button to replies. 2017-02-14 18:29:59 -05:00
Riley Davis 50e3e733c3 create FlagWidget 2017-02-14 16:12:38 -07:00
Riley Davis 63982aa241 add PropTypes to LoadMore component 2017-02-14 15:27:36 -07:00
Riley Davis 0bef413a3a saving my place 2017-02-14 15:20:28 -07:00
David Jay 714c251c77 Merge branch 'master' into load-more-comments 2017-02-14 17:02:32 -05:00
David Erwin d4651d4a2f Merge pull request #312 from coralproject/disable-e2e-ci
Comment out e2e in ci
2017-02-14 17:01:46 -05:00
David Erwin 06422d7074 Comment out e2e in ci 2017-02-14 16:56:55 -05:00
David Jay e95372373c Merge branch 'master' into load-more-comments 2017-02-14 16:35:44 -05:00
David Erwin b76f3bbf91 Merge branch 'all-flag'
t push origin master
2017-02-14 16:31:01 -05:00
David Jay a07b98dcc4 Updating logout path. 2017-02-14 16:16:40 -05:00
David Jay 85ea41725a Increasing logout button load time. 2017-02-14 16:03:38 -05:00
David Jay f524f539da Increasing logout time. 2017-02-14 15:53:40 -05:00
David Jay b497dc2286 Merge branch 'master' of github.com:coralproject/talk into load-more-comments 2017-02-14 15:42:32 -05:00
David Jay c3fe9fb43e Commenting out brittle e2e test. 2017-02-14 15:41:25 -05:00
Belén Curcio 023ba74c8f Merge branch 'master' into all-flag 2017-02-14 16:34:44 -03:00
Belen Curcio 41a1eeda26 Removing all code 2017-02-14 16:10:08 -03:00
Belen Curcio bbb82b6a77 New static routes <3 for global and single assets, also, deprecating the all view 2017-02-14 16:06:13 -03:00
Riley Davis 5e2c1a9d4f Merge branch 'master'
Conflicts:
	client/coral-admin/src/translations.json
2017-02-14 12:03:54 -07:00
David Erwin daec809d87 Merge pull request #307 from coralproject/external-css-file
Configure override css file
2017-02-14 13:58:05 -05:00
David Erwin ce2213549b Merge branch 'master' into external-css-file 2017-02-14 13:40:22 -05:00
David Erwin d2c9b4b7a7 Merge pull request #310 from coralproject/close-time-bug
fix Configure in admin
2017-02-14 13:40:07 -05:00
Riley Davis 53e3147290 properly save settings to server 2017-02-14 11:29:40 -07:00
Riley Davis fe1aaba9ed revert state shape to what it was before 2017-02-14 11:05:39 -07:00
David Jay 330c37ef86 Merge branch 'load-more-comments' of github.com:coralproject/talk into load-more-comments 2017-02-14 12:27:17 -05:00
David Jay 03a198a867 Adding View More translation. 2017-02-14 12:25:39 -05:00
David Jay 6b4ea14ce2 Merge branch 'master' into load-more-comments 2017-02-14 12:00:12 -05:00
gaba f51c857e8b Merge branch 'master' into sory-139595043-qbox 2017-02-14 08:51:58 -08:00
Wyatt Johnson d987b33d99 Merge branch 'master' into flag-widget 2017-02-14 09:00:12 -07:00
David Erwin d1749e0f35 Merge branch 'master' into external-css-file 2017-02-14 09:59:37 -05:00
David Erwin b85950140b Merge pull request #309 from coralproject/install-missing
Missing reducer in Talk Installer
2017-02-14 09:59:16 -05:00
Belen Curcio f07900b294 Missing reducer 2017-02-14 11:44:22 -03:00
gaba d4109c8fca Adds textfield to coral-UI. 2017-02-13 16:51:31 -08:00
David Jay edc3671839 Updating load-more-comments style. 2017-02-13 19:32:39 -05:00
David Jay 7d617ca611 Merge branch 'master' of github.com:coralproject/talk into load-more-comments 2017-02-13 18:26:05 -05:00
Riley Davis 86d0e59147 add translation to input label 2017-02-13 16:09:06 -07:00
Riley Davis 183d2e1ea0 Merged branch master into external-css-file 2017-02-13 16:04:00 -07:00
Riley Davis 0234112ad0 enable configuration for linking override stylesheets 2017-02-13 16:03:49 -07:00
David Erwin 586d3c4c8a Merge pull request #304 from coralproject/mod-queues-ql
Moderation Queue
2017-02-13 17:40:26 -05:00
Wyatt Johnson 51cbf79f30 Changed fragment 2017-02-13 15:33:10 -07:00
gaba 1d70ae0dec Adds question enable checkbox. 2017-02-13 14:31:56 -08:00
Belen Curcio 8953182329 merge 2017-02-13 19:14:27 -03:00
gaba f9f32b1ef6 Merge branch 'master' into sory-139595043-qbox 2017-02-13 14:13:28 -08:00
gaba 1b788b98c6 Adds questionBox to the gl query 2017-02-13 14:12:32 -08:00
Belen Curcio 51be47b62e added flag functionality 2017-02-13 19:09:26 -03:00
David Erwin b30ace74b4 Merge pull request #299 from coralproject/username-displayname
Inital rename of displayName -> username
2017-02-13 17:07:56 -05:00
gaba 5a2e4b708b Adds question box 2017-02-13 13:48:03 -08:00
David Jay 1af25bb2a7 Updating mocks. 2017-02-13 16:29:56 -05:00
Belen Curcio 37ba14f3b5 breaking big words 2017-02-13 18:27:26 -03:00
Belen Curcio 519298b980 Design Pass 2017-02-13 18:12:37 -03:00
gaba b16bdafc1a Adds plugin for question box. 2017-02-13 12:22:05 -08:00
David Jay c756b22302 Switching Load More to stateless component. 2017-02-13 15:15:27 -05:00
Belen Curcio 732ef9b8bd Adding moderationList for the tests 2017-02-13 16:24:12 -03:00
Belen Curcio 72156a01b8 Merge branch 'mod-queues-ql' of github.com:coralproject/talk into mod-queues-ql 2017-02-13 16:14:48 -03:00
Belen Curcio 0403c46c6c Adding errors response 2017-02-13 16:13:03 -03:00
Belen Curcio 594b87208a GraphQL Review and Responses 2017-02-13 16:00:55 -03:00
David Jay 03b73d8d6d Merge branch 'master' into load-more-comments 2017-02-13 12:45:25 -06:00
Belén Curcio 12acbeee45 Merge branch 'master' into mod-queues-ql 2017-02-13 15:23:43 -03:00
David Jay 5b2f52a283 Restyling Load More button. 2017-02-13 13:22:50 -05:00
Wyatt Johnson e494cbd7e8 Merge branch 'master' into username-displayname 2017-02-13 11:17:29 -07:00
Riley Davis e178b2e82a Merge pull request #305 from coralproject/date-spacing
add a non-breaking space in between username and pubdate
2017-02-13 11:08:58 -07:00
riley ba51b0742f remove non-breaking space and add css 2017-02-13 11:00:28 -07:00
Riley Davis 5a0af59c45 add a non-breaking space in between username and pubdate 2017-02-13 10:53:03 -07:00
Wyatt Johnson b30a5d2184 Removed 3.4 dep 2017-02-13 10:49:47 -07:00
Belen Curcio e2ebac7176 adds new mutations: setCommentStatus 2017-02-13 14:32:48 -03:00
Wyatt Johnson 4e73d7fc23 Switched from Circle 2.0 -> 1.0, added set -e 2017-02-13 10:23:11 -07:00
Wyatt Johnson a2855a7343 Removed unused shell 2017-02-13 10:03:55 -07:00
Belen Curcio 90ed1f1056 Ban user with graphql, 100 refactor 2017-02-13 11:50:16 -03:00
Wyatt Johnson af49b0f6fb Fixed checkout order 2017-02-10 16:29:29 -07:00
Wyatt Johnson 184e63e4e3 Updated circle based on feedback 2017-02-10 16:28:09 -07:00
Wyatt Johnson de54433c5c Merge branch 'master' into username-displayname 2017-02-10 16:19:27 -07:00
David Jay 4fedb7f31e Merge branch 'master' of github.com:coralproject/talk into load-more-comments 2017-02-10 14:34:19 -08:00
Riley Davis d4e68de349 setting up dashboard :) 2017-02-10 15:32:30 -07:00
Gabriela Rodríguez Berón b962d672f4 Merge pull request #300 from coralproject/story-137818425-tag-staff
Tag comments made by Staff with 'STAFF' tag automatically.
2017-02-10 13:48:41 -08:00
gaba e2e40ca2b3 Merge branch 'master' into story-137818425-tag-staff 2017-02-10 13:42:50 -08:00
David Erwin 4db2d63ee2 Merge pull request #302 from coralproject/like-bugfix
fix very annoying Like bug
2017-02-10 16:35:20 -05:00
gaba 11ef12c14c Adds padding and resolves when is not staff. 2017-02-10 13:33:57 -08:00
Belen Curcio 894fec9fb6 adding context for moderation 2017-02-10 17:55:51 -03:00
Riley Davis a5e66e61f3 Merge branch 'master' into like-bugfix 2017-02-10 13:26:55 -07:00
Riley Davis 81a408a0b1 fix very annoying Like bug 2017-02-10 13:21:58 -07:00
Belen Curcio bc14b908eb banUsers and moderate separate streams, not found assets etc 2017-02-10 17:15:26 -03:00
gaba 498eb5eb91 Fix the test. 2017-02-10 12:06:36 -08:00
David Erwin 5c4daa72cb Merge pull request #297 from coralproject/actions
Actions Metadata
2017-02-10 14:53:46 -05:00
gaba c80f406b21 Add tests 2017-02-10 11:52:02 -08:00
Wyatt Johnson c637be364a Moved tag creation into mutator 2017-02-10 12:00:50 -07:00
Gabriela Rodríguez Berón 7325525716 Merge branch 'master' into story-137818425-tag-staff 2017-02-10 10:32:43 -08:00
gaba 71fcf9cd3f FE for the Staff Tag. Modifed typedfs graphql. 2017-02-10 10:20:34 -08:00
Wyatt Johnson 2c2541e7be Added fix for not logged in user 2017-02-10 11:00:12 -07:00
Riley Davis 27ea409e31 html things 2017-02-10 10:52:55 -07:00
Belen Curcio 9320ece35b Merge branch 'master' of github.com:coralproject/talk into mod-queues-ql 2017-02-10 08:57:35 -03:00
David Jay 05fcc61c9c Resolving bug on comment load. 2017-02-09 17:56:28 -08:00
David Jay 52099efed9 Updating format of returned comments in CommentBox. 2017-02-09 17:27:16 -08:00
David Jay b4ee765437 Merge branch 'master' into actions 2017-02-09 17:14:53 -08:00
Wyatt Johnson fded4f0a91 Added new machine type 2017-02-09 18:05:47 -07:00
Wyatt Johnson c202042cda Revert "Revert "Adjusted circle""
This reverts commit 832d1f6763.
2017-02-09 18:04:31 -07:00
Wyatt Johnson 832d1f6763 Revert "Adjusted circle"
This reverts commit 229ddb11f2.
2017-02-09 18:02:25 -07:00
David Jay 1272dabb4b Updating frontend actions to new format. 2017-02-09 17:01:16 -08:00
Wyatt Johnson 229ddb11f2 Adjusted circle 2017-02-09 17:37:39 -07:00
Wyatt Johnson 7db3da5fdd Added fix for facebook 2017-02-09 17:03:41 -07:00
Wyatt Johnson 3cd2b28bc2 Inital rename of displayName -> username 2017-02-09 16:48:59 -07:00
gaba 10937cf717 Tag.name as string. Debuging comment.tag in Embed stream. 2017-02-09 15:28:09 -08:00
gaba 647ae2fca0 Assigned by will be a string for now. 2017-02-09 14:25:35 -08:00
Wyatt Johnson 646f6040ac Merge pull request #291 from coralproject/recent-items
Added new recentComments, recentReplies
2017-02-09 14:24:04 -07:00
Wyatt Johnson 91e94e8400 Merge branch 'master' into recent-items 2017-02-09 14:11:03 -07:00
gaba 3be4fea8ad Merge branch 'master' into story-137818425-tag-staff 2017-02-09 12:32:26 -08:00
gaba 1c26e64c71 Adds Tags to the graphql. 2017-02-09 12:23:40 -08:00
Gabriela Rodríguez Berón a825d29b60 Merge pull request #298 from coralproject/bugfix-9000
Bugfix 9000
2017-02-09 12:22:59 -08:00
David Erwin cd7d683fe2 Fixing for default url case 2017-02-09 15:16:02 -05:00
David Erwin 81e050214d fix js error 2017-02-09 15:03:29 -05:00
gaba f2341ca9d3 Adds tag label plugin. 2017-02-09 09:17:56 -08:00
Belen Curcio d3184d9afb Merge issues and Settings reducer updated 2017-02-09 13:58:55 -03:00
Belen Curcio e6087dfa25 Go to Moderate from Stream 2017-02-09 13:53:23 -03:00
Belen Curcio e234fa7e0b Reducers refactor, working Moderate Queue 2017-02-09 07:00:39 -03:00
Wyatt Johnson 25103630b5 Added graphql docs endpoint. 2017-02-08 17:41:28 -07:00
Wyatt Johnson fb9bedc57b Added new action rep, refactored mutations 2017-02-08 17:41:11 -07:00
Riley Davis ddeb28d729 have cursor be the date of the oldest comment 2017-02-08 16:27:09 -07:00
Riley Davis d4d1f088cd Merge branch 'master' into recent-items 2017-02-08 14:50:06 -07:00
Riley Davis ddc26f0cec Merge pull request #296 from coralproject/admin-apollo
Using GraphQL and Apollo in Talk Admin. [Don't Merge]
2017-02-08 14:17:09 -07:00
Riley Davis 0d141e7472 explicit return for clarity 2017-02-08 14:12:17 -07:00
gaba 85dabae355 Merge branch 'master' into story-137818425-tag-staff 2017-02-08 12:50:14 -08:00
Riley Davis a3f99f76e2 Merged branch master into admin-apollo 2017-02-08 13:42:37 -07:00
David Erwin 2352a21826 Merge pull request #295 from coralproject/story-138524853-domain-whitelist
Story 138524853 domain whitelist
2017-02-08 15:38:38 -05:00
gaba 075e62581e Tags should be an array 2017-02-08 09:03:21 -08:00
David Jay 0c777cbad4 Adding loadMore function. 2017-02-08 08:59:38 -08:00
Belen Curcio dc35425afd Moderate Queue by assetId 2017-02-08 13:31:41 -03:00
Belen Curcio fab0c7e74d Mod Queue Working 2017-02-08 12:39:35 -03:00
Belen Curcio 6a6574239f Mod Queue Working 2017-02-08 12:36:05 -03:00
gaba fc880de9bf Backend for the tags staff on staff users. 2017-02-07 16:31:39 -08:00
Gabriela Rodríguez Berón f014338083 Merge branch 'master' into story-138524853-domain-whitelist 2017-02-07 15:38:09 -08:00
gaba 1176a94bec Update tests 2017-02-07 15:36:10 -08:00
Belen Curcio a4d62f78f2 Apollo config ready 2017-02-07 20:10:18 -03:00
Belén Curcio 604a3a42b9 Merge pull request #293 from coralproject/install-admin
Install Talk
2017-02-07 19:43:37 -03:00
Gabriela Rodríguez Berón 5b609ceb86 Merge branch 'master' into story-138524853-domain-whitelist 2017-02-07 14:20:48 -08:00
gaba 3effd11ad2 Adding test. 2017-02-07 13:57:46 -08:00
Wyatt Johnson 4e1891c068 Fixed install state. 2017-02-07 14:30:25 -07:00
gaba d08a9fb7b2 Front End for Domain Whitelist. 2017-02-07 13:07:22 -08:00
Belen Curcio fd40135f1b Merge branch 'master' of github.com:coralproject/talk into install-admin 2017-02-07 17:46:45 -03:00
Belen Curcio a3e1bd972c Redirect if its initialized, funtionallity of finalStep 2017-02-07 17:44:27 -03:00
Belen Curcio f8d83ffbde Rest of the embed side config 2017-02-07 16:15:53 -03:00
Belen Curcio 3fc7fbd01e Ádmin running on Apollo 2017-02-07 16:06:32 -03:00
David Erwin f188df162c Merge pull request #294 from coralproject/wrong-error-proptype
fix a warning in the console when you're signed out
2017-02-07 13:02:18 -05:00
David Erwin fcd431dc21 Merge branch 'master' into wrong-error-proptype 2017-02-07 12:51:20 -05:00
Riley Davis ca2c55f643 Merge pull request #292 from coralproject/bugfix-tiny-mobile-fonts
Fixing mobile viewport width.
2017-02-07 10:50:30 -07:00
Riley Davis 9d3960fd7b fix a warning in the console when you're signed out 2017-02-07 10:37:38 -07:00
Wyatt Johnson 5cdfc7bd1b Merge branch 'master' into install-admin 2017-02-07 10:05:31 -07:00
Riley Davis cd3d08c6f9 Merge branch 'master' into bugfix-tiny-mobile-fonts 2017-02-07 10:05:20 -07:00
Wyatt Johnson cdc81c4fc5 Added new setup endpoint 2017-02-07 10:02:23 -07:00
Wyatt Johnson f47638ae07 Removed provisioning text. 2017-02-07 09:45:43 -07:00
Belen Curcio c837e89b38 wip 2017-02-07 13:43:17 -03:00
Belen Curcio 526f0145f1 if goTo is not defined 2017-02-07 02:06:32 -03:00
Kim Gardner ba450a0166 Merge pull request #283 from coralproject/username-ban
Username banning flow
2017-02-06 23:04:43 -05:00
Belen Curcio 0b9956a842 Merge branch 'master' of github.com:coralproject/talk into install-admin 2017-02-07 00:16:51 -03:00
Belen Curcio e239926866 Failure check 2017-02-07 00:07:53 -03:00
David Jay 586537084b Merge branch 'master' into bugfix-tiny-mobile-fonts 2017-02-06 18:29:34 -08:00
Belen Curcio 3a953c7efe First Step 2017-02-06 23:23:08 -03:00
Belen Curcio 77f090fd3c Multiple validations 2017-02-06 23:18:14 -03:00
David Jay 652170735b Merge branch 'master' into username-ban 2017-02-06 18:14:13 -08:00
Belen Curcio 16f93a0a19 Validaiton 2017-02-06 22:41:21 -03:00
Kim Gardner f19e0134db Merge pull request #286 from coralproject/more-email-verify
Cleanup on email verification
2017-02-06 20:26:07 -05:00
David Jay ecb72b036d Fixing mobile viewport width. 2017-02-06 16:49:31 -08:00
David Jay 80ea100fc5 Making username update services atomic. 2017-02-06 15:49:48 -08:00
Riley Davis 8c8a8eb3b8 define more propTypes. add a second button so tests work 2017-02-06 16:23:00 -07:00
gaba d93dfe55af Merge branch 'master' into story-138524853-domain-whitelist 2017-02-06 14:53:46 -08:00
David Jay b22895b908 Switching displayname update to account endpoint. 2017-02-06 14:38:14 -08:00
Riley Davis fe6d6f611d pause suddenly not a function? 2017-02-06 14:59:23 -07:00
Riley Davis 93ee5bfc4a just throwing stuff at the wall to see what sticks 2017-02-06 14:42:40 -07:00
Riley Davis 3a145e9927 add a pause to see if anything changes 2017-02-06 14:22:37 -07:00
gaba 8d4f87a95d Domain List. 2017-02-06 13:11:39 -08:00
Riley Davis 36ba9bd615 Merge branch 'master' into more-email-verify 2017-02-06 13:39:34 -07:00
Wyatt Johnson 7bef103217 Added new recentComments, recentReplies 2017-02-06 12:54:55 -07:00
David Jay 1901592780 Switching displayname edit after facebook auth to account endpoint. 2017-02-06 11:07:35 -08:00
David Jay cb78e8d563 Merge branch 'master' into username-ban 2017-02-06 10:23:14 -08:00
Kim Gardner adae7aadf3 Merge pull request #290 from coralproject/losowsky-patch-1
added product link, note about alpha
2017-02-04 15:03:32 -05:00
Andrew Losowsky 3815267919 added product link 2017-02-04 09:31:23 -05:00
Riley Davis ff3c49c7bd Merged branch master into more-email-verify 2017-02-03 14:21:40 -07:00
Riley Davis e91ff306e9 whoops, missed one 2017-02-03 14:21:22 -07:00
David Erwin c8bb59be5b Merge pull request #289 from coralproject/circle-release-fix
Circle release fix
2017-02-03 16:16:00 -05:00
David Erwin bc14a9a758 Merge branch 'master' into circle-release-fix 2017-02-03 16:11:23 -05:00
Riley Davis a0c03605b9 Merge branch 'master'
Conflicts:
	client/coral-embed-stream/src/Embed.js
	client/coral-framework/constants/auth.js
	client/coral-framework/reducers/auth.js
	client/coral-sign-in/components/SignUpContent.js
2017-02-03 13:12:32 -07:00
Riley Davis 3441ec36c6 re-direct to sign in when email verification is off. add proptypes 2017-02-03 12:50:01 -07:00
Benjamin Goering 5614b9781e Merge branch 'master' into 20170203_futureproof_embed_code 2017-02-04 03:31:33 +08:00
Benjamin Goering f9cfdd07a5 update e2e tests to find iframe using selector that works with coral-embed 2017-02-04 03:25:15 +08:00
David Jay c784de6e95 Updating e2e tests. 2017-02-03 11:15:10 -08:00
Belen Curcio 8664a16bfc modQueue Query 2017-02-03 16:05:27 -03:00
Riley Davis 5ae078ff6e Merge pull request #277 from coralproject/story-134624635
Adds a screen to create username after facebook signup
2017-02-03 11:55:00 -07:00
Wyatt Johnson 35919740fd Uncommented things that were 2017-02-03 11:39:31 -07:00
Wyatt Johnson 5caade2409 Adjusted circle scripts for current usage of flags 2017-02-03 11:36:49 -07:00
Benjamin Goering 838e2a66e3 trivial change to trigger CircleCI (wyatt asked me to) 2017-02-04 02:35:58 +08:00
David Jay 2dacb72341 Updating reload code. 2017-02-03 10:27:17 -08:00
Wyatt Johnson fb8450c7ff Fixed issues with default setup 2017-02-03 11:22:35 -07:00
David Jay d5abe78a3d Merge branch 'username-ban' of github.com:coralproject/talk into username-ban 2017-02-03 10:20:29 -08:00
David Jay 6e92f8c3d6 Merge branch 'master' of github.com:coralproject/talk into username-ban 2017-02-03 10:19:21 -08:00
Benjamin Goering 1e8a363205 remove use of template strings in coral-embed script as some browsers will throw exceptions on them 2017-02-04 02:18:54 +08:00
David Jay d92c2d9744 Updating user queue routes. 2017-02-03 10:18:29 -08:00
Benjamin Goering bbdd9f22c1 re-copypaste pym.js into coral-embed, as eslint-fix had changed it to use template strings 2017-02-04 02:11:59 +08:00
Wyatt Johnson f488b0e02d Added BE for setup
- Adjusted docs
- Fixed issues with process terminating wrong
2017-02-03 11:05:46 -07:00
Benjamin Goering 160fb6e4d4 Add async attribute to coral-embed scripts 2017-02-04 02:03:31 +08:00
Riley Davis 8f374852ac hide social stuff after sign up 2017-02-03 10:49:52 -07:00
Benjamin Goering 841d6add45 Rename /Coral.js to /embed.js 2017-02-04 01:48:57 +08:00
Benjamin Goering b9c918eb88 Add pym.js LICENSE to coral-embed 2017-02-04 01:29:24 +08:00
Riley Davis 5c365b1a79 YOU. SHALL. NOT. PASS 2017-02-03 10:19:56 -07:00
Benjamin Goering f1010462ec Dont' use const/let in coral-embed so it works in all browsers 2017-02-04 01:19:47 +08:00
Riley Davis c4b934a542 whoops. commited to the wrong branch 2017-02-03 10:19:18 -07:00
Riley Davis a6f7cf258b attempt to make e2e tests pass 2017-02-03 10:15:51 -07:00
Benjamin Goering fb60b088b3 Rename coral-embd-api to coral-embed 2017-02-04 01:15:35 +08:00
Benjamin Goering e376e19c2c remove console.log from coral-embed-api 2017-02-04 01:00:46 +08:00
Benjamin Goering f7cddad659 Futureproof Embed Code
* Add client/coral-embed-api module. It's meant to be included in an HTML page and define window.Coral
* Serve coral-embed-api at /Coral.js
* Update / handler (article view) to use new embed format
* Update Talk Admin embed code section to use new embed format
2017-02-04 00:47:32 +08:00
Belen Curcio 80241025e5 linitin 2017-02-03 13:43:17 -03:00
gaba b5c9c1c731 Returns the real displayname and not the one transformed. 2017-02-02 16:03:14 -08:00
gaba 7da141ed29 Dispatch other action to only trigger change when sign up. 2017-02-02 15:44:45 -08:00
gaba d74d2e9e87 Dispatchs the user. 2017-02-02 15:25:45 -08:00
gaba c272b88340 It returns user now. 2017-02-02 15:24:55 -08:00
Riley Davis 5a5c04f557 break the line as per the designs 2017-02-02 15:57:54 -07:00
Riley Davis 98c3d11815 add a translation 2017-02-02 15:46:51 -07:00
Riley Davis a53db77c21 redirect back to the proper page 2017-02-02 15:44:12 -07:00
Riley Davis a0cf467f1b remove confirm and replace with verify 2017-02-02 14:59:45 -07:00
David Jay b8b66bc50c Merge branch 'master' into username-ban 2017-02-02 12:51:11 -05:00
Wyatt Johnson 2df980b560 Merge branch 'master' into install-admin 2017-02-02 10:34:17 -07:00
David Jay feb0860ba1 Merge branch 'master' of github.com:coralproject/talk into username-ban 2017-02-02 12:29:29 -05:00
Riley Davis bb798b8b02 Merge branch 'master' into story-134624635 2017-02-02 10:05:17 -07:00
David Erwin 03c240054a Merge pull request #284 from coralproject/stream-admin-bugfix
Fix admin user stream bug
2017-02-02 12:02:06 -05:00
David Erwin 79a2a2144c Fix admin user stream bug 2017-02-02 11:51:09 -05:00
gaba d53dc09aed Merge branch 'master' into story-134624635 2017-02-02 08:16:50 -08:00
David Erwin ce002e366d Merge pull request #282 from coralproject/hide-pending-sometimes
Hide Pre-Mod tab sometimes
2017-02-02 10:33:00 -05:00
Belén Curcio 7fe6002219 Merge branch 'master' into hide-pending-sometimes 2017-02-02 12:28:17 -03:00
Belén Curcio 3273749fbb Merge pull request #269 from coralproject/design-pass-tweaks
Design pass tweaks
2017-02-02 12:21:47 -03:00
David Erwin 22e32b1869 Merge branch 'master' into hide-pending-sometimes 2017-02-02 10:16:53 -05:00
Belén Curcio 04669350a1 Merge branch 'master' into design-pass-tweaks 2017-02-02 12:15:23 -03:00
David Erwin b0045fd8a6 Merge pull request #281 from coralproject/add-yarn
Add yarn + Upgrade Webpack
2017-02-02 10:14:07 -05:00
Wyatt Johnson bd86eef441 Merge branch 'master' into add-yarn 2017-02-02 08:02:06 -07:00
Belen Curcio 65aa5e7e92 Ádding Select, Options to Coral Ui and steps with formField 2017-02-02 11:31:43 -03:00
Belén Curcio b4a03a5133 Merge branch 'master' into design-pass-tweaks 2017-02-02 07:55:41 -03:00
Belen Curcio ad8d92a8ea Merge branch 'master' of github.com:coralproject/talk into install-admin 2017-02-01 23:08:22 -03:00
David Jay 97c3ebc3e0 Whitelisting usernames once they have been approved. 2017-02-01 20:55:45 -05:00
David Jay 5831a6f927 Merge branch 'master' of github.com:coralproject/talk into username-ban 2017-02-01 20:00:54 -05:00
David Jay d703cc2168 Prompting user to update name when banned. 2017-02-01 19:53:37 -05:00
Gabriela Rodríguez Berón d8ab3b8fba Merge branch 'master' into story-134624635 2017-02-01 14:03:56 -08:00
gaba 445eb97d76 Moves create displayname dialog to sign-in plugin. 2017-02-01 14:00:09 -08:00
David Jay e3da45daeb Displaying appropriate suspension message. 2017-02-01 16:38:01 -05:00
Riley Davis 13114e7530 Merge pull request #276 from coralproject/bugfix-asset-search
Addressing console errors in asset search.
2017-02-01 14:35:42 -07:00
gaba 4df09ffbd2 Validation for the field underneath. 2017-02-01 13:29:47 -08:00
gaba 0c360e62a6 Display errors that come from the backend. 2017-02-01 13:20:17 -08:00
David Jay 7e907e18f2 Enabling canEditName when suspending user. 2017-02-01 16:19:42 -05:00
David Jay 5dca1611bc Moving account endpoint tests to account. 2017-02-01 15:58:07 -05:00
David Jay b2430eaf60 Adding displayname editing and appropriate toggle. 2017-02-01 15:55:33 -05:00
gaba ee0bb50dd5 Adds errors on field when not validated. 2017-02-01 12:34:02 -08:00
Riley Davis 6e118c93aa Merged branch master into hide-pending-sometimes 2017-02-01 13:33:42 -07:00
Riley Davis 22c0ed686f rename everything to Pre-Mod from Pending. hide the pre-mod tab sometimes. 2017-02-01 13:33:29 -07:00
David Jay 265860bccb Merge branch 'master' into bugfix-asset-search 2017-02-01 15:16:36 -05:00
Wyatt Johnson 064ccfbbb3 Added build step for static assets 2017-02-01 13:12:40 -07:00
gaba 990685f17a Merge branch 'master' into story-134624635 2017-02-01 12:11:01 -08:00
gaba a8e4ce466d Adds validation to the dialog. 2017-02-01 12:09:27 -08:00
Wyatt Johnson e4722249b6 Added node env to environment 2017-02-01 13:07:14 -07:00
Wyatt Johnson 3a87bdc282 Merge branch 'master' into add-yarn 2017-02-01 12:56:52 -07:00
Wyatt Johnson 7beb28e077 Updated documentation 2017-02-01 12:54:15 -07:00
Wyatt Johnson 6b582fdb2c Updated docker + build 2017-02-01 12:54:04 -07:00
Gabriela Rodríguez Berón f45429f510 Merge pull request #280 from coralproject/display-name-fix
Allowing uppercase letters in the display name.
2017-02-01 11:46:38 -08:00
riley 4a9c996cba enforce uniqueness case-independent 2017-02-01 12:39:16 -07:00
Wyatt Johnson f4177f318f Upgraded webpack 2017-02-01 12:34:17 -07:00
Wyatt Johnson 723dee3394 Added yarn.lock back 2017-02-01 12:33:50 -07:00
gaba 3d301b7e70 Allowing uppercase letters in the display name. 2017-02-01 10:51:33 -08:00
gaba c9b12fc5d6 Thanks git-godesses for the log on finding a bad remove when merging. 2017-02-01 10:46:32 -08:00
gaba f4d672ddcf Merge branch 'master' into story-134624635 2017-02-01 10:42:00 -08:00
Riley Davis ec956165a3 Merge branch 'master' into bugfix-asset-search 2017-02-01 11:35:59 -07:00
Gabriela Rodríguez Berón 81ae482524 Merge pull request #279 from coralproject/graphql-hotfix
fix a bug per wyatt advice
2017-02-01 10:28:36 -08:00
riley 00297d1a1a fix a bug per wyatt advice 2017-02-01 11:22:42 -07:00
Belen Curcio 7866d1f8cd Done Wizard mEnu 2017-02-01 15:17:15 -03:00
Gabriela Rodríguez Berón f7c43a7853 Merge pull request #270 from coralproject/timeago-fix
Fixing bug where timeAgo cannot interpret dates from mongoose properly.
2017-02-01 09:57:30 -08:00
Gabriela Rodríguez Berón e1650aa721 Merge branch 'master' into timeago-fix 2017-02-01 09:52:40 -08:00
Wyatt Johnson d1cf6fd19b Applied patch 2017-02-01 10:45:01 -07:00
David Jay 6a4c168882 Merge pull request #275 from coralproject/mobile-width-fix
Updating mock article template to support mobile widths.
2017-02-01 12:37:44 -05:00
David Jay 37975574ea Switching rejected usernames from suspended to banned. 2017-02-01 12:26:01 -05:00
David Jay 17eefeea15 Merge branch 'master' into mobile-width-fix 2017-02-01 12:16:57 -05:00
David Jay 26ce3c635d Merge pull request #274 from coralproject/emailus-veritas
Verify Email upon signup
2017-02-01 12:13:28 -05:00
David Jay 37d688f99d Merge branch 'mobile-width-fix' of github.com:coralproject/talk into mobile-width-fix 2017-02-01 12:12:14 -05:00
David Jay bc6c77176e Fixing error in styling. 2017-02-01 12:11:12 -05:00
David Jay e24b953f51 Merge branch 'master' into emailus-veritas 2017-02-01 12:06:57 -05:00
David Erwin 653e67c9c4 Merge pull request #271 from coralproject/e2e-port-fix
Switching e2e to port 3011.
2017-02-01 11:59:13 -05:00
David Jay 6c01a37f6a Removing user bio. 2017-02-01 11:48:16 -05:00
Belen Curcio caa04de09d linting and proptypes 2017-02-01 12:12:28 -03:00
Belen Curcio 12070309d6 Wizard Nav 2017-02-01 12:11:15 -03:00
Belen Curcio 6a4315d608 Working Install Wizard 2017-02-01 10:49:16 -03:00
Belen Curcio 6c318b0502 Merge branch 'master' of github.com:coralproject/talk into install-admin 2017-02-01 09:57:02 -03:00
gaba 097934dee5 Merge branch 'master' into story-134624635 2017-01-31 15:00:14 -08:00
gaba 282c7dc4f5 Removes duplicate func and adds tests. 2017-01-31 14:04:31 -08:00
gaba 37eae51641 Adds component to coral-framework to deal with the change of displayname. 2017-01-31 13:52:40 -08:00
gaba a377f61bdd Adds service and endpoint to change displayname. 2017-01-31 13:51:36 -08:00
gaba 80122dfdce Adds formfield to components. 2017-01-31 13:48:45 -08:00
Riley Davis f5d873ba57 use referer from headers 2017-01-31 13:45:23 -07:00
David Jay 20bdf648d7 Merge branch 'e2e-port-fix' of github.com:coralproject/talk into e2e-port-fix 2017-01-31 15:40:09 -05:00
Riley Davis 55dc229b32 Merged branch emailus-veritas into emailus-veritas 2017-01-31 13:29:34 -07:00
Riley Davis e6e8ad5ca6 close the resend email box after use. remove dead code 2017-01-31 13:29:23 -07:00
David Jay 5d185e7aca Updating port in globals. 2017-01-31 15:21:21 -05:00
Wyatt Johnson a13b1c95d6 Adjusted form 2017-01-31 12:57:09 -07:00
David Jay f44c1d6f0a Merge branch 'master' into timeago-fix 2017-01-31 14:41:58 -05:00
David Jay 4a6b09ac96 Merge branch 'master' into e2e-port-fix 2017-01-31 14:41:43 -05:00
David Jay 64d2aa7f42 Merge branch 'master' into mobile-width-fix 2017-01-31 14:41:30 -05:00
David Jay ae3b8a6474 Merge branch 'master' into bugfix-asset-search 2017-01-31 14:41:22 -05:00
David Jay 7babb14bab Addressing bug in asset search. 2017-01-31 14:37:52 -05:00
Belen Curcio 890da0400f Steps 2017-01-31 16:31:29 -03:00
David Jay 3ca263bcfb Updating mock article template to support mobile widths. 2017-01-31 14:23:15 -05:00
Riley Davis ec1dfefc5d lint 2017-01-31 12:15:33 -07:00
Riley Davis 3d83c246f7 simplify email text 2017-01-31 12:08:59 -07:00
Riley Davis e63e1f3428 Merge branch 'master'
Conflicts:
	errors.js
	test/routes/api/auth/index.js
2017-01-31 12:05:16 -07:00
Riley Davis 56ddc49386 resend verify email 2017-01-31 12:04:19 -07:00
Belen Curcio 6fd2a927f1 Linted 2017-01-30 20:51:28 -03:00
Belen Curcio 76f55b4524 Cursor on user name, and Reply Bio box 2017-01-30 20:50:25 -03:00
Belen Curcio 5b926b9b85 merge 2017-01-30 20:42:15 -03:00
David Jay e8d8c7af5b Switching e2e to port 3011. 2017-01-30 18:04:17 -05:00
Riley Davis 976c821715 move FormField to coral-ui 2017-01-30 16:00:24 -07:00
Belen Curcio 0881201465 Notification 2017-01-30 20:00:01 -03:00
David Jay 235214642e Fixing bug where timeAgo cannot interpret dates from mongoose properly. 2017-01-30 17:55:06 -05:00
Belen Curcio 5d2ed3f585 Notifications 2017-01-30 19:48:07 -03:00
Belen Curcio f83def83bd tab 2017-01-30 19:38:58 -03:00
Belen Curcio f9363e86d0 Merge 2017-01-30 17:24:57 -03:00
Belen Curcio 2e1dd35de3 Permalink box 2017-01-30 16:47:26 -03:00
Belen Curcio 019e2a4fa9 Flag order 2017-01-30 16:37:00 -03:00
gaba e803593fc7 Move stuff around. 2017-01-30 11:34:04 -08:00
Belen Curcio f5dc5ed8b7 Test updated 2017-01-30 15:53:29 -03:00
Belen Curcio 744f2085da Lint 🎉 2017-01-30 15:38:26 -03:00
Belen Curcio e4839468e3 Merge branch 'master' of github.com:coralproject/talk into design-pass-tweaks 2017-01-30 15:34:23 -03:00
Belen Curcio b316d626f5 Bio and Embed Stream refresh 2017-01-30 15:33:08 -03:00
Belen Curcio f7725f4143 Notifications working everywhere :D 2017-01-30 15:27:09 -03:00
Belen Curcio 7a227dff91 Notification in the CommentStream Working 2017-01-30 15:11:16 -03:00
Belen Curcio 805b5cb441 Lintin 2017-01-30 09:04:00 -03:00
Belen Curcio c42fea5889 SnackBar 2017-01-30 08:19:18 -03:00
Belen Curcio 8fe8f6c690 Working permalink 2017-01-30 07:56:08 -03:00
Belen Curcio 177a4d8f2e Permalinks and Embed 2017-01-30 07:32:19 -03:00
Belen Curcio 0105b1d4ac Permalink 2017-01-30 06:57:12 -03:00
Belen Curcio 61d24bad60 User bio, onClick and onMouseOver - Styles and Tooltip 2017-01-30 06:17:39 -03:00
Belen Curcio e5976576c1 merge 2017-01-30 05:37:25 -03:00
Riley Davis 5e521b832c add a confirm-email landing page 2017-01-27 15:04:42 -07:00
Belen Curcio 1778c19f6a çhanges 2017-01-27 14:55:55 -03:00
Belen Curcio 58c4d5ceb5 Comment button order 2017-01-26 16:09:56 -03:00
Belen Curcio dc89b22abb Clicking user goes to Settings Tab 2017-01-26 15:39:16 -03:00
295 changed files with 15839 additions and 3311 deletions
+1
View File
@@ -1,2 +1,3 @@
node_modules
test
.git
-1
View File
@@ -12,4 +12,3 @@ dump.rdb
*.cfg
.idea/
coverage/
yarn.lock
+7 -9
View File
@@ -1,9 +1,10 @@
# Contributor's Guide
Welcome! We are very excited that you are interested in contributing to Talk.
Welcome! We are very excited that you are interested in contributing to Talk.
This document is a companion to help you approach contributing. If it does not do so, please [let us know how we can improve it](https://github.com/coralproject/talk/issues)!
By contributing to this project you agree to the [Code of Conduct](https://coralproject.net/code-of-conduct.html).
## Product Roadmap
@@ -13,7 +14,7 @@ You can view product ideas and our longer term roadmap here https://trello.com/b
## Contribute to the documentation
Clear docs are a prerequisite for a successful open source project. We value non-code and code contributions equally.
Clear docs are a prerequisite for a successful open source project. We value non-code and code contributions equally.
We are looking for _documentarians_ to:
@@ -40,7 +41,7 @@ Talk is designed to integrate into existing environments in a variety of ways:
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 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.
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.
## Write some code
@@ -48,9 +49,9 @@ First, [set up a dev environment](https://github.com/coralproject/talk/blob/mast
### Build a New Feature / Plugin
Talk is beginning life as a Commenting Platform, but is architected to support many varieties of community engagement.
Talk is beginning life as a Commenting Platform, but is architected to support many varieties of community engagement.
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.
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.
To get an idea of where the Coral Team is going, see:
@@ -66,13 +67,10 @@ Examples:
### Work on the Core
There is always more work to be done to make an application more stable, scaleable and secure.
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).
+6 -3
View File
@@ -1,5 +1,8 @@
FROM node:7
# Install yarn
RUN npm install -g yarn
# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
@@ -11,10 +14,10 @@ ENV TALK_PORT 5000
EXPOSE 5000
# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install --production
COPY package.json yarn.lock /usr/src/app/
RUN yarn install --production
# Bundle app source
COPY . /usr/src/app
CMD [ "npm", "start" ]
CMD [ "yarn", "start" ]
+64 -65
View File
@@ -1,95 +1,94 @@
# Installing a dev environment
By contributing to this project you agree to the [Code of Conduct](https://coralproject.net/code-of-conduct.html).
# Installation
## Requirements
### System
- Any flavor of Linux, OSX or Windows
- Any flavour of Linux, OSX or Windows
- 1GB memory (minimum)
- 5GB storage (minimum)
### Software
## Installation From Source
* [Node](https://nodejs.org/es/download/package-manager) v7 or later
* Mongo v3.2 or later
* Redis v3.2 or later
### Requirements
There are some runtime requirements for running Talk from source:
- [Node](https://nodejs.org/) v7 or later
- [MongoDB](https://www.mongodb.com/) v3.4 or later
- [Redis](https://redis.io/) v3.2 or later
- [Yarn](https://yarnpkg.com/) v0.19.1 or later
_Please be sure to check the versions of these requirements. Insufficient versions of these may lead to unexpected errors!_
## First time setup
### Installing
### Installation
```bash
# Download the tarball containing the repository
curl -L https://github.com/coralproject/talk/tarball/master -o coralproject-talk.tar.gz
Navigate to a directory.
# Untar that file and change to that directory
tar xpf coralproject-talk.tar.gz
mv coralproject-talk-* coralproject-talk
cd coralproject-talk
```
git clone https://github.com/coralproject/talk
cd talk
npm install
# Install package dependancies
yarn
# Build static files
yarn build
```
### Environmental Variables
### Running
Talk uses environmental variables for configuration. You can learn about them in the [README file](README.md).
Refer to the `README.md` file for required configuration variables to add to the
environment.
You can start the server after configuring the server using the command:
## Workflows
### The server
Starting the server:
```
npm start
```bash
yarn start
```
Browse to `http://localhost:3000` (or your custom port.)
You can see other scripts we've made available by consulting the `package.json`
file under the `scripts` key including:
### Building the front end
- `yarn test` run unit tests
- `yarn e2e` run end to end tests
- `yarn build-watch` watch for changes to client files and build static assets
- `yarn dev-start` watch for changes to server files and reload the server
Our build process will build all front end components registered [here](https://github.com/coralproject/talk/blob/6052cac1d3494f8060325a88bb2ce03c88c2f94c/webpack.config.dev.js#L9-L15).
## Installation From Docker Hub
One time build:
### Requirements
```
npm build
There are some runtime requirements for running Talk for Docker:
- [MongoDB](https://www.mongodb.com/) v3.2 or later
- [Redis](https://redis.io/) v3.2 or later
- [Docker](https://www.docker.com/) v1.13.0 or later
- [Docker Compose](https://docs.docker.com/compose/) v1.10.0 or later
_Please be sure to check the versions of these requirements. Insufficient versions of these may lead to unexpected errors!_
### Installing
```bash
# Create a directory for talk
mkdir coralproject-talk
cd coralproject-talk
# Download the docker-compose.yml file from the repository
curl -LO https://raw.githubusercontent.com/coralproject/talk/master/docker-compose.yml
```
Build, then rebuild when a file is updated (development build):
At this stage, you should refer to the `README.md` file for required
configuration variables to add to the environment key for the `talk` service
listed in the `docker-compose.yml` file.
### Running
```bash
# Start the services using compose
docker-compose up -d
```
npm build-watch
```
### Testing
Run all tests once:
`
npm test
`
Run our end to end tests (will install Selenium and nightwatch):
`
npm run e2e
`
_Please ensure all tests are passing before submitting a PR!_
## Troubleshooting
##### Can't ping the redis server!
- Check that Redis Server is running.
- Check that TALK_REDIS_URL is set.
##### Authenticaiton doesn't work!
- Make sure Redis is the correct version.
+3 -2
View File
@@ -1,6 +1,6 @@
# Talk [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk)
A commenting platform from [The Coral Project](https://coralproject.net).
A commenting platform from [The Coral Project](https://coralproject.net). Talk enters a closed beta in March 2017, but you can download the code for our alpha here. [Read more about Talk here.](https://coralproject.net/products/talk.html)
## Contributing to Talk
@@ -12,7 +12,7 @@ See our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CO
To set up a development environment or build from source, see [INSTALL.md](https://github.com/coralproject/talk/blob/master/INSTALL.md).
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!!
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
@@ -37,6 +37,7 @@ Facebook Login enabled app.
- `TALK_SMTP_PASSWORD` (*required for email*) - password for the SMTP provider you are using.
- `TALK_SMTP_HOST` (*required for email*) - SMTP host url with format `smtp.domain.com`.
- `TALK_SMTP_PORT` (*required for email*) - SMTP port.
- `TALK_INSTALL_LOCK` (_optional for dynamic setup_) - Defaults to `FALSE`. When `TRUE`, disables the dynamic setup endpoint.
Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for
alternative methods of loading configuration during development.
+5
View File
@@ -94,6 +94,11 @@ if (app.get('env') !== 'production') {
endpointURL: '/api/v1/graph/ql'
}));
// GraphQL documention.
app.get('/admin/docs', (req, res) => {
res.render('admin/docs');
});
}
//==============================================================================
+1 -1
View File
@@ -29,5 +29,5 @@
"as": "POSTMARK"
}],
"image": "heroku/nodejs",
"success_url": "/admin/setup"
"success_url": "/admin/install"
}
+11 -15
View File
@@ -4,7 +4,7 @@
* Module dependencies.
*/
const util = require('../util');
// const util = require('./util');
const program = require('./commander');
program
@@ -15,18 +15,14 @@ program
.command('users', 'work with the application auth')
.parse(process.argv);
// If there is no command listed, output help.
if (!process.argv.slice(2).length) {
program.outputHelp();
return;
}
// The ensures that the child process that is created here is always cleaned up
// properly when the parent process dies.
util.onshutdown([
(signal) => {
if ((program.runningCommand.killed === false) && (program.runningCommand.exitCode === null)) {
program.runningCommand.kill(signal);
}
/**
* When this provess exists, check to see if we have a running command, if we do
* check to see if it is still running. If it is, then kill it with a SIGINT
* signal. This is for the use case where we want to kill the process that is
* labled with the PID written out by the parent process.
*/
process.once('exit', () => {
if ((program.runningCommand.killed === false) && (program.runningCommand.exitCode === null)) {
program.runningCommand.kill('SIGINT');
}
]);
});
+1 -1
View File
@@ -10,7 +10,7 @@ const Table = require('cli-table');
const AssetModel = require('../models/asset');
const mongoose = require('../services/mongoose');
const scraper = require('../services/scraper');
const util = require('../util');
const util = require('./util');
// Register the shutdown criteria.
util.onshutdown([
+1 -1
View File
@@ -7,7 +7,7 @@
const program = require('./commander');
const scraper = require('../services/scraper');
const mailer = require('../services/mailer');
const util = require('../util');
const util = require('./util');
const mongoose = require('../services/mongoose');
const kue = require('../services/kue');
+2 -3
View File
@@ -2,13 +2,12 @@
const app = require('../app');
const program = require('./commander');
const debug = require('debug')('talk:server');
const http = require('http');
const scraper = require('../services/scraper');
const mailer = require('../services/mailer');
const kue = require('../services/kue');
const mongoose = require('../services/mongoose');
const util = require('../util');
const util = require('./util');
/**
* Get port from environment and store in Express.
@@ -79,7 +78,7 @@ function onListening() {
let bind = typeof addr === 'string'
? `pipe ${ addr}`
: `port ${ addr.port}`;
debug(`Listening on ${ bind}`);
console.log(`Listening on ${ bind}`);
}
/**
+163 -54
View File
@@ -9,7 +9,10 @@ const inquirer = require('inquirer');
const mongoose = require('../services/mongoose');
const SettingModel = require('../models/setting');
const SettingsService = require('../services/settings');
const util = require('../util');
const SetupService = require('../services/setup');
const UsersService = require('../services/users');
const util = require('./util');
const errors = require('../errors');
// Register the shutdown criteria.
util.onshutdown([
@@ -29,60 +32,166 @@ program
// Setup the application
//==============================================================================
SettingsService
.init()
.then((settings) => {
if (program.defaults) {
return settings.save();
}
const performSetup = () => {
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];
}
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);
});
}
return settings.save();
// Get the current settings, we are expecing an error here.
return SettingsService
.retrieve()
.then(() => {
// We should NOT have gotten a settings object, this means that the
// application is already setup. Error out here.
throw errors.ErrSettingsInit;
})
.catch((err) => {
// If the error is `not init`, then we're good, otherwise, it's something
// else.
if (err !== errors.ErrSettingsNotInit) {
throw err;
}
})
.then(() => {
// Create the base settings model.
let settings = new SettingModel();
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'));
}
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);
});
})
.then(() => {
console.log('Talk is now installed!');
util.shutdown();
})
.catch((err) => {
console.error(err);
util.shutdown(1);
});
};
// Start tthe setup process.
performSetup();
+10 -10
View File
@@ -9,7 +9,7 @@ const inquirer = require('inquirer');
const UsersService = require('../services/users');
const UserModel = require('../models/user');
const mongoose = require('../services/mongoose');
const util = require('../util');
const util = require('./util');
const Table = require('cli-table');
const validateRequired = (msg = 'Field is required', len = 1) => (input) => {
@@ -32,7 +32,7 @@ function getUserCreateAnswers(options) {
email: options.email,
password: options.password,
confirmPassword: options.password,
displayName: options.name,
username: options.name,
roles: []
};
@@ -75,11 +75,11 @@ function getUserCreateAnswers(options) {
}
},
{
name: 'displayName',
message: 'Display Name',
filter: (displayName) => {
name: 'username',
message: 'Username',
filter: (username) => {
return UsersService
.isValidDisplayName(displayName)
.isValidUsername(username)
.catch((err) => {
throw err.message;
});
@@ -108,7 +108,7 @@ function createUser(options) {
})
.then((answers) => {
return UsersService
.createLocalUser(answers.email.trim(), answers.password.trim(), answers.displayName.trim())
.createLocalUser(answers.email.trim(), answers.password.trim(), answers.username.trim())
.then((user) => {
console.log(`Created user ${user.id}.`);
@@ -209,7 +209,7 @@ function updateUser(userID, options) {
'id': userID
}, {
$set: {
displayName: options.name
username: options.name
}
});
@@ -238,7 +238,7 @@ function listUsers() {
let table = new Table({
head: [
'ID',
'Display Name',
'Username',
'Profiles',
'Roles',
'Status',
@@ -249,7 +249,7 @@ function listUsers() {
users.forEach((user) => {
table.push([
user.id,
user.displayName,
user.username,
user.profiles.map((p) => p.provider).join(', '),
user.roles.join(', '),
user.status,
+12 -2
View File
@@ -2,7 +2,6 @@ const pkg = require('../package.json');
const dotenv = require('dotenv');
const fs = require('fs');
const program = require('commander');
const util = require('../util');
// Perform rewrites to the runtime environment variables based on the contents
// of the process.env.REWRITE_ENV if it exists. This is done here as it is the
@@ -27,6 +26,10 @@ const parseArgs = require('minimist')(process.argv.slice(2), {
}
});
/**
* If the config flag is present, then we have to load the configuration from
* the file specified. We will then load those values into the environment.
*/
if (parseArgs.config) {
let envConfig = dotenv.parse(fs.readFileSync(parseArgs.config, {encoding: 'utf8'}));
@@ -35,8 +38,15 @@ if (parseArgs.config) {
});
}
// If the `--pid` flag is used, put the current pid there.
/**
* If the pid flag is present, then we have to create a pid file at the location
* specified.
*/
if (parseArgs.pid) {
const util = require('./util');
console.log('Wrote PID');
util.pid(parseArgs.pid);
}
View File
+24 -5
View File
@@ -4,11 +4,30 @@ machine:
services:
- docker
- redis
environment:
PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
NODE_ENV: "test"
dependencies:
override:
# TODO: use the following to add in support for MongoDB 3.4.
# # Upgrade the database version to 3.4.
# - sudo apt-get purge mongodb-org*
# - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6
# - echo "deb [ arch=amd64 ] http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list
# - sudo apt-get update
# - sudo apt-get install -y mongodb-org
# - sudo service mongod restart
# Install node dependencies.
- yarn
cache_directories:
- ~/.cache/yarn
post:
# Build the static assets.
- yarn build
# Lint the project here, before tests are ran.
- npm run lint
- yarn lint
database:
post:
@@ -20,13 +39,13 @@ database:
test:
override:
# Run the tests using the junit reporter.
- MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml NPM_PACKAGE_CONFIG_MOCHA_REPORTER=mocha-junit-reporter npm run test
# Run the e2e test suite
- E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e npm run e2e
- MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test
# Run the e2e test suite.
# - E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e
deployment:
release:
tag: /[0-9]+(\.[0-9]+)*/
tag: /v[0-9]+(\.[0-9]+)*/
commands:
- bash ./scripts/deploy.sh
+3 -3
View File
@@ -5,19 +5,19 @@ This app handles moderation for Talk (and maybe more later on)
## Installation
$ npm install
$ yarn install
$ cp config.sample.json config.json
Then change `config.json` to adjust it to your project
## Building for production
$ npm run build
$ yarn build
The public folder has everything you need for deployment. You can just copy that folder to your favorite static web server
## Development
$ npm start
$ yarn start
A development server will be running at http://localhost:4132
+34 -12
View File
@@ -1,21 +1,43 @@
import React from 'react';
import {Router, Route, IndexRoute, browserHistory} from 'react-router';
import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-router';
import ModerationContainer from 'containers/ModerationQueue/ModerationContainer';
import CommentStream from 'containers/CommentStream/CommentStream';
import Configure from 'containers/Configure/Configure';
import Streams from 'containers/Streams/Streams';
import CommunityContainer from 'containers/Community/CommunityContainer';
import Configure from 'containers/Configure/Configure';
import LayoutContainer from 'containers/LayoutContainer';
import InstallContainer from 'containers/Install/InstallContainer';
import CommunityContainer from 'containers/Community/CommunityContainer';
import ModerationLayout from 'containers/ModerationQueue/ModerationLayout';
import ModerationContainer from 'containers/ModerationQueue/ModerationContainer';
import Dashboard from 'containers/Dashboard/Dashboard';
const routes = (
<Route path='/admin' component={LayoutContainer}>
<IndexRoute component={ModerationContainer} />
<Route path='embed' component={CommentStream} />
<Route path='community' component={CommunityContainer} />
<Route path='configure' component={Configure} />
<Route path='streams' component={Streams} />
</Route>
<div>
<Route exact path="/admin/install" component={InstallContainer}/>
<Route path='/admin' component={LayoutContainer}>
<IndexRoute component={ModerationContainer} />
<Route path='community' component={CommunityContainer} />
<Route path='configure' component={Configure} />
<Route path='streams' component={Streams} />
<Route path='dashboard' component={Dashboard} />
{/* Moderation Routes */}
<Route path='moderate' component={ModerationLayout}>
<Route path='premod' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
</Route>
<Route path='rejected' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
</Route>
<Route path='flagged' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
</Route>
<Route path=':id' components={ModerationContainer} />
<IndexRedirect to='premod' />
</Route>
</Route>
</div>
);
const AppRouter = () => <Router history={browserHistory} routes={routes} />;
+7 -1
View File
@@ -4,8 +4,10 @@ import {
FETCH_ASSETS_FAILURE,
UPDATE_ASSET_STATE_REQUEST,
UPDATE_ASSET_STATE_SUCCESS,
UPDATE_ASSET_STATE_FAILURE
UPDATE_ASSET_STATE_FAILURE,
UPDATE_ASSETS
} from '../constants/assets';
import coralApi from '../../../coral-framework/helpers/response';
/**
@@ -34,3 +36,7 @@ export const updateAssetState = (id, closedAt) => (dispatch) => {
dispatch({type: UPDATE_ASSET_STATE_SUCCESS}))
.catch(error => dispatch({type: UPDATE_ASSET_STATE_FAILURE, error}));
};
export const updateAssets = assets => dispatch => {
dispatch({type: UPDATE_ASSETS, assets});
};
+2 -2
View File
@@ -9,14 +9,14 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
export const checkLogin = () => dispatch => {
dispatch(checkLoginRequest());
coralApi('/auth')
return coralApi('/auth')
.then(result => {
const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length;
dispatch(checkLoginSuccess(result.user, isAdmin));
})
.catch(error => {
console.error(error);
dispatch(checkLoginFailure(`${error.message}`));
dispatch(checkLoginFailure(`${error.translation_key}`));
});
};
-112
View File
@@ -1,112 +0,0 @@
import coralApi from '../../../coral-framework/helpers/response';
import * as commentTypes from '../constants/comments';
import * as actionTypes from '../constants/actions';
function addUsersCommentsActions (dispatch, {comments, users, actions}) {
dispatch({type: commentTypes.USERS_MODERATION_QUEUE_FETCH_SUCCESS, users});
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS, comments});
dispatch({type: actionTypes.ACTIONS_MODERATION_QUEUE_FETCH_SUCCESS, actions});
}
// Get comments to fill each of the three lists on the mod queue
export const fetchModerationQueueComments = () => {
return dispatch => {
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST});
return Promise.all([
coralApi('/queue/comments/pending'),
coralApi('/queue/users/pending'),
coralApi('/queue/comments/rejected'),
coralApi('/queue/comments/flagged')
])
.then(([pendingComments, pendingUsers, rejected, flagged]) => {
/* Combine seperate calls into a single object */
flagged.comments.forEach(comment => comment.flagged = true);
return {
comments: [...pendingComments.comments, ...rejected.comments, ...flagged.comments],
users: [...pendingComments.users, ...pendingUsers.users, ...rejected.users, ...flagged.users],
actions: [...pendingComments.actions, ...pendingUsers.actions, ...rejected.actions, ...flagged.actions]
};
})
.then(addUsersCommentsActions.bind(this, dispatch));
};
};
export const fetchPendingQueue = () => {
return dispatch => {
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST});
return coralApi('/queue/comments/pending')
.then(addUsersCommentsActions.bind(this, dispatch));
};
};
export const fetchPendingUsersQueue = () => {
return dispatch => {
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST});
return coralApi('/queue/users/pending')
.then(addUsersCommentsActions.bind(this, dispatch));
};
};
export const fetchRejectedQueue = () => {
return dispatch => {
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST});
return coralApi('/queue/comments/rejected')
.then(addUsersCommentsActions.bind(this, dispatch));
};
};
export const fetchFlaggedQueue = () => {
return dispatch => {
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST});
return coralApi('/queue/comments/flagged')
.then(results => {
results.comments.forEach(comment => comment.flagged = true);
return results;
})
.then(addUsersCommentsActions.bind(this, dispatch));
};
};
// Create a new comment
export const createComment = (name, body) => {
return (dispatch) => {
const formData = {body, name};
return coralApi('/comments', {method: 'POST', body: formData})
.then(res => dispatch({type: commentTypes.COMMENT_CREATE_SUCCESS, comment: res}))
.catch(error => dispatch({type: commentTypes.COMMENT_CREATE_FAILED, error}));
};
};
/**
* Action disptacher related to comments
*/
// Update a comment. Now to update a comment we need to send back the whole object
export const updateStatus = (status, comment) => {
return dispatch => {
dispatch({type: commentTypes.COMMENT_STATUS_UPDATE_REQUEST, id: comment.id, status});
return coralApi(`/comments/${comment.id}/status`, {method: 'PUT', body: {status}})
.then(res => dispatch({type: commentTypes.COMMENT_STATUS_UPDATE_SUCCESS, res}))
.catch(error => dispatch({type: commentTypes.COMMENT_STATUS_UPDATE_FAILURE, error}));
};
};
export const flagComment = id => (dispatch, getState) => {
dispatch({type: commentTypes.COMMENT_FLAG, id});
dispatch({type: 'COMMENT_UPDATE', comment: getState().comments.get('byId').get(id)});
};
// Dialog Actions
export const showBanUserDialog = (userId, userName, commentId) => {
return {type: commentTypes.SHOW_BANUSER_DIALOG, userId, userName, commentId};
};
export const hideBanUserDialog = (showDialog) => {
return {type: commentTypes.HIDE_BANUSER_DIALOG, showDialog};
};
+109
View File
@@ -0,0 +1,109 @@
import coralApi from 'coral-framework/helpers/response';
import * as actions from '../constants/install';
import validate from 'coral-framework/helpers/validate';
import errorMsj from 'coral-framework/helpers/error';
export const nextStep = () => ({type: actions.NEXT_STEP});
export const previousStep = () => ({type: actions.PREVIOUS_STEP});
export const goToStep = step => ({type: actions.GO_TO_STEP, step});
const installRequest = () => ({type: actions.INSTALL_REQUEST});
const installSuccess = () => ({type: actions.INSTALL_SUCCESS});
const installFailure = error => ({type: actions.INSTALL_FAILURE, error});
const addError = (name, error) => ({type: actions.ADD_ERROR, name, error});
const hasError = error => ({type: actions.HAS_ERROR, error});
const clearErrors = () => ({type: actions.CLEAR_ERRORS});
const validation = (formData, dispatch, next) => {
if (!(formData != null)) {
return dispatch(hasError());
}
// Required Validation
const empty = Object.keys(formData).filter(name => {
const cond = !formData[name].length;
if (cond) {
// Adding Error
dispatch(addError(name, 'This field is required.'));
} else {
dispatch(addError(name, ''));
}
return cond;
});
if (empty.length) {
return dispatch(hasError());
}
// RegExp Validation
const validation = Object.keys(formData).filter(name => {
const cond = !validate[name](formData[name]);
if (cond) {
// Adding Error
dispatch(addError(name, errorMsj[name]));
} else {
dispatch(addError(name, ''));
}
return cond;
});
if (validation.length) {
return dispatch(hasError());
}
dispatch(clearErrors());
next();
};
export const submitSettings = () => (dispatch, getState) => {
const settingsFormData = getState().install.toJS().data.settings;
validation(settingsFormData, dispatch, function() {
dispatch(nextStep());
});
};
export const submitUser = () => (dispatch, getState) => {
const userFormData = getState().install.toJS().data.user;
validation(userFormData, dispatch, function() {
const data = getState().install.toJS().data;
dispatch(installRequest());
coralApi('/setup', {method: 'POST', body: data})
.then(result => {
console.log(result);
dispatch(installSuccess());
dispatch(nextStep());
})
.catch(error => {
console.error(error);
dispatch(installFailure(`${error.translation_key}`));
});
});
};
export const updateSettingsFormData = (name, value) => ({type: actions.UPDATE_FORMDATA_SETTINGS, name, value});
export const updateUserFormData = (name, value) => ({type: actions.UPDATE_FORMDATA_USER, name, value});
const checkInstallRequest = () => ({type: actions.CHECK_INSTALL_REQUEST});
const checkInstallSuccess = installed => ({type: actions.CHECK_INSTALL_SUCCESS, installed});
const checkInstallFailure = error => ({type: actions.CHECK_INSTALL_FAILURE, error});
export const checkInstall = next => dispatch => {
dispatch(checkInstallRequest());
coralApi('/setup')
.then(({installed}) => {
dispatch(checkInstallSuccess(installed));
if (installed) {
next();
}
})
.catch(error => {
console.error(error);
dispatch(checkInstallFailure(`${error.translation_key}`));
});
};
@@ -0,0 +1,8 @@
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) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId});
export const hideBanUserDialog = (showDialog) => ({type: actions.HIDE_BANUSER_DIALOG, showDialog});
+6 -1
View File
@@ -11,6 +11,7 @@ export const SAVE_SETTINGS_SUCCESS = 'SAVE_SETTINGS_SUCCESS';
export const SAVE_SETTINGS_FAILED = 'SAVE_SETTINGS_FAILED';
export const WORDLIST_UPDATED = 'WORDLIST_UPDATED';
export const DOMAINLIST_UPDATED = 'DOMAINLIST_UPDATED';
export const fetchSettings = () => dispatch => {
dispatch({type: SETTINGS_LOADING});
@@ -33,8 +34,12 @@ export const updateWordlist = (listName, list) => {
return {type: WORDLIST_UPDATED, listName, list};
};
export const updateDomainlist = (listName, list) => {
return {type: DOMAINLIST_UPDATED, listName, list};
};
export const saveSettingsToServer = () => (dispatch, getState) => {
let settings = getState().settings.toJS().settings;
let settings = getState().settings.toJS();
if (settings.charCount) {
settings.charCount = parseInt(settings.charCount);
}
+8
View File
@@ -21,3 +21,11 @@ export const sendNotificationEmail = (userId, subject, body) => {
.catch(error => dispatch({type: userTypes.USER_EMAIL_FAILURE, error}));
};
};
// let a user edit their username
export const enableUsernameEdit = (userId) => {
return (dispatch) => {
return coralApi(`/users/${userId}/username-enable`, {method: 'POST'})
.catch(error => dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error}));
};
};
@@ -1,48 +1,22 @@
import React from 'react';
import styles from './ModerationList.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
import {FabButton, Button, Icon} from 'coral-ui';
import BanUserButton from './BanUserButton';
import {FabButton} from 'coral-ui';
import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap';
const ActionButton = ({option, type, comment = {}, user, menuOptionsMap, onClickAction, onClickShowBanDialog}) =>
{
const banned = user.status === 'BANNED';
const ActionButton = ({type = '', user, ...props}) => {
if (type === 'BAN') {
return <BanUserButton user={user} onClick={() => props.showBanUserDialog(props.user, props.id)} />;
}
if (option === 'flag' && (type === 'USERS' || comment.status || comment.flagged === true)) {
return null;
}
if (option === 'ban') {
return (
<div className={styles.ban}>
<Button
className={`ban ${styles.banButton}`}
cStyle='darkGrey'
disabled={banned ? 'disabled' : ''}
onClick={() => onClickShowBanDialog(user.id, user.displayName, comment.id)
}
raised
>
<Icon name='not_interested' className={styles.banIcon} />
{lang.t('comment.ban_user')}
</Button>
</div>
);
}
const menuOption = menuOptionsMap[option];
const action = {
item_type: type,
item_id: type === 'COMMENTS' ? comment.id : user.id
};
return (
<FabButton
className={`${option} ${styles.actionButton}`}
cStyle={option}
icon={menuOption.icon}
onClick={() => onClickAction(menuOption.status, type === 'COMMENTS' ? comment : user, action)}
className={`${type.toLowerCase()} ${styles.actionButton}`}
cStyle={type.toLowerCase()}
icon={menuActionsMap[type].icon}
onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment}
/>
);
};
export default ActionButton;
const lang = new I18n(translations);
@@ -0,0 +1,10 @@
.banButton {
width: 114px;
letter-spacing: 1px;
i {
vertical-align: middle;
margin-right: 10px;
font-size: 14px;
}
}
@@ -0,0 +1,26 @@
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='darkGrey'
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;
@@ -22,17 +22,17 @@
}
}
.formField {
.textField {
margin-top: 15px;
}
.formField label {
.textField label {
font-size: 1.08em;
font-weight: bold;
margin-bottom: 5px;
}
.formField input {
.textField input {
width: 100%;
display: block;
border: none;
@@ -1,4 +1,4 @@
import React from 'react';
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
@@ -8,34 +8,28 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
const BanUserDialog = ({open, handleClose, onClickBanUser, user = {}}) => (
const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
open={open}
onClose={() => handleClose()}
onCancel={() => handleClose()}
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>
<h2>{lang.t('bandialog.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>
{lang.t('bandialog.are_you_sure', user.userName)}
</h3>
<i>
{lang.t('bandialog.note')}
</i>
<h3>{lang.t('bandialog.are_you_sure', user.name)}</h3>
<i>{lang.t('bandialog.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={() => handleClose()} raised>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{lang.t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={() => onClickBanUser('BANNED', user.userId, user.commentId)} raised>
<Button cStyle="black" className={styles.ban} onClick={() => handleBanUser({userId: user.id})} raised>
{lang.t('bandialog.yes_ban_user')}
</Button>
</div>
@@ -43,4 +37,10 @@ const BanUserDialog = ({open, handleClose, onClickBanUser, user = {}}) => (
</Dialog>
);
BanUserDialog.propTypes = {
handleBanUser: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
};
export default BanUserDialog;
+1 -1
View File
@@ -23,7 +23,7 @@ const Comment = props => {
<li tabIndex={props.index} className={`mdl-card mdl-shadow--2dp ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span>{author.displayName || lang.t('comment.anon')}</span>
<span>{author.username || lang.t('comment.anon')}</span>
<span className={styles.created}>{timeago().format(comment.createdAt || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
{comment.flagged ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
</div>
@@ -0,0 +1,14 @@
import React, {PropTypes} from 'react';
import {Card} from 'coral-ui';
const EmptyCard = props => (
<Card style={{textAlign: 'center', maxWidth: 400, margin: '0 auto'}}>
{props.children}
</Card>
);
EmptyCard.propTypes = {
children: PropTypes.node.isRequired
};
export default EmptyCard;
@@ -0,0 +1,34 @@
.heading {
margin: 0;
font-size: 1.5rem;
font-weight: bold;
}
.widgetTable {
width: 100%;
border-collapse: collapse;
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.2);
}
.widgetTable thead th {
border-bottom: 1px solid #f47e6b;
padding: 10px;
text-align: left;
}
.widgetTable tbody tr {
border-bottom: 1px solid lightgrey;
}
.widgetTable tbody tr:last-child {
border-bottom: none;
}
.widgetTable tbody td {
padding: 10px;
}
.lede {
font-size: 0.9em;
color: grey;
}
@@ -0,0 +1,66 @@
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './FlagWidget.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
const lang = new I18n(translations);
const FlagWidget = ({assets}) => {
return (
<table className={styles.widgetTable}>
<thead className={styles.widgetHead}>
<tr>
<th></th>{/* empty on purpose */}
<th>{lang.t('streams.article')}</th>
<th>{lang.t('modqueue.flagged')}</th>
<th>{lang.t('modqueue.likes')}</th>
<th>{lang.t('dashboard.comment_count')}</th>
</tr>
</thead>
<tbody>
{
assets.length
? assets.map((asset, index) => {
const flagSummary = asset.action_summaries.find(s => s.__typename === 'FlagAssetActionSummary');
const likeSummary = asset.action_summaries.find(s => s.__typename === 'LikeAssetActionSummary');
return (
<tr key={asset.id}>
<td>{index + 1}.</td>
<td>
<Link to={`/admin/moderate/flagged/${asset.id}`}>{asset.title}</Link>
<p className={styles.lede}>{asset.author} - Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</td>
<td>{flagSummary ? flagSummary.actionCount : 0}</td>
<td>{likeSummary ? likeSummary.actionCount : 0}</td>
<td>{asset.commentCount}</td>
</tr>
);
})
: <tr><td colSpan="3">{lang.t('dashboard.no_flags')}</td></tr>
}
</tbody>
</table>
);
};
FlagWidget.propTypes = {
assets: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
title: PropTypes.string,
url: PropTypes.string,
commentCount: PropTypes.number,
action_summaries: PropTypes.arrayOf(
PropTypes.shape({
__typename: PropTypes.string.isRequired,
actionCount: PropTypes.number.isRequired,
actionableItemCount: PropTypes.number.isRequired
})
)
})
).isRequired
};
export default FlagWidget;
@@ -96,11 +96,6 @@
margin-left: 40px;
}
.actionButton {
transform: scale(.8);
margin: 0;
}
.body {
margin-top: 20px;
flex: 1;
@@ -182,3 +177,9 @@
font-size: 14px;
}
}
.actionButton {
transform: scale(.8);
margin: 0;
}
@@ -3,7 +3,7 @@ import styles from './ModerationList.css';
import key from 'keymaster';
import Hammer from 'hammerjs';
import Comment from './Comment';
import UserAction from './UserAction';
import User from './User';
import SuspendUserModal from './SuspendUserModal';
// Each action has different meaning and configuration
@@ -138,7 +138,7 @@ export default class ModerationList extends React.Component {
if (menuOption === 'REJECTED') {
this.setState({suspendUserModal: action});
} else if (menuOption === 'ACCEPTED') {
this.props.userStatusUpdate('ACTIVE', action.item_id);
this.props.userStatusUpdate('APPROVED', action.item_id);
}
}
}
@@ -177,7 +177,7 @@ export default class ModerationList extends React.Component {
// If the item is an action...
const user = users[item.item_id];
modItem = user && <UserAction
modItem = user && <User
suspectWords={suspectWords}
action={item}
user={user}
@@ -36,7 +36,7 @@ class SuspendUserModal extends Component {
}
componentDidMount() {
const about = this.props.actionType === 'flag_bio' ? lang.t('suspenduser.bio') : lang.t('suspenduser.username');
const about = lang.t('suspenduser.username');
this.setState({email: lang.t('suspenduser.email', about)});
}
@@ -1,22 +1,16 @@
import React from 'react';
import Linkify from 'react-linkify';
import styles from './ModerationList.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
import {Icon} from 'react-mdl';
import Highlighter from 'react-highlight-words';
import ActionButton from './ActionButton';
const linkify = new Linkify();
// Render a single comment for the list
const UserAction = props => {
const User = props => {
const {action, user} = props;
let userStatus = user.status;
const links = user.settings.bio ? linkify.getMatches(user.settings.bio) : [];
// Do not display unless the user status is 'pending' or 'banned'.
// This means that they have already been reviewed and approved.
@@ -24,18 +18,15 @@ const UserAction = props => {
<li tabIndex={props.index} className={`mdl-card mdl-shadow--2dp ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span>{user.displayName}</span>
<span>{user.username}</span>
</div>
<div className={styles.sideActions}>
{links ?
<span className={styles.hasLinks}><Icon name='error_outline'/> Contains Link</span> : null}
<div className={`actions ${styles.actions}`}>
{props.modActions.map(
(action, i) =>
<ActionButton
option={action}
type={action.toUpperCase()}
key={i}
type='USERS'
user={user}
menuOptionsMap={props.menuOptionsMap}
onClickAction={props.onClickAction}
@@ -48,32 +39,12 @@ const UserAction = props => {
<span className={styles.banned}><Icon name='error_outline'/> {lang.t('comment.banned_user')}</span> : null}
</div>
</div>
{
user.settings.bio &&
<div>
<div className={styles.itemBody}>
<div>{lang.t('user.user_bio')}:</div>
<span className={styles.body}>
<Linkify component='span' properties={{style: linkStyles}}>
<Highlighter
searchWords={props.suspectWords}
textToHighlight={user.settings.bio} />
</Linkify>
</span>
</div>
</div>
}
<div className={styles.flagCount}>
{`${action.count} ${action.action_type === 'flag_bio' ? lang.t('user.bio_flags') : lang.t('user.username_flags')}`}
</div>
</li>;
};
export default UserAction;
const linkStyles = {
backgroundColor: 'rgb(255, 219, 135)',
padding: '1px 2px'
};
export default User;
const lang = new I18n(translations);
@@ -16,6 +16,7 @@
.rightPanel {
position: absolute;
top: 0;
right: 0;
width: 170px;
height: 100%;
+53 -26
View File
@@ -6,34 +6,61 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import {Logo} from './Logo';
export default ({handleLogout}) => (
export default ({handleLogout, restricted = false}) => (
<Header className={styles.header}>
<Logo />
<Navigation className={styles.nav}>
<IndexLink className={styles.navLink} to="/admin"
activeClassName={styles.active}>{lang.t('configure.moderate')}</IndexLink>
<Link className={styles.navLink} to="/admin/community"
activeClassName={styles.active}>{lang.t('configure.community')}</Link>
<Link className={styles.navLink} to="/admin/configure"
activeClassName={styles.active}>{lang.t('configure.configure')}</Link>
<Link className={styles.navLink} to="/admin/streams"
activeClassName={styles.active}>{lang.t('configure.streams')}</Link>
</Navigation>
<div className={styles.rightPanel}>
<ul>
<li className={styles.settings}>
<div>
<IconButton name="settings" id="menu-settings"/>
<Menu target="menu-settings" align="right">
<MenuItem onClick={handleLogout}>Sign Out</MenuItem>
</Menu>
</div>
</li>
<li>
{`v${process.env.VERSION}`}
</li>
</ul>
</div>
{
!restricted ?
<div>
<Navigation className={styles.nav}>
<IndexLink
className={styles.navLink}
to="/admin/dashboard"
activeClassName={styles.active}>
{lang.t('configure.dashboard')}
</IndexLink>
<Link
className={styles.navLink}
to="/admin/moderate"
activeClassName={styles.active}>
{lang.t('configure.moderate')}
</Link>
<Link className={styles.navLink}
to="/admin/streams"
activeClassName={styles.active}>
{lang.t('configure.streams')}
</Link>
<Link className={styles.navLink}
to="/admin/community"
activeClassName={styles.active}>
{lang.t('configure.community')}
</Link>
<Link
className={styles.navLink}
to="/admin/configure"
activeClassName={styles.active}>
{lang.t('configure.configure')}
</Link>
</Navigation>
<div className={styles.rightPanel}>
<ul>
<li className={styles.settings}>
<div>
<IconButton name="settings" id="menu-settings"/>
<Menu target="menu-settings" align="right">
<MenuItem onClick={handleLogout}>Sign Out</MenuItem>
</Menu>
</div>
</li>
<li>
{`v${process.env.VERSION}`}
</li>
</ul>
</div>
</div>
:
null
}
</Header>
);
@@ -1,6 +1,9 @@
export const FETCH_ASSETS_REQUEST = 'FETCH_ASSETS_REQUEST';
export const FETCH_ASSETS_SUCCESS = 'FETCH_ASSETS_SUCCESS';
export const FETCH_ASSETS_FAILURE = 'FETCH_ASSETS_FAILURE';
export const UPDATE_ASSET_STATE_REQUEST = 'UPDATE_ASSET_STATE_REQUEST';
export const UPDATE_ASSET_STATE_SUCCESS = 'UPDATE_ASSET_STATE_SUCCESS';
export const UPDATE_ASSET_STATE_FAILURE = 'UPDATE_ASSET_STATE_FAILURE';
export const UPDATE_ASSETS = 'UPDATE_ASSETS';
@@ -0,0 +1,16 @@
export const NEXT_STEP = 'NEXT_STEP';
export const GO_TO_STEP = 'GO_TO_STEP';
export const PREVIOUS_STEP = 'PREVIOUS_STEP';
export const ADD_ERROR = 'ADD_ERROR';
export const HAS_ERROR = 'HAS_ERROR';
export const SHOW_ERRORS = 'SHOW_ERRORS';
export const CLEAR_ERRORS = 'CLEAR_ERRORS';
export const INSTALL_REQUEST = 'INSTALL_REQUEST';
export const INSTALL_SUCCESS = 'INSTALL_SUCCESS';
export const INSTALL_FAILURE = 'INSTALL_FAILURE';
export const UPDATE_FORMDATA_USER = 'UPDATE_FORMDATA_USER';
export const UPDATE_FORMDATA_SETTINGS = 'UPDATE_FORMDATA_SETTINGS';
export const CHECK_INSTALL_REQUEST = 'CHECK_INSTALL_REQUEST';
export const CHECK_INSTALL_SUCCESS = 'CHECK_INSTALL_SUCCESS';
export const CHECK_INSTALL_FAILURE = 'CHECK_INSTALL_FAILURE';
@@ -0,0 +1,4 @@
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';
@@ -2,4 +2,5 @@ export const UPDATE_STATUS_REQUEST = 'UPDATE_STATUS_REQUEST';
export const UPDATE_STATUS_SUCCESS = 'UPDATE_STATUS_SUCCESS';
export const UPDATE_STATUS_FAILURE = 'UPDATE_STATUS_FAILURE';
export const USER_EMAIL_FAILURE = 'USER_EMAIL_FAILURE';
export const USERNAME_ENABLE_FAILURE = 'USERNAME_ENABLE_FAILURE';
export const USERS_MODERATION_QUEUE_FETCH_SUCCESS = 'USERS_MODERATION_QUEUE_FETCH_SUCCESS';
@@ -1,13 +0,0 @@
@custom-media --big-viewport (min-width: 780px);
.container {
max-width: 860px;
margin: 0 auto;
}
@media (--big-viewport) {
.tab {
flex: none;
}
}
@@ -1,66 +0,0 @@
import React from 'react';
import styles from './CommentStream.css';
import {Snackbar} from 'react-mdl';
import {connect} from 'react-redux';
import {createComment, flagComment} from 'actions/comments';
import ModerationList from 'components/ModerationList';
import CommentBox from 'components/CommentBox';
/**
* Renders a comment stream using a ModerationList component
* and adds a box for adding a new comment
*/
class CommentStream extends React.Component {
constructor (props) {
super(props);
this.state = {snackbar: false, snackbarMsg: ''};
this.onSubmit = this.onSubmit.bind(this);
this.onClickAction = this.onClickAction.bind(this);
}
// Fetch the comments before mounting
componentWillMount () {
this.props.dispatch({type: 'COMMENT_STREAM_FETCH'});
}
// Submit the new comment
onSubmit (comment) {
this.props.dispatch(createComment(comment.name, comment.body));
}
// The only action for now is flagging
onClickAction (action, id) {
if (action === 'flag') {
this.props.dispatch(flagComment(id));
clearTimeout(this._snackTimeout);
this.setState({snackbar: true, snackbarMsg: 'Thank you for reporting this comment. Our moderation team has been notified and will review it shortly.'});
this._snackTimeout = setTimeout(() => this.setState({snackbar: false}), 30000);
}
}
// Render the comment box along with the ModerationList
render ({comments, users}, {snackbar, snackbarMsg}) {
return (
<div className={styles.container}>
<CommentBox onSubmit={this.onSubmit} />
<ModerationList isActive hideActive
singleView={false}
commentIds={comments.ids}
comments={comments.byId}
users={users.byId}
onClickAction={this.onClickAction}
actions={['flag']}
loading={comments.loading} />
<Snackbar active={snackbar}>{snackbarMsg}</Snackbar>
</div>
);
}
}
const mapStateToProps = state => ({
comments: state.comments.toJS(),
users: state.users.toJS()
});
export default connect(mapStateToProps)(CommentStream);
@@ -1,19 +1,19 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import translations from 'coral-admin/src/translations.json';
import styles from './Community.css';
import Table from './Table';
import Loading from './Loading';
import NoResults from './NoResults';
import {Pager} from 'coral-ui';
import EmptyCard from '../../components/EmptyCard';
const lang = new I18n(translations);
const tableHeaders = [
{
title: lang.t('community.username_and_email'),
field: 'displayName'
field: 'username'
},
{
title: lang.t('community.account_creation_date'),
@@ -54,13 +54,14 @@ const Community = ({isFetching, commenters, ...props}) => {
</div>
<div className={styles.mainContent}>
{ isFetching && <Loading /> }
{ !hasResults && <NoResults /> }
{ hasResults &&
<Table
headers={tableHeaders}
data={commenters}
onHeaderClickHandler={props.onHeaderClickHandler}
/>
{
hasResults
? <Table
headers={tableHeaders}
data={commenters}
onHeaderClickHandler={props.onHeaderClickHandler}
/>
: <EmptyCard>{lang.t('community.no-results')}</EmptyCard>
}
<Pager
totalPages={props.totalPages}
@@ -1,9 +0,0 @@
import React from 'react';
const NoResults = () => (
<div>
No users found with that user name or email address
</div>
);
export default NoResults;
@@ -44,7 +44,7 @@ class Table extends Component {
{commenters.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.displayName}
{row.username}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
@@ -32,6 +32,10 @@ const updateModeration = (updateSettings, mod) => () => {
updateSettings({moderation});
};
const updateEmailConfirmation = (updateSettings, verify) => () => {
updateSettings({requireEmailConfirmation: !verify});
};
const updateInfoBoxEnable = (updateSettings, infoBox) => () => {
const infoBoxEnable = !infoBox;
updateSettings({infoBoxEnable});
@@ -47,6 +51,12 @@ const updateClosedMessage = (updateSettings) => (event) => {
updateSettings({closedMessage});
};
const updateCustomCssUrl = (updateSettings) => (event) => {
console.log('updateCustomCssUrl', event.target.value);
const customCssUrl = event.target.value;
updateSettings({customCssUrl});
};
// If we are changing the measure we need to recalculate using the old amount
// Same thing if we are just changing the amount
const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => {
@@ -67,106 +77,135 @@ const CommentSettings = ({fetchingSettings, title, updateSettings, settingsError
return <Card shadow="4"><Spinner/>Loading settings...</Card>;
}
// just putting this here for shorthand below
const on = styles.enabledSetting;
const off = styles.disabledSetting;
return (
<div className={styles.commentSettingsSection}>
<h3>{title}</h3>
<Card className={`${styles.configSetting} ${settings.moderation === 'PRE' ? styles.enabledSetting : styles.disabledSetting}`}>
<div className={styles.action}>
<Checkbox
onChange={updateModeration(updateSettings, settings.moderation)}
checked={settings.moderation === 'PRE'} />
</div>
<div className={styles.content}>
<Card className={`${styles.configSetting} ${settings.moderation === 'PRE' ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateModeration(updateSettings, settings.moderation)}
checked={settings.moderation === 'PRE'} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.enable-pre-moderation')}</div>
<p className={settings.moderation === 'PRE' ? '' : styles.disabledSettingText}>
{lang.t('configure.enable-pre-moderation-text')}
</p>
</div>
</Card>
<Card className={`${styles.configSetting} ${settings.charCountEnable ? styles.enabledSetting : styles.disabledSetting}`}>
<div className={styles.action}>
<Checkbox
onChange={updateCharCountEnable(updateSettings, settings.charCountEnable)}
checked={settings.charCountEnable} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.comment-count-header')}</div>
<p className={settings.charCountEnable ? '' : styles.disabledSettingText}>
<span>{lang.t('configure.comment-count-text-pre')}</span>
<input type='text'
className={`${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
htmlFor='charCount'
onChange={updateCharCount(updateSettings, settingsError)}
value={settings.charCount}/>
<span>{lang.t('configure.comment-count-text-post')}</span>
{
errors.charCount &&
<span className={styles.settingsError}>
<br/>
<Icon name="error_outline"/>
{lang.t('configure.comment-count-error')}
</span>
}
</p>
</div>
</Card>
<Card className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? styles.enabledSetting : styles.disabledSetting}`}>
<div className={styles.action}>
<Checkbox
onChange={updateInfoBoxEnable(updateSettings, settings.infoBoxEnable)}
checked={settings.infoBoxEnable} />
</div>
<div className={styles.content}>
{lang.t('configure.include-comment-stream')}
<p>
{lang.t('configure.include-comment-stream-desc')}
</p>
<div className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? null : styles.hidden}`} >
<div className={styles.content}>
<Textfield
onChange={updateInfoBoxContent(updateSettings)}
value={settings.infoBoxContent}
label={lang.t('configure.include-text')}
rows={3}/>
</div>
</Card>
<Card className={`${styles.configSetting} ${settings.requireEmailConfirmation ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateEmailConfirmation(updateSettings, settings.requireEmailConfirmation)}
checked={settings.requireEmailConfirmation} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.require-email-verification')}</div>
<p className={settings.requireEmailConfirmation ? '' : styles.disabledSettingText}>
{lang.t('configure.require-email-verification-text')}
</p>
</div>
</Card>
<Card className={`${styles.configSetting} ${settings.charCountEnable ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateCharCountEnable(updateSettings, settings.charCountEnable)}
checked={settings.charCountEnable} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.comment-count-header')}</div>
<p className={settings.charCountEnable ? '' : styles.disabledSettingText}>
<span>{lang.t('configure.comment-count-text-pre')}</span>
<input type='text'
className={`${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
htmlFor='charCount'
onChange={updateCharCount(updateSettings, settingsError)}
value={settings.charCount}/>
<span>{lang.t('configure.comment-count-text-post')}</span>
{
errors.charCount &&
<span className={styles.settingsError}>
<br/>
<Icon name="error_outline"/>
{lang.t('configure.comment-count-error')}
</span>
}
</p>
</div>
</Card>
<Card className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateInfoBoxEnable(updateSettings, settings.infoBoxEnable)}
checked={settings.infoBoxEnable} />
</div>
<div className={styles.content}>
{lang.t('configure.include-comment-stream')}
<p>
{lang.t('configure.include-comment-stream-desc')}
</p>
<div className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? null : styles.hidden}`} >
<div className={styles.content}>
<Textfield
onChange={updateInfoBoxContent(updateSettings)}
value={settings.infoBoxContent}
label={lang.t('configure.include-text')}
rows={3}/>
</div>
</div>
</Card>
<Card className={styles.configSettingInfoBox}>
<div className={styles.content}>
{lang.t('configure.closed-comments-desc')}
<div>
<Textfield
onChange={updateClosedMessage(updateSettings)}
value={settings.closedMessage}
label={lang.t('configure.closed-comments-label')}
rows={3}/>
</div>
</div>
</Card>
<Card className={styles.configSettingInfoBox}>
<div className={styles.content}>
{lang.t('configure.closed-comments-desc')}
<div>
<Textfield
onChange={updateClosedMessage(updateSettings)}
value={settings.closedMessage}
label={lang.t('configure.closed-comments-label')}
rows={3}/>
</div>
</Card>
<Card className={`${styles.configSettingInfoBox}`}>
<div className={styles.content}>
{lang.t('configure.close-after')}
<br />
<Textfield
type='number'
pattern='[0-9]+'
style={{width: 50}}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout)}
value={getTimeoutAmount(settings.closedTimeout)}
label={lang.t('configure.closed-comments-label')} />
<div className={styles.configTimeoutSelect}>
<SelectField
label="comments closed time window"
value={getTimeoutMeasure(settings.closedTimeout)}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout, true)}>
<Option value={'hours'}>{lang.t('configure.hours')}</Option>
<Option value={'days'}>{lang.t('configure.days')}</Option>
<Option value={'weeks'}>{lang.t('configure.weeks')}</Option>
</SelectField>
</div>
</div>
</Card>
<Card className={styles.configSettingInfoBox}>
<div className={styles.content}>
{lang.t('configure.close-after')}
<br />
<Textfield
type='number'
pattern='[0-9]+'
style={{width: 50}}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout)}
value={getTimeoutAmount(settings.closedTimeout)}
label={lang.t('configure.closed-comments-label')} />
<div className={styles.configTimeoutSelect}>
<SelectField
label="comments closed time window"
value={getTimeoutMeasure(settings.closedTimeout)}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout, true)}>
<Option value={'hours'}>{lang.t('configure.hours')}</Option>
<Option value={'days'}>{lang.t('configure.days')}</Option>
<Option value={'weeks'}>{lang.t('configure.weeks')}</Option>
</SelectField>
</div>
</Card>
</div>
</Card>
<Card className={styles.configSettingInfoBox}>
<div className={styles.content}>
{lang.t('configure.custom-css-url')}
<p>{lang.t('configure.custom-css-url-desc')}</p>
<br />
<Textfield
style={{width: '100%'}}
label={lang.t('configure.custom-css-url')}
value={settings.customCssUrl}
onChange={updateCustomCssUrl(updateSettings)} />
</div>
</Card>
</div>
);
};
@@ -5,6 +5,7 @@ import {
updateSettings,
saveSettingsToServer,
updateWordlist,
updateDomainlist
} from '../../actions/settings';
import {Button, List, Item} from 'coral-ui';
@@ -14,6 +15,7 @@ import translations from '../../translations.json';
import EmbedLink from './EmbedLink';
import CommentSettings from './CommentSettings';
import Wordlist from './Wordlist';
import Domainlist from './Domainlist';
import has from 'lodash/has';
class Configure extends Component {
@@ -47,6 +49,11 @@ class Configure extends Component {
this.props.dispatch(updateWordlist(listName, list));
}
onChangeDomainlist = (listName, list) => {
this.setState({changed: true});
this.props.dispatch(updateDomainlist(listName, list));
}
onSettingUpdate = (setting) => {
this.setState({changed: true});
this.props.dispatch(updateSettings(setting));
@@ -73,7 +80,14 @@ class Configure extends Component {
errors={this.state.errors}
settingsError={this.onSettingError}/>;
case 'embed':
return <EmbedLink title={pageTitle} />;
return has(this, 'props.settings.domains.whitelist')
? <div>
<Domainlist
domains={this.props.settings.domains.whitelist}
onChangeDomainlist={this.onChangeDomainlist}/>
<EmbedLink title={pageTitle} />
</div>
: <EmbedLink title={pageTitle} />;
case 'wordlist':
return has(this, 'props.settings.wordlist')
? <Wordlist
@@ -151,7 +165,9 @@ class Configure extends Component {
}
}
const mapStateToProps = state => state.settings.toJS();
const mapStateToProps = state => ({
settings: state.settings.toJS()
});
export default connect(mapStateToProps)(Configure);
const lang = new I18n(translations);
@@ -0,0 +1,26 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import TagsInput from 'react-tagsinput';
import styles from './Configure.css';
import {Card} from 'coral-ui';
const Domainlist = ({domains, onChangeDomainlist}) => (
<div>
<h3>{lang.t('configure.domain-list-title')}</h3>
<Card id={styles.domainlist}>
<p className={styles.domainlistDesc}>{lang.t('configure.domain-list-text')}</p>
<TagsInput
value={domains}
inputProps={{placeholder: 'URL'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => onChangeDomainlist('whitelist', tags)}
/>
</Card>
</div>
);
export default Domainlist;
const lang = new I18n(translations);
@@ -25,8 +25,24 @@ class EmbedLink extends Component {
}
render () {
const embedText = `<div id='coralStreamEmbed'></div><script type='text/javascript' src='${window.location.protocol}//pym.nprapps.org/pym.v1.min.js'></script><script>var pymParent = new pym.Parent('coralStreamEmbed', '${window.location.protocol}//${window.location.host}/embed/stream', {title: 'Comments'});</script>`;
const location = window.location;
const talkBaseUrl = [
location.protocol,
'//',
location.hostname,
location.port ? (`:${ window.location.port}`) : ''
].join('');
const coralJsUrl = [talkBaseUrl, '/embed.js'].join('');
const nonce = String(Math.random()).slice(2);
const streamElementId = `coral_talk_${nonce}`;
const embedText = `
<div id="${streamElementId}"></div>
<script src="${coralJsUrl}" async onload="
Coral.Talk.render(document.getElementById('${streamElementId}'), {
talk: '${talkBaseUrl}'
});
"></script>
`.trim();
return (
<div>
<h3>{this.props.title}</h3>
@@ -0,0 +1,23 @@
.Dashboard {
display: flex;
padding: 5px;
}
.widget {
margin-top: 10px;
flex: 1;
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.2);
margin-right: 10px;
padding: 15px;
}
.widget:last-child {
margin-right: 0;
}
.heading {
margin: 0;
font-size: 1.5rem;
font-weight: bold;
}
@@ -0,0 +1,38 @@
import React from 'react';
import {compose} from 'react-apollo';
import {mostFlags} from 'coral-admin/src/graphql/queries';
import {Spinner} from 'coral-ui';
import styles from './Dashboard.css';
import FlagWidget from '../../components/FlagWidget';
class Dashboard extends React.Component {
render () {
const {data} = this.props;
const {metrics: assets} = data;
if (data.loading) {
return <Spinner />;
}
if (data.error) {
return <code><pre>{data.error}</pre></code>;
}
return (
<div className={styles.Dashboard}>
<div className={styles.widget}>
<h2 className={styles.heading}>Top Ten Articles with the most flagged comments</h2>
<FlagWidget assets={assets} />
</div>
<div className={styles.widget}>
<h2 className={styles.heading}>Top ten comments with the most likes</h2>
</div>
</div>
);
}
}
export default compose(
mostFlags
)(Dashboard);
@@ -0,0 +1,93 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import styles from './style.css';
import {Wizard, WizardNav} from 'coral-ui';
import {Layout} from '../../components/ui/Layout';
import {
nextStep,
previousStep,
goToStep,
updateUserFormData,
updateSettingsFormData,
submitSettings,
submitUser,
checkInstall
} from '../../actions/install';
import InitialStep from './components/Steps/InitialStep';
import AddOrganizationName from './components/Steps/AddOrganizationName';
import CreateYourAccount from './components/Steps/CreateYourAccount';
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/>
<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 => ({
checkInstall: next => dispatch(checkInstall(next)),
nextStep: () => dispatch(nextStep()),
previousStep: () => dispatch(previousStep()),
goToStep: step => dispatch(goToStep(step)),
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);
@@ -0,0 +1,30 @@
import React from 'react';
import styles from './style.css';
import {TextField, Button} from 'coral-ui';
const AddOrganizationName = props => {
const {handleSettingsChange, handleSettingsSubmit, install} = props;
return (
<div className={styles.step}>
<p>
Please tell us the name of your organization. This will appear in emails when
inviting new team members
</p>
<div className={styles.form}>
<form onSubmit={handleSettingsSubmit}>
<TextField
className={styles.TextField}
id="organizationName"
type="text"
label='Organization name'
onChange={handleSettingsChange}
showErrors={install.showErrors}
errorMsg={install.errors.organizationName} />
<Button type="submit" cStyle='black' full>Save</Button>
</form>
</div>
</div>
);
};
export default AddOrganizationName;
@@ -0,0 +1,65 @@
import React from 'react';
import styles from './style.css';
import {TextField, Button, Spinner} from 'coral-ui';
const InitialStep = props => {
const {handleUserChange, handleUserSubmit, install} = props;
return (
<div className={styles.step}>
<div className={styles.form}>
<form onSubmit={handleUserSubmit}>
<TextField
className={styles.textField}
id="email"
type="email"
label='Email address'
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.email}
noValidate
/>
<TextField
className={styles.textField}
id="username"
type="text"
label='Username'
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.username}
/>
<TextField
className={styles.textField}
id="password"
type="password"
label='Password'
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.password}
/>
<TextField
className={styles.textField}
id="confirmPassword"
type="password"
label='Confirm Password'
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.confirmPassword}
/>
{
!props.install.isLoading ?
<Button cStyle='black' type="submit" full>Save</Button>
:
<Spinner />
}
{props.install.installRequest === 'FAILURE' && <div className={styles.error}>Error: {props.install.installRequestError}</div>}
</form>
</div>
</div>
);
};
export default InitialStep;
@@ -0,0 +1,20 @@
import React from 'react';
import styles from './style.css';
import {Button} from 'coral-ui';
import {Link} from 'react-router';
const InitialStep = () => {
return (
<div className={`${styles.step} ${styles.finalStep}`}>
<p>
Thanks for installing Talk! We sent an email to verify your email
address. While you finish setting the account, you can start engaging
with your readers now.
</p>
<Button raised><Link to='/admin'>Launch Talk</Link></Button>
<Button cStyle='black' raised><a href="http://coralproject.net">Close this Installer</a></Button>
</div>
);
};
export default InitialStep;
@@ -0,0 +1,19 @@
import React from 'react';
import styles from './style.css';
import {Button} from 'coral-ui';
const InitialStep = props => {
const {nextStep} = props;
return (
<div className={styles.step}>
<p>
The remainder of the Talk installation will take about ten minutes.
Once you complete the following two steps, you will have a free
installation and provision of Mongo and Redis.
</p>
<Button cStyle='green' onClick={nextStep} raised>Get Started</Button>
</div>
);
};
export default InitialStep;
@@ -0,0 +1,44 @@
import React from 'react';
import styles from './style.css';
import {Button, Select, Option, TextField} from 'coral-ui';
const InviteTeamMembers = props => {
const {nextStep} = props;
return (
<div className={styles.step}>
<h3>Invite Team Members </h3>
<p>
Once registered, new team members will receive an email to Create
their password.
</p>
<div className={styles.form}>
<form>
<TextField
className={styles.textField}
id="email"
type="email"
label='Email address' required/>
<TextField
className={styles.textField}
id="username"
type="text"
label='Username' required/>
<div className={styles.textField}>
<label htmlFor='role'>Assing a role</label>
<Select id='role' label='Select Role'>
<Option>Admin</Option>
<Option>Moderator</Option>
</Select>
</div>
<Button cStyle='black' onClick={nextStep} icon='arrow_forward' full>Invite team member</Button>
</form>
</div>
</div>
);
};
export default InviteTeamMembers;
@@ -0,0 +1,77 @@
.step {
padding: 20px 0;
h3 {
max-width: 450px;
margin: 0 auto;
text-align: left;
font-size: 1.4em;
font-weight: bold;
}
p {
max-width: 450px;
margin: 0 auto 30px;
font-size: 1.1em;
text-align: left;
}
> button {
min-width: 145px;
a {
text-decoration: none;
color: inherit;
}
}
.form {
max-width: 300px;
margin: 30px auto;
text-align: left;
form > button {
margin: 30px 0;
}
.textField {
text-align: left;
label {
text-align: left;
display: block;
margin: 10px 0;
font-weight: 400;
font-size: 1.08em;
}
> input {
border: solid 1px rgba(0, 0, 0, 0.23);
padding: 10px;
border-radius: 3px;
width: 100%;
box-sizing: border-box;
transition: border-color 0.2s ease;
font-size: 1em;
&:focus {
border-color: black;
}
}
}
}
}
.finalStep {
button {
width: 225px;
margin-right: 10px;
}
}
.error {
background: #FFEBEE;
color: #B71C1C;
padding: 10px;
margin-bottom: 20px;
border-radius: 2px;
}
@@ -0,0 +1,12 @@
.Install {
max-width: 900px;
margin: 0 auto;
text-align: center;
padding: 50px 0;
h2 {
font-size: 2em;
font-weight: 500;
margin: 0;
}
}
@@ -8,7 +8,11 @@ import {PermissionRequired} from '../components/PermissionRequired';
class LayoutContainer extends Component {
componentWillMount () {
const {checkLogin} = this.props;
checkLogin();
checkLogin().then(() => {
if (!this.props.auth.isAdmin) {
location.href = '/admin/login';
}
});
}
render () {
const {isAdmin, loggedIn, loadingUser} = this.props.auth;
@@ -1,42 +1,32 @@
import React from 'react';
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {compose} from 'react-apollo';
import key from 'keymaster';
import isEqual from 'lodash/isEqual';
import {modQueueQuery} from '../../graphql/queries';
import {banUser, setCommentStatus} from '../../graphql/mutations';
import {
updateStatus,
showBanUserDialog,
hideBanUserDialog,
fetchPendingQueue,
fetchRejectedQueue,
fetchFlaggedQueue,
fetchModerationQueueComments,
} from 'actions/comments';
import {userStatusUpdate, sendNotificationEmail} from 'actions/users';
import {fetchSettings} from 'actions/settings';
import {updateAssets} from 'actions/assets';
import {toggleModal, singleView, showBanUserDialog, hideBanUserDialog} from 'actions/moderation';
import {Spinner} from 'coral-ui';
import BanUserDialog from '../../components/BanUserDialog';
import ModerationQueue from './ModerationQueue';
import ModerationMenu from './components/ModerationMenu';
import ModerationHeader from './components/ModerationHeader';
import NotFoundAsset from './components/NotFoundAsset';
class ModerationContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
activeTab: 'all',
singleView: false,
modalOpen: false
};
this.onClose = this.onClose.bind(this);
this.onTabClick = this.onTabClick.bind(this);
}
class ModerationContainer extends Component {
componentWillMount() {
this.props.fetchModerationQueueComments();
const {toggleModal, singleView} = this.props;
this.props.fetchSettings();
key('s', () => this.setState({singleView: !this.state.singleView}));
key('shift+/', () => this.setState({modalOpen: true}));
key('esc', () => this.setState({modalOpen: false}));
key('s', () => singleView());
key('shift+/', () => toggleModal(true));
key('esc', () => toggleModal(false));
}
componentWillUnmount() {
@@ -45,85 +35,85 @@ class ModerationContainer extends React.Component {
key.unbind('esc');
}
componentDidMount() {
// Hack for dynamic mdl tabs
if (typeof componentHandler !== 'undefined') {
// FIXME: fix this hack
componentHandler.upgradeAllRegistered(); // eslint-disable-line no-undef
componentWillReceiveProps(nextProps) {
const {updateAssets} = this.props;
if(!isEqual(nextProps.data.assets, this.props.data.assets)) {
updateAssets(nextProps.data.assets);
}
}
onTabClick(activeTab) {
this.setState({activeTab});
if (activeTab === 'pending') {
this.props.fetchPendingQueue();
} else if (activeTab === 'rejected') {
this.props.fetchRejectedQueue();
} else if (activeTab === 'flagged') {
this.props.fetchFlaggedQueue();
} else {
this.props.fetchModerationQueueComments();
}
}
onClose() {
this.setState({modalOpen: false});
}
render () {
const {comments, actions} = this.props;
const premodIds = comments.ids.filter(id => comments.byId[id].status === 'PREMOD');
const rejectedIds = comments.ids.filter(id => comments.byId[id].status === 'REJECTED');
const flaggedIds = comments.ids.filter(id =>
comments.byId[id].flagged === true &&
comments.byId[id].status !== 'REJECTED' &&
comments.byId[id].status !== 'ACCEPTED'
);
const userActionIds = actions.ids.filter(id => actions.byId[id].item_type === 'USERS');
const {data, moderation, settings, assets, ...props} = this.props;
const providedAssetId = this.props.params.id;
const activeTab = this.props.route.path === ':id' ? 'premod' : this.props.route.path;
let asset;
if (data.loading) {
return <div><Spinner/></div>;
}
if (data.error) {
console.log(data);
return <div>Error</div>;
}
if (providedAssetId) {
asset = assets.find(asset => asset.id === this.props.params.id);
if (!asset) {
return <NotFoundAsset assetId={providedAssetId} />;
}
}
return (
<ModerationQueue
onTabClick={this.onTabClick}
onClose={this.onClose}
premodIds={premodIds}
userActionIds={userActionIds}
rejectedIds={rejectedIds}
flaggedIds={flaggedIds}
{...this.props}
{...this.state}
/>
<div>
<ModerationHeader asset={asset} />
<ModerationMenu
asset={asset}
premodCount={data.premodCount}
rejectedCount={data.rejectedCount}
flaggedCount={data.flaggedCount}
/>
<ModerationQueue
data={data}
currentAsset={asset}
activeTab={activeTab}
suspectWords={settings.wordlist.suspect}
showBanUserDialog={props.showBanUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
/>
<BanUserDialog
open={moderation.banDialog}
user={moderation.user}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
/>
</div>
);
}
}
const mapStateToProps = state => ({
comments: state.comments.toJS(),
moderation: state.moderation.toJS(),
settings: state.settings.toJS(),
users: state.users.toJS(),
actions: state.actions.toJS(),
assets: state.assets.get('assets')
});
const mapDispatchToProps = dispatch => {
return {
fetchSettings: () => dispatch(fetchSettings()),
fetchModerationQueueComments: () => dispatch(fetchModerationQueueComments()),
fetchPendingQueue: () => dispatch(fetchPendingQueue()),
fetchRejectedQueue: () => dispatch(fetchRejectedQueue()),
fetchFlaggedQueue: () => dispatch(fetchFlaggedQueue()),
showBanUserDialog: (userId, userName, commentId) => dispatch(showBanUserDialog(userId, userName, commentId)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => {
dispatch(fetchModerationQueueComments());
}),
suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId))
.then(() => dispatch(sendNotificationEmail(userId, subject, text)))
.then(() => dispatch(fetchModerationQueueComments()))
,
updateStatus: (action, comment) => dispatch(updateStatus(action, comment))
};
};
const mapDispatchToProps = dispatch => ({
toggleModal: toggle => dispatch(toggleModal(toggle)),
onClose: () => dispatch(toggleModal(false)),
singleView: () => dispatch(singleView()),
updateAssets: assets => dispatch(updateAssets(assets)),
fetchSettings: () => dispatch(fetchSettings()),
showBanUserDialog: (user, commentId) => dispatch(showBanUserDialog(user, commentId)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
});
export default connect(mapStateToProps, mapDispatchToProps)(ModerationContainer);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
setCommentStatus,
modQueueQuery,
banUser
)(ModerationContainer);
@@ -0,0 +1,9 @@
import React from 'react';
const ModerationLayout = props => (
<div>
{props.children}
</div>
);
export default ModerationLayout;
@@ -1,53 +0,0 @@
@custom-media --big-viewport (min-width: 780px);
.listContainer {
max-width: 860px;
margin: 0 auto;
}
.tabBar {
background: #262626;
z-index: 5;
}
.tab {
flex: 1;
color: white;
text-transform: capitalize;
font-weight: 500;
font-size: 15px;
letter-spacing: 1px;
transition: border-bottom 200ms;
}
.active {
color: white;
box-sizing: border-box;
border-bottom: solid 5px #F36451;
}
.active > span {
color: white;
}
.active:after {
background: transparent !important;
}
.showShortcuts {
position: absolute;
right: 130px;
display: flex;
align-items: center;
font-size: 13px;
span {
margin-left: 7px;
}
}
@media (--big-viewport) {
.tab {
flex: none;
}
}
@@ -1,198 +1,48 @@
import React from 'react';
import styles from './ModerationQueue.css';
import ModerationKeysModal from 'components/ModerationKeysModal';
import ModerationList from 'components/ModerationList';
import BanUserDialog from 'components/BanUserDialog';
import React, {PropTypes} from 'react';
import Comment from './components/Comment';
import EmptyCard from '../../components/EmptyCard';
import {actionsMap} from './helpers/moderationQueueActionsMap';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import translations from 'coral-admin/src/translations';
const lang = new I18n(translations);
export default (props) => (
<div>
<div className='mdl-tabs'>
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<a href='#all'
onClick={(e) => {
e.preventDefault();
props.onTabClick('all');
}}
className={`mdl-tabs__tab ${styles.tab} ${props.activeTab === 'all' ? styles.active : ''}`}
>
{lang.t('modqueue.all')}
</a>
<a href='#pending'
onClick={(e) => {
e.preventDefault();
props.onTabClick('pending');
}}
className={`mdl-tabs__tab ${styles.tab} ${props.activeTab === 'pending' ? styles.active : ''}`}
>
{lang.t('modqueue.pending')}
</a>
<a href='#account'
onClick={(e) => {
e.preventDefault();
props.onTabClick('account');
}}
className={`mdl-tabs__tab ${styles.tab} ${props.activeTab === 'account' ? styles.active : ''}`}>
{lang.t('modqueue.account')}
</a>
<a href='#rejected'
onClick={(e) => {
e.preventDefault();
props.onTabClick('rejected');
}}
className={`mdl-tabs__tab ${styles.tab} ${props.activeTab === 'rejected' ? styles.active : ''}`}
>
{lang.t('modqueue.rejected')}
</a>
<a href='#flagged'
onClick={(e) => {
e.preventDefault();
props.onTabClick('flagged');
}}
className={`mdl-tabs__tab ${styles.tab} ${props.activeTab === 'flagged' ? styles.active : ''}`}
>
{lang.t('modqueue.flagged')}
</a>
</div>
<div className={`mdl-tabs__panel is-active ${styles.listContainer}`} id='all'>
{
props.activeTab === 'all' &&
<div>
<ModerationList
suspectWords={props.settings.settings.wordlist.suspect}
isActive={props.activeTab === 'all'}
singleView={props.singleView}
commentIds={[...props.premodIds, ...props.flaggedIds]}
comments={props.comments.byId}
users={props.users.byId}
actionIds={props.userActionIds}
actions={props.actions.byId}
userStatusUpdate={props.userStatusUpdate}
suspendUser={props.suspendUser}
updateCommentStatus={props.updateStatus}
onClickShowBanDialog={props.showBanUserDialog}
modActions={['reject', 'approve', 'ban']}
loading={props.comments.loading}/>
<BanUserDialog
open={props.comments.showBanUserDialog}
handleClose={props.hideBanUserDialog}
onClickBanUser={props.userStatusUpdate}
user={props.comments.banUser}
/>
</div>
}
</div>
<div className={`mdl-tabs__panel is-active ${styles.listContainer}`} id='pending'>
{
props.activeTab === 'pending' &&
<div>
<ModerationList
suspectWords={props.settings.settings.wordlist.suspect}
isActive={props.activeTab === 'pending'}
singleView={props.singleView}
commentIds={props.premodIds}
comments={props.comments.byId}
users={props.users.byId}
actions={props.actions.byId}
userStatusUpdate={props.userStatusUpdate}
suspendUser={props.suspendUser}
updateCommentStatus={props.updateStatus}
onClickShowBanDialog={props.showBanUserDialog}
modActions={['reject', 'approve', 'ban']}
loading={props.comments.loading}/>
<BanUserDialog
open={props.comments.showBanUserDialog}
handleClose={props.hideBanUserDialog}
onClickBanUser={props.userStatusUpdate}
user={props.comments.banUser}
/>
</div>
}
</div>
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='account'>
{
props.activeTab === 'account' &&
<div>
<ModerationList
suspectWords={props.settings.settings.wordlist.suspect}
isActive={props.activeTab === 'account'}
singleView={props.singleView}
users={props.users.byId}
actionIds={props.userActionIds}
actions={props.actions.byId}
userStatusUpdate={props.userStatusUpdate}
suspendUser={props.suspendUser}
updateCommentStatus={props.updateStatus}
onClickShowBanDialog={props.showBanUserDialog}
modActions={['reject', 'approve', 'ban']}
loading={props.comments.loading}/>
<BanUserDialog
open={props.comments.showBanUserDialog}
handleClose={props.hideBanUserDialog}
onClickBanUser={props.userStatusUpdate}
user={props.comments.banUser}
/>
</div>
}
</div>
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='flagged'>
const ModerationQueue = ({activeTab = 'premod', ...props}) => {
const areComments = props.data[activeTab].length;
return (
<div id="moderationList">
<ul style={{paddingLeft: 0}}>
{
props.activeTab === 'flagged' &&
<div>
<ModerationList
suspectWords={props.settings.settings.wordlist.suspect}
isActive={props.activeTab === 'flagged'}
singleView={props.singleView}
commentIds={props.flaggedIds}
userStatusUpdate={props.userStatusUpdate}
suspendUser={props.suspendUser}
comments={props.comments.byId}
users={props.users.byId}
updateCommentStatus={props.updateStatus}
modActions={['reject', 'approve']}
loading={props.comments.loading}/>
<BanUserDialog
open={props.comments.showBanUserDialog}
handleClose={props.hideBanUserDialog}
onClickBanUser={props.userStatusUpdate}
user={props.comments.banUser}
/>
</div>
}
</div>
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='rejected'>
{
props.activeTab === 'rejected' &&
<div>
<ModerationList
suspectWords={props.settings.settings.wordlist.suspect}
isActive={props.activeTab === 'rejected'}
singleView={props.singleView}
commentIds={props.rejectedIds}
userStatusUpdate={props.userStatusUpdate}
suspendUser={props.suspendUser}
comments={props.comments.byId}
users={props.users.byId}
updateCommentStatus={props.updateStatus}
modActions={['approve']}
loading={props.comments.loading}
/>
<BanUserDialog
open={props.comments.showBanUserDialog}
handleClose={props.hideBanUserDialog}
onClickBanUser={props.userStatusUpdate}
user={props.comments.banUser}
/>
</div>
}
</div>
<ModerationKeysModal open={props.modalOpen} onClose={props.closeModal} />
areComments
? props.data[activeTab].map((comment, i) => {
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
return <Comment
key={i}
index={i}
comment={comment}
suspectWords={props.suspectWords}
actions={actionsMap[status]}
showBanUserDialog={props.showBanUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
currentAsset={props.currentAsset}
/>;
})
: <EmptyCard>{lang.t('modqueue.emptyqueue')}</EmptyCard>
}
</ul>
</div>
</div>
);
);
};
ModerationQueue.propTypes = {
data: PropTypes.object.isRequired,
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
showBanUserDialog: PropTypes.func.isRequired,
currentAsset: PropTypes.object,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired
};
export default ModerationQueue;
@@ -0,0 +1,95 @@
import React, {PropTypes} from 'react';
import timeago from 'timeago.js';
import Linkify from 'react-linkify';
import Highlighter from 'react-highlight-words';
import {Link} from 'react-router';
import styles from './styles.css';
import {Icon} from 'coral-ui';
import ActionButton from '../../../components/ActionButton';
import FlagBox from './FlagBox';
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 = [], ...props}) => {
const links = linkify.getMatches(props.comment.body);
const actionSummaries = props.comment.action_summaries;
return (
<li tabIndex={props.index}
className={`mdl-card mdl-shadow--2dp ${styles.Comment} ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span>{props.comment.user.name}</span>
<span className={styles.created}>
{timeago().format(props.comment.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}
</span>
{props.comment.action_summaries ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
</div>
<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) =>
<ActionButton key={i}
type={action}
user={props.comment.user}
acceptComment={() => props.acceptComment({commentId: props.comment.id})}
rejectComment={() => props.rejectComment({commentId: props.comment.id})}
showBanUserDialog={() => props.showBanUserDialog(props.comment.user, props.comment.id)}
/>
)}
</div>
{props.comment.user.status === 'banned' ?
<span className={styles.banned}>
<Icon name='error_outline'/>
{lang.t('comment.banned_user')}
</span>
: null}
</div>
</div>
{!props.currentAsset && (
<div className={styles.moderateArticle}>
Article: {props.comment.asset.title} <Link to={`/admin/moderate/${props.comment.asset.id}`}>Moderate Article</Link>
</div>
)}
<div className={styles.itemBody}>
<p className={styles.body}>
<Linkify component='span' properties={{style: linkStyles}}>
<Highlighter searchWords={props.suspectWords} textToHighlight={props.comment.body}/>
</Linkify>
</p>
</div>
{actionSummaries && <FlagBox actionSummaries={actionSummaries} />}
</li>
);
};
Comment.propTypes = {
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
isActive: PropTypes.bool.isRequired,
comment: PropTypes.shape({
body: PropTypes.string.isRequired,
action_summaries: PropTypes.array,
created_at: PropTypes.string.isRequired,
user: PropTypes.shape({
status: PropTypes.string
}),
asset: PropTypes.shape({
title: PropTypes.string,
id: PropTypes.string
})
})
};
const linkStyles = {
backgroundColor: 'rgb(255, 219, 135)',
padding: '1px 2px'
};
export default Comment;
@@ -0,0 +1,14 @@
.count {
display: inline-block;
background: #989797;
margin: 2px;
vertical-align: middle;
padding: 4px 7px;
border-radius: 2px;
margin-left: 10px;
line-height: 20px;
box-sizing: border-box;
height: 27px;
right: 0;
margin-top: -2px;
}
@@ -0,0 +1,12 @@
import React, {PropTypes} from 'react';
import styles from './CommentCount.css';
const CommentCount = props => (
<span className={styles.count}>{props.count}</span>
);
CommentCount.propTypes = {
count: PropTypes.number.isRequired
};
export default CommentCount;
@@ -0,0 +1,19 @@
import React, {PropTypes} from 'react';
import styles from './styles.css';
const FlagBox = props => (
<div className={styles.flagBox}>
<h3>Flags:</h3>
<ul>
{props.actionSummaries.map((action, i) =>
<li key={i}>{!action.reason ? <i>No reason provided</i> : action.reason} (<strong>{action.count}</strong>)</li>
)}
</ul>
</div>
);
FlagBox.propTypes = {
actionSummaries: PropTypes.array.isRequired
};
export default FlagBox;
@@ -0,0 +1,29 @@
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">
{props.asset.title}
<a href={props.asset.url} className={styles.settingsButton}><Icon name="settings"/></a>
</a>
<Link className="mdl-tabs__tab" to="/admin/streams">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/streams">Select Stream</Link>
</div>
}
</div>
</div>
);
export default ModerationHeader;
@@ -0,0 +1,42 @@
import React, {PropTypes} from 'react';
import CommentCount from './CommentCount';
import styles from './styles.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
import {Link} from 'react-router';
const lang = new I18n(translations);
const ModerationMenu = ({asset, premodCount, rejectedCount, flaggedCount}) => {
const premodPath = asset ? `/admin/moderate/premod/${asset.id}` : '/admin/moderate/premod';
const rejectPath = asset ? `/admin/moderate/rejected/${asset.id}` : '/admin/moderate/rejected';
const flagPath = asset ? `/admin/moderate/flagged/${asset.id}` : '/admin/moderate/flagged';
return (
<div className='mdl-tabs'>
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<div>
<Link to={premodPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('modqueue.premod')} <CommentCount count={premodCount} />
</Link>
<Link to={rejectPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('modqueue.rejected')} <CommentCount count={rejectedCount} />
</Link>
<Link to={flagPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('modqueue.flagged')} <CommentCount count={flaggedCount} />
</Link>
</div>
</div>
</div>
);
};
ModerationMenu.propTypes = {
premodCount: PropTypes.number.isRequired,
rejectedCount: PropTypes.number.isRequired,
flaggedCount: PropTypes.number.isRequired,
asset: PropTypes.shape({
id: PropTypes.string
})
};
export default ModerationMenu;
@@ -0,0 +1,14 @@
import React from 'react';
import {Link} from 'react-router';
import styles from './styles.css';
const NotFound = props => (
<div className={`mdl-card mdl-shadow--2dp ${styles.notFound}`}>
<p>
The provided asset id <Link to={`/admin/moderate/${props.assetId}`}>{props.assetId}</Link> does not exist.
<Link className={styles.goToStreams} to="/admin/streams">Go to Streams</Link>
</p>
</div>
);
export default NotFound;
@@ -0,0 +1,336 @@
@custom-media --big-viewport (min-width: 780px);
.listContainer {
max-width: 860px;
margin: 0 auto;
}
.tabBar {
background-color: rgba(44, 44, 44, 0.89);
z-index: 5;
}
.tab {
flex: 1;
color: white;
text-transform: capitalize;
font-weight: 500;
font-size: 15px;
letter-spacing: 1px;
transition: border-bottom 200ms;
}
.active {
color: white;
box-sizing: border-box;
border-bottom: solid 5px #F36451;
}
.active > span {
color: white;
}
.active:after {
background: transparent !important;
}
.showShortcuts {
position: absolute;
right: 130px;
display: flex;
align-items: center;
font-size: 13px;
span {
margin-left: 7px;
}
}
@media (--big-viewport) {
.tab {
flex: none;
}
}
.notFound {
position: relative;
margin: 20px auto;
text-align: center;
padding: 68px 45px;
vertical-align: middle;
min-width: 500px;
a {
color: rgb(244, 126, 107);
font-weight: 500;
&.goToStreams {
position: absolute;
right: 10px;
bottom: 10px;
}
}
}
.header {
background-color: #2c2c2c;
color: white;
margin-bottom: -1px;
.settingsButton {
i {
vertical-align: middle;
margin-left: 10px;
margin-top: -4px;
}
}
.moderateAsset {
a {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
color: white;
text-transform: capitalize;
font-weight: 500;
font-size: 15px;
letter-spacing: 1px;
transition: opacity 200ms;
opacity: 1;
&:hover {
opacity: .8;
cursor: pointer;
}
&:first-child {
text-align: left;
}
&:nth-child(2) {
text-align: center;
}
&:last-child {
text-align: right;
}
}
}
}
@custom-media --big-viewport (min-width: 780px);
.list {
padding: 8px 0;
list-style: none;
display: block;
&.singleView .listItem {
display: none;
}
&.singleView .listItem.activeItem {
display: block;
height: 100%;
font-size: 1.5em;
line-height: 1.5em;
border: none;
.actions {
position: fixed;
bottom: 60px;
left: 25%;
margin: 0 auto;
display: flex;
justify-content: space-around;
width: 50%;
margin: 0;
}
.actionButton {
transform: scale(1.4);
}
}
}
.listItem {
border-bottom: 1px solid #e0e0e0;
font-size: 16px;
width: 100%;
max-width: 660px;
min-width: 400px;
margin: 0 auto;
padding: 16px 14px;
position: relative;
transition: box-shadow 200ms;
margin-top: 0;
&:hover {
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
&:last-child {
border-bottom: none;
}
.context {
a {
color: #f36451;
text-decoration: underline;
float: right;
}
}
.sideActions {
position: absolute;
right: 0;
height: 100%;
top: 0;
padding: 40px 18px;
box-sizing: border-box;
}
.itemHeader {
display: flex;
align-items: center;
justify-content: space-between;
.author {
min-width: 230px;
display: flex;
align-items: center;
}
}
.itemBody {
display: flex;
justify-content: space-between;
}
.avatar {
margin-right: 16px;
height: 40px;
width: 40px;
border-radius: 50%;
background-color: #757575;
font-size: 40px;
color: #fff;
}
.created {
color: #666;
font-size: 13px;
margin-left: 40px;
}
.actionButton {
transform: scale(.8);
margin: 0;
}
.body {
margin-top: 0px;
flex: 1;
font-size: 0.88em;
color: black;
max-width: 500px;
word-wrap: break-word;
}
.flagged {
color: rgba(255, 0, 0, .5);
padding-top: 15px;
padding-left: 10px;
}
.flagCount{
font-size: 12px;
color: #d32f2f;
}
}
.empty {
color: #444;
margin-top: 50px;
text-align: center;
}
@media (--big-viewport) {
.listItem {
border: 1px solid #e0e0e0;
margin-bottom: 30px;
&:last-child {
border-bottom: 1px solid #e0e0e0;
}
&.activeItem {
border: 2px solid #333;
}
}
}
.hasLinks {
color: #f00;
text-align: right;
display: flex;
align-items: center;
i {
margin-right: 5px;
}
}
.banned {
color: #f00;
text-align: left;
display: flex;
align-items: center;
i {
margin-right: 5px;
}
}
.ban {
display: block;
text-align: center;
margin-top: 5px;
}
.Comment {
.moderateArticle {
font-size: 12px;
a {
display: inline-block;
color: #679af3;
text-decoration: none;
font-size: 1em;
font-weight: 400;
letter-spacing: .5px;
font-size: 12px;
margin-left: 10px;
&:hover {
text-decoration: underline;
opacity: .9;
cursor: pointer;
}
}
}
}
.flagBox {
max-width: 480px;
border-top: 1px solid rgba(66, 66, 66, 0.12);
h3 {
font-size: 14px;
margin: 0;
font-weight: 500;
}
}
@@ -0,0 +1,13 @@
export const actionsMap = {
PREMOD: ['REJECT', 'APPROVE', 'BAN'],
FLAGGED: ['REJECT', 'APPROVE', 'BAN'],
REJECTED: ['APPROVE']
};
export const menuActionsMap = {
'REJECT': {status: 'REJECTED', icon: 'close', key: 'r'},
'APPROVE': {status: 'ACCEPTED', icon: 'done', key: 't'},
'FLAGGED': {status: 'FLAGGED', icon: 'flag', filter: 'Untouched'},
'BAN': {status: 'BANNED', icon: 'not interested'},
'': {icon: 'done'}
};
@@ -55,6 +55,12 @@
border-left: none;
border-right: none;
a {
color: rgb(44, 44, 44);
font-weight: 500;
text-decoration: none;
}
th {
font-size: 1.1em;
}
@@ -4,14 +4,11 @@ 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 {
RadioGroup,
Radio,
Icon,
DataTable,
TableHeader
} from 'react-mdl';
import Pager from 'coral-ui/components/Pager';
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;
@@ -38,12 +35,13 @@ class Streams extends Component {
}
onSearchChange = (e) => {
const search = e.target.value;
this.setState((prevState) => {
prevState.search = e.target.value;
prevState.search = search;
clearTimeout(prevState.timer);
const fetchAssets = this.props.fetchAssets;
prevState.timer = setTimeout(() => {
fetchAssets(0, limit, e.target.value, this.state.sort, this.state.filter);
fetchAssets(0, limit, search, this.state.sort, this.state.filter);
}, 350);
return prevState;
});
@@ -73,6 +71,8 @@ class Streams extends Component {
}
}
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];
@@ -103,6 +103,9 @@ class Streams extends Component {
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}>
@@ -140,24 +143,25 @@ class Streams extends Component {
<Radio value='asc'>{lang.t('streams.oldest')}</Radio>
</RadioGroup>
</div>
<div className={styles.mainContent}>
<DataTable
className={styles.streamsTable}
rows={assets.ids.map((id) => assets.byId[id])}>
<TableHeader name="title">{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>
{
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>
);
}
@@ -168,6 +172,7 @@ const mapStateToProps = ({assets}) => {
assets: assets.toJS()
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchAssets: (...args) => {
@@ -0,0 +1,15 @@
fragment commentView on Comment {
id
body
created_at
status
user {
id
name: username
status
}
asset {
id
title
}
}
@@ -0,0 +1,38 @@
import {graphql} from 'react-apollo';
import SET_USER_STATUS from './setUserStatus.graphql';
import SET_COMMENT_STATUS from './setCommentStatus.graphql';
export const banUser = graphql(SET_USER_STATUS, {
props: ({mutate}) => ({
banUser: ({userId}) => {
return mutate({
variables: {
userId,
status: 'BANNED'
}
});
}}),
});
export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
props: ({mutate}) => ({
acceptComment: ({commentId}) => {
return mutate({
variables: {
commentId,
status: 'ACCEPTED'
},
refetchQueries: ['ModQueue']
});
},
rejectComment: ({commentId}) => {
return mutate({
variables: {
commentId,
status: 'REJECTED'
},
refetchQueries: ['ModQueue']
});
}
})
});
@@ -0,0 +1,7 @@
mutation setCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){
setCommentStatus(id: $commentId, status: $status) {
errors {
translation_key
}
}
}
@@ -0,0 +1,7 @@
mutation setUserStatus($userId: ID!, $status: USER_STATUS!) {
setUserStatus(id: $userId, status: $status) {
errors {
translation_key
}
}
}
@@ -0,0 +1,6 @@
query Assets {
assets {
id
title
}
}
@@ -0,0 +1,30 @@
import {graphql} from 'react-apollo';
import MOST_FLAGS from './mostFlags.graphql';
import MOD_QUEUE_QUERY from './modQueueQuery.graphql';
export const mostFlags = graphql(MOST_FLAGS, {
options: () => {
// currently hard-coded per Greg's advice
const fiveMinutesAgo = new Date();
fiveMinutesAgo.setMinutes(fiveMinutesAgo.getMinutes() - 305);
return {
variables: {
sort: 'FLAG',
from: fiveMinutesAgo.toISOString(),
to: new Date().toISOString()
}
};
}
});
export const modQueueQuery = graphql(MOD_QUEUE_QUERY, {
options: ({params: {id = null}}) => {
return {
variables: {
asset_id: id
}
};
}
});
@@ -0,0 +1,47 @@
#import "../fragments/commentView.graphql"
query ModQueue ($asset_id: ID) {
premod: comments(query: {
statuses: [PREMOD],
asset_id: $asset_id
}) {
...commentView
}
flagged: comments(query: {
action_type: FLAG,
asset_id: $asset_id,
statuses: [NONE, PREMOD]
}) {
...commentView
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
rejected: comments(query: {
statuses: [REJECTED],
asset_id: $asset_id
}) {
...commentView
}
assets: assets {
id
title
url
}
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]
})
}
@@ -0,0 +1,14 @@
query Metrics ($from: Date!, $to: Date!, $sort: ACTION_TYPE!) {
metrics(from: $from, to: $to, sort: $sort) {
id
title
url
commentCount
author
created_at
action_summaries {
actionCount
actionableItemCount
}
}
}
+12 -3
View File
@@ -1,6 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {render} from 'react-dom';
import {ApolloProvider} from 'react-apollo';
import {client} from './services/client';
import store from './services/store';
import App from './components/App';
// Render the application into the DOM
ReactDOM.render(<App />, document.querySelector('#root'));
render(
<ApolloProvider client={client} store={store}>
<App />
</ApolloProvider>
, document.querySelector('#root')
);
@@ -1,27 +0,0 @@
import {Map, Set, fromJS} from 'immutable';
import * as types from '../constants/actions';
const initialState = Map({
ids: Set(),
byId: Map()
});
export default (state = initialState, action) => {
switch (action.type) {
case types.ACTIONS_MODERATION_QUEUE_FETCH_SUCCESS: return addActions(state, action);
default:
return state;
}
};
const addActions = (state, action) => {
// Make ids that are unique by item_id and by action type
const typeId = (action) => `${action.action_type}_${action.item_id}`;
const ids = action.actions.map(action => typeId(action));
const map = action.actions.reduce((memo, action) => {
memo[typeId(action)] = action;
return memo;
}, {});
return state.set('byId', fromJS(map)).set('ids', new Set(ids));
};
+14 -8
View File
@@ -1,20 +1,26 @@
import {Map, List, fromJS} from 'immutable';
import {FETCH_ASSETS_SUCCESS, UPDATE_ASSET_STATE_REQUEST} from '../constants/assets';
import * as actions from '../constants/assets';
const initialState = Map({
byId: Map(),
ids: List()
ids: List(),
assets: List()
});
export default (state = initialState, action) => {
export default function assets (state = initialState, action) {
switch (action.type) {
case FETCH_ASSETS_SUCCESS:
case actions.FETCH_ASSETS_SUCCESS:
return replaceAssets(action, state);
case UPDATE_ASSET_STATE_REQUEST:
return state.setIn(['byId', action.id, 'closedAt'], action.closedAt);
default: return state;
case actions.UPDATE_ASSET_STATE_REQUEST:
return state
.setIn(['byId', action.id, 'closedAt'], action.closedAt);
case actions.UPDATE_ASSETS:
return state
.set('assets', List(action.assets));
default:
return state;
}
};
}
const replaceAssets = (action, state) => {
const assets = fromJS(action.assets.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {}));
@@ -1,77 +0,0 @@
import * as actions from '../constants/comments';
import * as userActions from '../constants/users';
import {Map, List, fromJS} from 'immutable';
/**
* Comments state is stored using 2 structures:
* - byId is a Map holding the comments using the item_id property as keys
* - ids is a List of item_id, this allows us to order and iterate easily
* since maps are unordered and some times we just need a list of things
*/
const initialState = Map({
byId: Map(),
ids: List(),
loading: false,
showBanUserDialog: false,
banUser: {
'userName': '',
'userId': '',
'commentId': ''
}
});
// Handle the comment actions
export default (state = initialState, action) => {
switch (action.type) {
case actions.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST: return state.set('loading', true);
case actions.COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS: return replaceComments(action, state);
case actions.COMMENTS_MODERATION_QUEUE_FAILED: return state.set('loading', false);
case actions.COMMENT_STATUS_UPDATE_REQUEST: return updateStatus(state, action);
case actions.COMMENT_FLAG: return flag(state, action);
case actions.COMMENT_CREATE_SUCCESS: return addComment(state, action);
case actions.COMMENT_STREAM_FETCH_SUCCESS: return replaceComments(action, state);
case actions.SHOW_BANUSER_DIALOG: return setBanUser(state, true, action);
case actions.HIDE_BANUSER_DIALOG: return setBanUser(state, false, action);
case actions.USER_BAN_SUCCESS: return setBanUser(state, false, action);
case userActions.UPDATE_STATUS_SUCCESS: return setBanUser(state, false, action);
default: return state;
}
};
// hide or show the UI for the dialog confirming the ban
// set the user that is going to set and the comment that is the reason
const setBanUser = (state, showBanUser, action) => {
const banUser = {'userName': action.userName, 'userId': action.userId, 'commentId': action.commentId};
return state.set('showBanUserDialog', showBanUser)
.set('banUser', banUser);
};
// Update a comment status
const updateStatus = (state, action) => {
const byId = state.get('byId');
const data = byId.get(action.id).set('status', action.status.toLowerCase());
return state.set('byId', byId.set(action.id, data));
};
// Flag a comment
const flag = (state, action) => {
const byId = state.get('byId');
const data = byId.get(action.id).set('flagged', true);
const comment = byId.get(action.id).set('data', data);
return state.set('byId', byId.set(action.id, comment));
};
// Replace the comment list with a new one
const replaceComments = (action, state) => {
const comments = fromJS(action.comments.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {}));
return state.set('byId', comments).set('loading', false)
.set('ids', List(comments.keys()));
};
// Add a new comment
const addComment = (state, action) => {
const comment = fromJS(action.comment);
return state.set('byId', state.get('byId').set(comment.get('item_id'), comment))
.set('ids', state.get('ids').unshift(comment.get('item_id')));
};
+12 -16
View File
@@ -1,19 +1,15 @@
import {combineReducers} from 'redux';
import comments from 'reducers/comments';
import settings from 'reducers/settings';
import community from 'reducers/community';
import users from 'reducers/users';
import auth from 'reducers/auth';
import actions from 'reducers/actions';
import assets from 'reducers/assets';
import auth from './auth';
import assets from './assets';
import settings from './settings';
import community from './community';
import moderation from './moderation';
import install from './install';
// Combine all reducers into a main one
export default combineReducers({
settings,
comments,
community,
export default {
auth,
actions,
assets,
users
});
settings,
community,
moderation,
install
};
@@ -0,0 +1,91 @@
import {Map} from 'immutable';
import * as actions from '../constants/install';
const initialState = Map({
isLoading: false,
data: Map({
settings: Map({
organizationName: ''
}),
user: Map({
username: '',
email: '',
password: '',
confirmPassword: ''
})
}),
errors: Map({
organizationName: '',
username: '',
email: '',
password: '',
confirmPassword: ''
}),
showErrors: false,
hasError: false,
error: null,
step: 0,
navItems: [{
text: '1. Add Organization Name',
step: 1
},
{
text: '2. Create your account',
step: 2
}],
installRequest: null,
installRequestError: null,
alreadyInstalled: false
});
export default function install (state = initialState, action) {
switch (action.type) {
case actions.NEXT_STEP:
return state
.set('step', state.get('step') + 1);
case actions.PREVIOUS_STEP:
return state
.set('step', state.get('step') - 1);
case actions.GO_TO_STEP:
return state
.set('step', action.step);
case actions.UPDATE_FORMDATA_SETTINGS:
return state
.setIn(['data', 'settings', action.name], action.value);
case actions.UPDATE_FORMDATA_USER:
return state
.setIn(['data', 'user', action.name], action.value);
case actions.HAS_ERROR:
return state
.merge({
hasError: true,
showErrors: true
});
case actions.ADD_ERROR:
return state
.setIn(['errors', action.name], action.error);
case actions.CLEAR_ERRORS:
return state
.set('errors', Map());
case actions.INSTALL_REQUEST:
return state
.set('isLoading', true);
case actions.INSTALL_SUCCESS:
return state
.set('isLoading', false)
.set('installRequest', 'SUCCESS');
case actions.INSTALL_FAILURE:
return state
.merge({
isLoading: false,
installRequest: 'FAILURE',
installRequestError: action.error
});
case actions.CHECK_INSTALL_SUCCESS:
return state
.set('alreadyInstalled', action.installed);
default :
return state;
}
}
@@ -0,0 +1,36 @@
import {Map} from 'immutable';
import * as actions from '../constants/moderation';
const initialState = Map({
singleView: false,
modalOpen: false,
user: Map({}),
commentId: null,
banDialog: false
});
export default function moderation (state = initialState, action) {
switch (action.type) {
case actions.HIDE_BANUSER_DIALOG:
return state
.set('banDialog', false);
case actions.SHOW_BANUSER_DIALOG:
return state
.merge({
user: Map(action.user),
commentId: action.commentId,
banDialog: true
});
case actions.SET_ACTIVE_TAB:
return state
.set('activeTab', action.activeTab);
case actions.TOGGLE_MODAL:
return state
.set('modalOpen', action.open);
case actions.SINGLE_VIEW:
return state
.set('singleView', !state.get('singleView'));
default :
return state;
}
}
+51 -44
View File
@@ -1,55 +1,62 @@
import {Map, List} from 'immutable';
import * as types from '../actions/settings';
import * as actions from '../actions/settings';
const initialState = Map({
settings: Map({
wordlist: Map({
banned: List(),
suspect: List()
})
wordlist: Map({
banned: List(),
suspect: List()
}),
domains: Map({
whitelist: List()
}),
saveSettingsError: null,
fetchSettingsError: null,
fetchingSettings: false
});
// Handle the comment actions
export default (state = initialState, action) => {
export default function settings (state = initialState, action) {
switch (action.type) {
case types.SETTINGS_LOADING: return state.set('fetchingSettings', true).set('fetchSettingsError', null);
case types.SETTINGS_RECEIVED: return updateSettings(state, action);
case types.SETTINGS_FETCH_ERROR: return settingsFetchFailed(state, action);
case types.SETTINGS_UPDATED: return updateSettings(state, action);
case types.SAVE_SETTINGS_LOADING: return state.set('fetchingSettings', true).set('saveSettingsError', null);
case types.SAVE_SETTINGS_SUCCESS: return saveComplete(state, action);
case types.SAVE_SETTINGS_FAILED: return settingsSaveFailed(state, action);
case types.WORDLIST_UPDATED: return updateWordlist(state, action);
default: return state;
case actions.SETTINGS_LOADING:
return state
.set('fetchingSettings', true)
.set('fetchSettingsError', null);
case actions.SETTINGS_RECEIVED:
return state.merge({
fetchingSettings: null,
fetchSettingsError: null,
...action.settings
});
case actions.SETTINGS_FETCH_ERROR:
return state
.set('fetchingSettings', false)
.set('fetchSettingsError', action.error);
case actions.SETTINGS_UPDATED:
return state.merge({
fetchingSettings: null,
fetchSettingsError: null,
...action.settings
});
case actions.SAVE_SETTINGS_LOADING:
return state
.set('fetchingSettings', true)
.set('saveSettingsError', null);
case actions.SAVE_SETTINGS_SUCCESS:
return state.merge({
fetchingSettings: false,
fetchSettingsError: null,
...action.settings
});
case actions.SAVE_SETTINGS_FAILED:
return state
.set('fetchingSettings', false)
.set('fetchSettingsError', action.error);
case actions.WORDLIST_UPDATED:
return state
.setIn(['wordlist', action.listName], action.list);
case actions.DOMAINLIST_UPDATED:
return state
.setIn(['domains', action.listName], action.list);
default:
return state;
}
};
// only for updating top-level settings
const updateSettings = (state, action) => {
const s = state.set('fetchingSettings', false).set('fetchSettingsError', null);
const settings = s.get('settings').merge(action.settings);
return s.set('settings', settings);
};
// any nested settings must have a specialized setter
const updateWordlist = (state, action) => {
return state.setIn(['settings', 'wordlist', action.listName], action.list);
};
const saveComplete = (state, action) => {
const s = state.set('fetchingSettings', false).set('saveSettingsError', null);
const settings = s.get('settings').merge(action.settings);
return s.set('settings', settings);
};
const settingsFetchFailed = (state, action) => {
return state.set('fetchingSettings', false).set('fetchSettingsError', action.error);
};
const settingsSaveFailed = (state, action) => {
return state.set('fetchingSettings', false).set('fetchSettingsError', action.error);
};
}
-28
View File
@@ -1,28 +0,0 @@
import {Map, List, fromJS} from 'immutable';
const initialState = Map({
byId: Map(),
ids: List()
});
export default (state = initialState, action) => {
switch (action.type) {
case 'USERS_MODERATION_QUEUE_FETCH_SUCCESS': return replaceUsers(action, state);
case 'USER_STATUS_UPDATE': return updateUserStatus(state, action);
default: return state;
}
};
// Replace the comment list with a new one
const replaceUsers = (action, state) => {
const users = fromJS(action.users.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {}));
return state.set('byId', users)
.set('ids', List(users.keys()));
};
// Update a user status
const updateUserStatus = (state, action) => {
const byId = state.get('byId');
const data = byId.get(action.author_id).set('status', action.status.toLowerCase());
return state.set('byId', byId.set(action.author_id, data));
};
+13
View File
@@ -0,0 +1,13 @@
import ApolloClient, {addTypename} from 'apollo-client';
import getNetworkInterface from './transport';
export const client = new ApolloClient({
queryTransformer: addTypename,
dataIdFromObject: (result) => {
if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle
return result.__typename + result.id; // eslint-disable-line no-underscore-dangle
}
return null;
},
networkInterface: getNetworkInterface()
});
+19 -12
View File
@@ -1,17 +1,24 @@
import {createStore, applyMiddleware} from 'redux';
import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import mainReducer from 'reducers';
import mainReducer from '../reducers';
import {client} from './client';
/**
* Create the store by merging the app reducers with
* the talk adapter. The talk adapter is the wire between
* this client and the coral backend. The idea is we can
* write different adapters for other platforms if we want
*/
const middlewares = [
applyMiddleware(client.middleware()),
applyMiddleware(thunk)
];
if (window.devToolsExtension) {
// we can't have the last argument of compose() be undefined
middlewares.push(window.devToolsExtension());
}
export default createStore(
mainReducer,
window.devToolsExtension && window.devToolsExtension(),
applyMiddleware(thunk)
combineReducers({
...mainReducer,
apollo: client.reducer()
}),
{},
compose(...middlewares)
);

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