Compare commits

...
968 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
Wyatt Johnson d72b661c93 Merge pull request #272 from coralproject/fix-252
CLI + Init fixes
2017-01-31 10:47:02 -07:00
Wyatt Johnson 0af41a5b6b Updated circle test 2017-01-30 18:41:23 -07:00
Wyatt Johnson 864413b5d6 Fixed issues with e2e 2017-01-30 18:38:06 -07:00
Wyatt Johnson 91cd2e9e86 Fixes #266 2017-01-30 18:21:36 -07:00
Wyatt Johnson e6ef4b615f Changed prompt lib, updated cli's
- fixes #252
2017-01-30 17:46:09 -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
Wyatt Johnson b75557912f Merge pull request #268 from coralproject/graph-pagination
Graph Pagination
2017-01-30 14:43:44 -07:00
Belen Curcio f9363e86d0 Merge 2017-01-30 17:24:57 -03:00
Belén Curcio 887fe08819 Merge branch 'master' into graph-pagination 2017-01-30 17:12:23 -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
David Erwin 1070dfd5ae Merge pull request #249 from coralproject/e2e-env-fix
E2E environment fix
2017-01-30 12:14:50 -05: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
Wyatt Johnson 04cd7312a6 Added support for new commentCount 2017-01-27 15:52:05 -07:00
David Jay b674ff45c0 Addressing errors with replies in e2e. 2017-01-27 17:29:23 -05:00
Riley Davis 5e521b832c add a confirm-email landing page 2017-01-27 15:04:42 -07:00
David Jay 1da61784b4 Fixing bugs around settings in e2e. 2017-01-27 16:10:47 -05:00
David Jay f0bb9db892 Merge branch 'e2e-env-fix' of github.com:coralproject/talk into e2e-env-fix 2017-01-27 15:21:47 -05:00
David Jay 9d15a0448a Adding settings init to pree2e script. 2017-01-27 15:21:18 -05:00
David Jay f09d9d0a8b Merge branch 'master' into e2e-env-fix 2017-01-27 14:10:59 -05:00
David Jay 801d18639b Merge branch 'e2e-env-fix' of github.com:coralproject/talk into e2e-env-fix 2017-01-27 14:09:29 -05:00
David Jay fcf22ef4f9 Adding selenium settings and fixing bug in moderation approval. 2017-01-27 14:07:34 -05:00
Wyatt Johnson 2e6c7f7b09 First pass at pagination 2017-01-27 12:05:19 -07:00
Wyatt Johnson d829d2541f Added new env var for e2e reports 2017-01-27 11:20:45 -07:00
Wyatt Johnson ebc928c6f2 Merge pull request #267 from coralproject/lodash-dep
Lodash Import
2017-01-27 11:03:59 -07:00
Belen Curcio 55a8b4984a only importing isEqual 2017-01-27 14:58:36 -03:00
Belen Curcio 1778c19f6a çhanges 2017-01-27 14:55:55 -03:00
David Erwin 087b1a7bd7 Merge pull request #264 from coralproject/postmod-default
Update default setting to POST moderation
2017-01-27 11:16:36 -05:00
Wyatt Johnson 0096de6e79 Merge branch 'master' into postmod-default 2017-01-27 09:11:51 -07:00
Kim Gardner 88d718e7c9 Merge pull request #265 from coralproject/remove-avatar
Remove avatar from user cards
2017-01-27 10:32:12 -05:00
David Erwin c8c160a551 Remove avatar from user cards 2017-01-27 10:16:09 -05:00
Wyatt Johnson 5d1fd61425 Merge branch 'master' into e2e-env-fix 2017-01-27 08:04:34 -07:00
Wyatt Johnson 9fbde9c023 Update init.js 2017-01-27 08:04:00 -07:00
David Erwin 744fce4563 Set settings model as well 2017-01-27 10:01:58 -05:00
David Erwin c071484357 Removing test artifiacts 2017-01-27 09:48:41 -05:00
David Erwin 0a80205095 Update default setting to POST moderation 2017-01-27 09:45:35 -05:00
David Erwin fbc5a6660a Merge pull request #263 from coralproject/moderate-bio
Marking correct user as "Pending" on user flags.
2017-01-27 09:25:02 -05:00
Wyatt Johnson 71647d5233 Merge branch 'master' into e2e-env-fix 2017-01-26 16:47:52 -07:00
David Jay e7587d9a41 Merge branch 'master' into moderate-bio 2017-01-26 17:46:20 -05:00
David Jay 02ece80227 Marking correct user as Pending on user flags. 2017-01-26 17:42:45 -05:00
David Erwin 74d78b76a5 Merge pull request #219 from coralproject/moderate-bio
Moderate bio or username
2017-01-26 17:09:44 -05:00
David Jay d4c9bdb6fd Merge branch 'e2e-env-fix' of github.com:coralproject/talk into e2e-env-fix 2017-01-26 16:52:12 -05:00
David Jay b8ff2b6763 Resetting chrome version to 2.25 in nightwatch. 2017-01-26 16:51:03 -05:00
Wyatt Johnson fa985acd14 Update circle.yml 2017-01-26 14:45:02 -07:00
David Jay f06edec6a2 Merge branch 'master' of github.com:coralproject/talk into moderate-bio 2017-01-26 16:38:43 -05:00
David Jay b6d8a90084 Updating style of UserActions and moving ActionButton to its own component. 2017-01-26 16:36:43 -05:00
David Erwin 3d08d1f54f Merge branch 'master' into e2e-env-fix 2017-01-26 16:30:14 -05:00
David Erwin b35336d1ed Merge pull request #260 from coralproject/hide-premod-links
Hiding premod links
2017-01-26 16:28:48 -05:00
David Erwin f42b8f324a Merge branch 'master' into hide-premod-links 2017-01-26 16:24:06 -05:00
David Erwin 30583cd93f Merge pull request #261 from coralproject/collapse-replies
Collapse replies
2017-01-26 16:21:07 -05:00
David Erwin 2086bd7b72 Merge branch 'master' into collapse-replies 2017-01-26 16:17:23 -05:00
David Erwin fe62c1a739 Merge pull request #257 from coralproject/refresh-data-fix
Cleanup and more GraphQL features
2017-01-26 16:15:55 -05:00
David Jay 06331d3c07 Updating bio moderation to graphQL architecture. 2017-01-26 15:31:15 -05:00
Riley Davis 2ad27ef6f9 add a cancel button to the reply box 2017-01-26 13:21:23 -07:00
Riley Davis 3cbe8bb7ac Stream now tracks what reply box is open 2017-01-26 12:45:14 -07:00
David Jay 963fc1f091 Merge branch 'e2e-env-fix' of github.com:coralproject/talk into e2e-env-fix 2017-01-26 14:41:41 -05:00
David Jay 2e34f726eb Updating tests to reflect graphql changes. 2017-01-26 14:41:03 -05: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
David Erwin afb246b173 Hiding premod links 2017-01-26 10:46:01 -05:00
Wyatt Johnson 48cc82b039 Merge branch 'master' into e2e-env-fix 2017-01-26 07:59:01 -07:00
Belen Curcio 88169526c3 Missing semicolon 2017-01-26 10:50:53 -03:00
Belen Curcio 4a54c571ed Open Stream and Closed Stream status 2017-01-26 10:45:50 -03:00
Belen Curcio 853981e21f Working configure tab and refresh data. Adding uncontrolled components, deleting state 2017-01-26 10:19:49 -03:00
Belen Curcio 74bef18098 user checker 2017-01-26 09:34:47 -03:00
Belen Curcio 64ed10845b Merge 2017-01-25 21:59:42 -03:00
David Erwin 0b73f18eb3 Merge pull request #258 from coralproject/streams-by-asset-id
Streams by asset
2017-01-25 17:48:41 -05:00
David Erwin 1a065ffa51 Merge branch 'streams-by-asset-id' of https://github.com/coralproject/talk into streams-by-asset-id 2017-01-25 17:45:09 -05:00
David Erwin c4256fd27b Ditch console logs 2017-01-25 17:44:21 -05:00
David Jay aa83aea849 Merge branch 'master' of github.com:coralproject/talk into e2e-env-fix 2017-01-25 17:17:04 -05:00
David Jay 31abe57cb2 Merge branch 'master' of github.com:coralproject/talk into moderate-bio
Integrating graphql, updating tests, and preserving transition to ModerationList from CommentList.
2017-01-25 17:15:12 -05:00
Wyatt Johnson 000f2c64bb Merge branch 'master' into streams-by-asset-id 2017-01-25 14:50:51 -07:00
Wyatt Johnson 34ca36822f Merge pull request #259 from coralproject/graphql-abstraction
Moved the graphql file out of the const string
2017-01-25 14:50:42 -07:00
Wyatt Johnson 0035037583 Merge branch 'master' into graphql-abstraction 2017-01-25 14:32:52 -07:00
Wyatt Johnson 693e92a627 Moved the graphql file out of the string 2017-01-25 14:31:11 -07:00
David Erwin b4abb9724e Fix homepage bug 2017-01-25 15:59:08 -05:00
David Erwin 9764631a37 Fix asset search skip limit bug 2017-01-25 15:37:18 -05:00
David Erwin e320a9dd20 Merge branch 'master' into streams-by-asset-id 2017-01-25 15:30:44 -05:00
David Erwin 1f1b1d43ae Add useful links 2017-01-25 15:27:38 -05:00
David Erwin 29e69a66bb Add basic index page for assets 2017-01-25 15:23:02 -05:00
Wyatt Johnson 9598c55466 Merge pull request #256 from coralproject/graoh-fixes
Comment ordering fixed, banned users can't do anything
2017-01-25 13:16:05 -07:00
Belen Curcio ec0dd34d40 Configure tab while you are loggedOff 2017-01-25 16:33:55 -03:00
David Erwin ad9684acd9 Create load stream by id route 2017-01-25 14:08:19 -05:00
Wyatt Johnson 91402a3e8b Moved PID into util.js 2017-01-25 11:57:40 -07:00
Wyatt Johnson edf9ced454 Added PID support 2017-01-25 11:11:33 -07:00
Belen Curcio 7c132600f0 Console log and typo 2017-01-25 11:47:35 -03:00
Belen Curcio b3eb3e3c43 Updated tests for Comment History 2017-01-25 11:44:51 -03:00
Belen Curcio d19d2ebe48 My Comment History Working - linted 2017-01-25 11:19:34 -03:00
Belen Curcio 30b6f8d112 my Comment History working 🎉 : TADA : 2017-01-25 11:04:23 -03:00
Belen Curcio 14483a39a0 Tests passing, removing legacy items 2017-01-25 08:38:03 -03:00
Belen Curcio c1d3cdce77 deprecating items 2017-01-25 08:11:52 -03:00
Belen Curcio cebc5f2f45 Adding fragment views 2017-01-25 07:09:11 -03:00
Belen Curcio b1848c28e4 Adding Missing Translations 2017-01-25 07:08:55 -03:00
Belen Curcio e993330467 Adding fragments 🎉 2017-01-25 05:20:34 -03:00
Belen Curcio e3b183c565 Items cleanup 2017-01-25 05:15:04 -03:00
Wyatt Johnson 373f119ac2 Added more comment sorting 2017-01-24 17:22:29 -07:00
Wyatt Johnson eb469df573 Removed debugging tool 2017-01-24 17:08:12 -07:00
Wyatt Johnson 26b7d48f1f Comment ordering fixed, banned users can't do anything 2017-01-24 17:06:40 -07:00
David Erwin 79b366f257 Merge pull request #255 from coralproject/translation-fix
fixed translation key, enforced moderation setting schema
2017-01-24 18:34:48 -05:00
Wyatt Johnson b6d917a22b fixed translation key, enforced moderation setting schema 2017-01-24 16:08:13 -07:00
David Erwin 622afcc80f Merge pull request #254 from coralproject/graphql
Graphql redux (as in 'the return of')
2017-01-24 18:02:18 -05:00
David Erwin a09c208f58 Merge branch 'master' into graphql 2017-01-24 17:56:04 -05:00
Wyatt Johnson 2698045e82 Fixed capital 2017-01-24 15:50:37 -07:00
Wyatt Johnson 5a2d8c5201 Fixed template tag 2017-01-24 15:40:32 -07:00
David Erwin 9616c50d49 Merge pull request #251 from coralproject/graphql
Graphql for the comment feed
2017-01-24 17:24:11 -05:00
David Erwin 66b0babdde Merge branch 'graphql' of https://github.com/coralproject/talk into graphql 2017-01-24 17:17:38 -05:00
David Erwin 9435b627b1 Uppercase more enums in coral-admin 2017-01-24 17:17:03 -05:00
Riley Davis da7349343e Merged branch graphql into graphql 2017-01-24 15:09:09 -07:00
Riley Davis 62ae297887 make sure the app does not break if you don't have react devtools installed 2017-01-24 15:08:38 -07:00
Belen Curcio d2acffc19c linting! 2017-01-24 19:07:53 -03:00
Belen Curcio 054baa0530 çonfig actions not defined 2017-01-24 19:01:45 -03:00
Belen Curcio fb791a9df9 merge 2017-01-24 18:52:37 -03:00
Belen Curcio ecfb365c24 log 2017-01-24 18:51:06 -03:00
Wyatt Johnson fa300b1c24 Fixes to circle 2017-01-24 14:38:44 -07:00
David Jay b0acdff34d Merge branch 'graphql' of github.com:coralproject/talk into graphql 2017-01-24 16:23:27 -05:00
Belen Curcio 6f44b9cba0 Merge branch 'graphql' of github.com:coralproject/talk into graphql 2017-01-24 18:23:11 -03:00
David Jay 7c3f566202 Handling links inside of iframe in via Pym. 2017-01-24 16:21:49 -05:00
Belen Curcio 9edf99730d Status 2017-01-24 18:21:49 -03:00
Wyatt Johnson 050e98f157 Fixed report location 2017-01-24 14:12:52 -07:00
David Erwin 5566856542 Merge branch 'graphql' of https://github.com/coralproject/talk into graphql 2017-01-24 16:12:13 -05:00
David Erwin 53dd34f406 Updating admin screen for all caps enums 2017-01-24 16:11:17 -05:00
Belen Curcio 9d7b727f08 Settings container 2017-01-24 18:08:58 -03:00
Wyatt Johnson 603854c0be Adjusted tests for circle 2017-01-24 14:06:39 -07:00
David Jay ce73118f53 Loading history links in parent page. 2017-01-24 16:04:03 -05:00
Wyatt Johnson 2ffecbeeff Removed build step, is already done via postinstall hook 2017-01-24 13:46:53 -07:00
Wyatt Johnson 0a5a23c1f3 Added fixes for tests on CI 2017-01-24 13:45:06 -07:00
Belen Curcio ee9674cd23 Settings stream tab 2017-01-24 17:27:30 -03:00
Wyatt Johnson 9dc57c82d1 Fixes to consts + services 2017-01-24 13:27:22 -07:00
Belen Curcio 5750758cf1 Error with asset resolver 2017-01-24 17:27:05 -03:00
David Jay 88ed6ffe76 Updating logic for returning users to frontend. 2017-01-24 15:20:27 -05:00
Wyatt Johnson aa1f06d1ee Added explicit joinBy to dataLoader reponse 2017-01-24 13:12:51 -07:00
Wyatt Johnson cda768d694 Fixer pre/post 2017-01-24 13:10:35 -07:00
Belen Curcio 33ec0798e9 Uppercase model 2017-01-24 17:07:23 -03:00
Wyatt Johnson eeaefd9656 fixes for cli 2017-01-24 13:01:56 -07:00
Wyatt Johnson 3a59a785f8 Uppercased status's 2017-01-24 12:58:20 -07:00
Wyatt Johnson 5e9f8eb624 Fixed statuses 2017-01-24 12:43:22 -07:00
David Jay d921a59858 Hiding bio tooltip when the user has no bio. 2017-01-24 14:39:46 -05:00
David Jay 74ced3b2fd Removing display of user ID in settings. 2017-01-24 14:35:14 -05:00
David Jay 6e9757ae91 Updating tests to use capitalized admin. 2017-01-24 14:27:50 -05:00
David Jay 8b8bef4ea7 Merge branch 'graphql' of github.com:coralproject/talk into graphql 2017-01-24 14:19:58 -05:00
David Jay 4b33199f2f Adding signin button to settings page. 2017-01-24 14:19:20 -05:00
Wyatt Johnson 368606befe Fixed yet, another, import 2017-01-24 12:18:32 -07:00
Wyatt Johnson 71f219c0c4 Fixed another import 2017-01-24 12:17:06 -07:00
Wyatt Johnson 0e27984518 Fixed import 2017-01-24 12:15:31 -07:00
Riley Davis 98b3d299e5 Merged branch graphql into graphql 2017-01-24 12:14:06 -07:00
Riley Davis d977c33cf5 refetch when you post a comment 2017-01-24 12:13:56 -07:00
Wyatt Johnson a7e9c0c776 Const, service, and model updates
- Updated enum values to be uppercase
- Updated services to expose service models
- Updated models to only export the mongoose model
- Moved all mongoose static methods over to new services
- Updated tests to refelct new setup

BREAKING

- Status that were uppercased (caps) have caused issues with the
  admin pages
2017-01-24 12:10:32 -07:00
Riley Davis 0994023864 Merged branch graphql into graphql 2017-01-24 12:09:16 -07:00
Riley Davis 8cd929bb1b npm chokes on .js package names 2017-01-24 12:09:04 -07:00
David Jay 840e0d0ef6 Re-adding buttons when the user is not logged in and showing login if a non-logged-in user clicks 'Reply.' 2017-01-24 14:00:16 -05:00
David Jay 90c58885aa Fixing lint errors and renaming Pym file. 2017-01-24 13:23:42 -05:00
David Jay 0d7b482dc1 Re-adding pym import. 2017-01-24 13:21:13 -05:00
Belen Curcio c1fea3151c Merge 2017-01-24 15:15:17 -03:00
Belen Curcio a409fcbab7 Asset reducers 2017-01-24 15:10:09 -03:00
Riley Davis 4e8306bcc9 close reply box when comment is posted 2017-01-24 10:55:51 -07:00
David Jay c4e1c1ae44 Merge branch 'graphql' of github.com:coralproject/talk into graphql 2017-01-24 12:53:30 -05:00
David Jay 58e50b4242 Fixing permalinks and moving Pym to a seperate file. 2017-01-24 12:51:52 -05:00
Riley Davis 7fd075a7a6 posting a reply refetches comments 2017-01-24 10:43:29 -07:00
David Jay 75bcf29154 Replies to replies are children of the root comment. 2017-01-24 12:37:50 -05:00
David Jay 33d274386a Re-adding display of comment date. 2017-01-24 12:24:30 -05:00
Belen Curcio 365f60c428 Reducer and constants 2017-01-24 14:22:58 -03:00
Belen Curcio f375ff48c9 dispatching 2017-01-24 14:13:43 -03:00
Belen Curcio 765edb48b9 Émbed 2017-01-24 14:07:46 -03:00
David Jay 9fbd1d3a77 Displaying author names and bios. 2017-01-24 11:49:59 -05:00
Belen Curcio 9fd184f638 Store normalized 💪 2017-01-24 10:06:42 -03:00
Belen Curcio c3f238b0b2 Connected to the Apollo Store 🎉 2017-01-24 09:56:26 -03:00
Riley Davis fa4c6e8ec5 lint! 2017-01-23 16:56:44 -07:00
Riley Davis 6282c39f3a merge remote. can reply to a comment 2017-01-23 16:52:16 -07:00
Wyatt Johnson 9e22c100f0 Added admin login form (naive approach) 2017-01-23 16:31:37 -07:00
Belen Curcio 01f905b79d Merge branch 'graphql' of github.com:coralproject/talk into graphql 2017-01-23 19:46:34 -03:00
Belen Curcio adf4729d89 Resolver settings fix, info box and func working! 2017-01-23 19:45:34 -03:00
David Jay 44058b1718 Adding flag action buttons. 2017-01-23 17:34:24 -05:00
David Jay da761468b6 Enabling like buttons. 2017-01-23 16:18:51 -05:00
David Jay a8227bd4ad Activating post and delete actions on likebutton. 2017-01-23 14:22:25 -05:00
Wyatt Johnson d1621ad424 Fixed context 2017-01-23 11:57:35 -07:00
David Jay b2d9688c89 Adding postAction mutation and fixing lint errors. 2017-01-23 13:25:24 -05:00
David Jay f023d1de08 Merge branch 'master' into e2e-env-fix 2017-01-23 12:07:12 -05:00
Wyatt Johnson 6563f29ccf Moved tests -> test because mocha likes that 2017-01-20 17:28:08 -07:00
Wyatt Johnson fbd0bf52fe Fixed methods 2017-01-20 16:53:43 -07:00
Wyatt Johnson 0ec2c11cf7 modularized mutators, loaders 2017-01-20 16:49:19 -07:00
Riley Davis 32d23214f1 replies 2017-01-20 16:23:34 -07:00
Riley Davis 0dcc912fd0 whoops, committed broken code 2017-01-20 15:58:36 -07:00
Riley Davis dfc073ab10 Merge remote-tracking branch 'refs/remotes/origin/graphql'
Conflicts:
	client/coral-embed-stream/src/Embed.js
2017-01-20 15:56:03 -07:00
Riley Davis d724351fb6 merge my changes 2017-01-20 15:54:18 -07:00
Belen Curcio 9c40ee1245 Login sign In back on! 🎉 2017-01-20 19:35:31 -03:00
Belen Curcio 450d74848e Notifications are back 2017-01-20 19:20:43 -03:00
Belen Curcio 39bf092ebb Correct Status 2017-01-20 18:56:42 -03:00
Belen Curcio b98b71189f atempt to solution 2017-01-20 18:53:11 -03:00
Belen Curcio f813a77194 queries and mutations 2017-01-20 18:48:35 -03:00
Belen Curcio 715faf321f Wordlist is undefined 2017-01-20 18:04:30 -03:00
Belen Curcio 2615db8575 mutations and queries, lint errors 2017-01-20 17:59:28 -03:00
Belen Curcio ae29d65c75 mutations and queries in separate folders 🎉 2017-01-20 17:40:33 -03:00
Belen Curcio d89f41a041 Merge branch 'graphql' of github.com:coralproject/talk into graphql 2017-01-20 16:47:28 -03:00
Belen Curcio 3c540b9daa createComment and suspect list 2017-01-20 16:47:02 -03:00
Riley Davis 8f95e29315 Stream is a separate component that renders comments 2017-01-20 12:41:33 -07:00
Belen Curcio f0f692fc26 ádding webpack loader and post comment 2017-01-20 16:39:23 -03:00
Riley Davis d14fa33077 rename files to make more sense 2017-01-20 12:36:33 -07:00
Riley Davis c16f3b8fbe change scalar URL type for a bit 2017-01-20 11:54:18 -07:00
Belen Curcio 7a5aa0a80c Changes 2017-01-20 15:25:18 -03:00
Belen Curcio cc740a4a5e Adding closedAt status commentStream 2017-01-20 12:52:35 -03:00
Belen Curcio 0f15c21cc9 Merge branch 'graphql' of github.com:coralproject/talk into graphql 2017-01-20 12:34:14 -03:00
Belen Curcio 89c1fd19b7 Merge branch 'graphql' of github.com:coralproject/talk into graphql 2017-01-20 12:32:46 -03:00
Wyatt Johnson 07b2f9c2e5 Update typeDefs.js 2017-01-20 08:13:45 -07:00
Wyatt Johnson f286263f1f Adjusted placement of server side graph code. 2017-01-19 18:26:33 -07:00
Wyatt Johnson fec18b50d1 Merge branch 'master' into graphql 2017-01-19 18:08:38 -07:00
Wyatt Johnson 1db1908778 Introduced some changes to the resolvers + mutators 2017-01-19 18:06:06 -07:00
Riley Davis 54e5a18546 Merge pull request #250 from coralproject/registration-fix
found the bang
2017-01-19 14:31:34 -07:00
Riley Davis a7094efda6 Merge branch 'master' into registration-fix 2017-01-19 14:27:49 -07:00
David Erwin c207a59c18 Merge pull request #248 from coralproject/registration-fix
Registration fix
2017-01-19 16:24:49 -05:00
riley 9f4c30f466 found the bang 2017-01-19 14:23:57 -07:00
David Jay 8bad6f7a73 Enabling signup. 2017-01-19 15:51:38 -05:00
David Jay e9846c654c Enabling stream login with graphql. 2017-01-19 15:37:05 -05:00
David Erwin fa53327571 Merge branch 'master' into registration-fix 2017-01-19 15:20:52 -05:00
David Jay 672d1ee19e Updating store to include auth and apollo. 2017-01-19 13:39:46 -05:00
Wyatt Johnson 840c91274d Added comments to typeDefs 2017-01-19 11:17:47 -07:00
Wyatt Johnson 03a5acc736 Modularized reolvers 2017-01-19 11:01:24 -07:00
Wyatt Johnson d683b09b1d Added more mutators 2017-01-19 10:39:29 -07:00
Kim Gardner 70a1525058 Merge pull request #242 from coralproject/design-pass-adm
Design Pass Admin
2017-01-19 12:31:48 -05:00
Belen Curcio b172e30e5b By doing this we can refactor one by one and still having a working app 2017-01-19 11:30:30 -03:00
Belen Curcio 2d414a5ab7 Transport, Client, from coral-framework 2017-01-19 10:01:43 -03:00
David Jay d34d511ce6 Merge branch 'graphql' of github.com:coralproject/talk into graphql 2017-01-18 23:13:50 -05:00
David Jay a40971e376 Adding thunk to apollog redux store. 2017-01-18 19:23:47 -05:00
David Jay 44f3f99b47 Adding ability to query the currently logged in user. 2017-01-18 19:20:36 -05:00
David Jay d3c215a18d Switching bare-bones commentstream to graphql. 2017-01-18 18:23:32 -05:00
Riley Davis bbfd017998 adding changes 2017-01-18 15:27:48 -07:00
Riley Davis 58e7abbe0c sdfasdf 2017-01-18 15:25:48 -07:00
Riley Davis 89a188255f super amazing code. 2017-01-18 15:23:07 -07:00
Riley Davis 5f63af8140 Merged branch master into graphql 2017-01-18 14:24:45 -07:00
David Erwin 347fe8bf2b Showing graphql data in console 2017-01-18 16:15:40 -05:00
David Erwin e36ecdfb33 Implement stream with data fetch 2017-01-18 15:58:36 -05:00
Riley Davis 07e24496da allow uppercase displaynames at creation 2017-01-18 13:13:02 -07:00
Riley Davis f07026d6dd hide uppercase login 2017-01-18 12:57:07 -07:00
Wyatt Johnson 5b30fd89c8 More comments 2017-01-18 11:28:39 -07:00
Wyatt Johnson 52ed18a9b8 Added comments 2017-01-18 11:23:47 -07:00
David Jay f8e55ddc8b Merge branch 'master' of github.com:coralproject/talk into e2e-env-fix 2017-01-18 12:44:44 -05:00
David Jay 074630e628 Updating usernames and passwords to new standards. 2017-01-18 12:43:09 -05:00
Belén Curcio 7140723570 Merge branch 'master' into design-pass-adm 2017-01-18 14:40:09 -03:00
Riley Davis b4ca90a05d Merge pull request #247 from coralproject/prevent-server-restart
add nodemon config to prevent server restart while developing front end
2017-01-18 10:14:34 -07:00
David Jay b59512d2b8 Allowing e2e to run with only node_env=test. 2017-01-18 12:12:49 -05:00
Riley Davis f9bc623764 make a hidden file 2017-01-18 10:10:47 -07:00
Riley Davis 810cc002c9 add nodemon config to prevent server restart while developing front end 2017-01-18 10:02:15 -07:00
David Erwin c03f85d4b6 Merge pull request #246 from coralproject/my-comment-fix-part-deux
fix my comments
2017-01-18 10:49:33 -05:00
Wyatt Johnson 17849898ae First pass 2017-01-18 00:09:26 -07:00
Riley Davis 14fd5e4451 does a null check 2017-01-17 15:12:19 -07:00
Riley Davis 8588cab228 Merge pull request #245 from coralproject/revert-244-my-comments-fix
Revert "My Comment bug fix"
2017-01-17 14:59:37 -07:00
Riley Davis af71059948 Revert "My Comment bug fix" 2017-01-17 14:53:28 -07:00
Riley Davis dc866437ff Merge pull request #244 from coralproject/my-comments-fix
My Comment bug fix
2017-01-17 14:26:49 -07:00
Belen Curcio b5afd9cbeb çhanges 2017-01-17 16:39:05 -03:00
Belen Curcio 3640a93a2f Changes 2017-01-17 16:25:47 -03:00
Riley Davis 1b50cb06d4 gotta commit something to see changes 2017-01-17 11:38:15 -07:00
Belen Curcio 6d826e9115 Working Stream 2017-01-17 12:47:06 -03:00
David Erwin 5fff644e7d Merge pull request #230 from coralproject/bug-comment-number
Fixes comment number change when clicking the "Settings" tab.
2017-01-17 10:37:33 -05:00
David Erwin 249a8f78dc Merge branch 'master' into bug-comment-number 2017-01-17 10:03:34 -05:00
David Erwin 5408036eb3 Merge pull request #231 from coralproject/bugfix-flagged-actions
Removed 'rejected' and 'approved' comments from flagged queue.
2017-01-17 10:01:19 -05:00
David Erwin 04e3f12c87 Merge branch 'master' into bugfix-flagged-actions 2017-01-17 09:52:26 -05:00
David Erwin d5137ba1c1 Merge pull request #236 from coralproject/password-length-test
show password error when input length < 8
2017-01-17 09:51:34 -05:00
David Erwin e41acffcf0 Merge branch 'master' into password-length-test 2017-01-17 09:47:42 -05:00
Gabriela Rodríguez Berón 5a22e99282 Merge branch 'master' into bug-comment-number 2017-01-13 15:25:27 -08:00
Gabriela Rodríguez Berón 77b52a2454 Merge branch 'master' into bugfix-flagged-actions 2017-01-13 15:10:42 -08:00
Gabriela Rodríguez Berón 05180394c7 Merge pull request #234 from coralproject/preserve-comment-view-state
Adjusted stream to preserve comment view state
2017-01-13 15:10:01 -08:00
Gabriela Rodríguez Berón 4f013c50e6 Merge branch 'master' into bugfix-flagged-actions 2017-01-13 15:05:54 -08:00
Wyatt Johnson da0ca97483 Added comment for SIGUSR2 2017-01-13 16:02:49 -07:00
Gabriela Rodríguez Berón 53a403cddb Merge branch 'master' into preserve-comment-view-state 2017-01-13 14:29:50 -08:00
Gabriela Rodríguez Berón 06812edcd2 Merge pull request #218 from coralproject/unique-username
Unique username
2017-01-13 14:29:36 -08:00
Gabriela Rodríguez Berón 1accc54238 Merge branch 'master' into unique-username 2017-01-13 14:21:42 -08:00
Wyatt Johnson 05c055c3d2 Merge branch 'master' into preserve-comment-view-state 2017-01-13 15:20:49 -07:00
gaba 06ab33343f Errors needs to use APIError. 2017-01-13 14:15:31 -08:00
David Jay 3cc120cd25 Merge pull request #237 from coralproject/bizarro-world
do not render tab content unless they're active
2017-01-13 17:05:36 -05:00
David Jay dc63cc6bce Merge branch 'moderate-bio' of github.com:coralproject/talk into moderate-bio 2017-01-13 16:39:17 -05:00
David Jay 0131dd6b13 Merge branch 'master' of github.com:coralproject/talk into moderate-bio 2017-01-13 16:37:31 -05:00
Riley Davis 44a06aef46 do not render tab content unless they're active 2017-01-13 14:33:01 -07:00
Belen Curcio f0cc82cb6d Changes 2017-01-13 18:16:46 -03:00
David Jay 8632cd9b67 Merge pull request #233 from coralproject/my-comments-part-deux-the-commenting
don't show unpublished comments in My Comments
2017-01-13 16:15:50 -05:00
David Jay 5f58ca6b5d Merge branch 'master' into my-comments-part-deux-the-commenting 2017-01-13 16:08:39 -05:00
Belen Curcio cae101627f Merge branch 'master' of github.com:coralproject/talk into design-pass-adm 2017-01-13 17:14:51 -03:00
Belen Curcio e69262c665 Community Section Ready 2017-01-13 17:11:46 -03:00
Riley Davis 7295d78523 typos 2017-01-13 11:13:28 -07:00
Wyatt Johnson 041241d6b0 Merge branch 'master' into preserve-comment-view-state 2017-01-13 10:33:17 -07:00
Belen Curcio 9876e04454 Ban User box 2017-01-13 13:54:45 -03:00
Belen Curcio 712c53389b Banned Words and Suspect 2017-01-13 13:07:45 -03:00
Wyatt Johnson 9e5dbfd9cf Merge pull request #235 from coralproject/dev-env-docs
Updating docs for .env functionality
2017-01-13 08:29:50 -07:00
Wyatt Johnson 39a4d031c7 Adjusted for new wiki page 2017-01-13 08:24:12 -07:00
David Erwin 7c95781072 Update README.md 2017-01-13 10:21:12 -05:00
David Erwin f56e30c1a1 Updating docs for .env functionality 2017-01-13 10:08:35 -05:00
Wyatt Johnson c90230c115 Adjusted stream to preserve comment view state 2017-01-12 17:55:08 -07:00
Riley Davis ad0f530fd3 don't return unpublished comments in My Comments section. add tests 2017-01-12 17:07:35 -07:00
Wyatt Johnson 281a49dc74 Merge branch 'master' into bugfix-flagged-actions 2017-01-12 16:29:32 -07:00
Gabriela Rodríguez Berón 1cc048d46e Merge branch 'master' into unique-username 2017-01-12 14:53:13 -08:00
Riley Davis 20f90572f5 Merge pull request #228 from coralproject/pending-queue-routes
moderation queue routes
2017-01-12 15:49:44 -07:00
gaba 536a2165cc Merge branch 'master' into unique-username 2017-01-12 14:47:45 -08:00
gaba 1027b19a49 Added regex to look for the field that is duplicated. Not sure if there is a better way to do it. 2017-01-12 14:43:36 -08:00
Riley Davis 5136fce4a3 Merge branch 'master' into pending-queue-routes 2017-01-12 15:34:13 -07:00
David Jay f3f5397459 Removing rejected and approved comments from flagged feed. 2017-01-12 17:32:48 -05:00
Riley Davis 0eefd3364b update tests 2017-01-12 15:25:36 -07:00
David Jay 361be84562 Merge branch 'master' into bug-comment-number 2017-01-12 16:49:16 -05:00
David Jay 3e0d56b942 Keeping hydrated comments in assets in COMMENTS_BY_USER_REQUEST. 2017-01-12 16:41:50 -05:00
Wyatt Johnson 31b7dac48a Merge pull request #229 from coralproject/topic-220
Standardizes errors for API error returns
2017-01-12 14:33:58 -07:00
Belen Curcio ce01389809 Ádding Icon to Coral UI Button 2017-01-12 18:20:42 -03:00
Wyatt Johnson 6e89734e12 Moved errors to errors.js, std error msgs/types 2017-01-12 14:08:52 -07:00
Riley Davis 44b41b031e lint 2017-01-12 14:03:58 -07:00
Belen Curcio 87b350e3a9 Missing tab wrapper 2017-01-12 17:56:00 -03:00
David Jay 1432cfba4a Updating signing dialog style to render as intended in Firefox. 2017-01-12 15:49:23 -05:00
Belen Curcio c375d11e32 Coral UI - List and Item 2017-01-12 17:47:45 -03:00
Riley Davis b66efb7f2a return flagged comments 2017-01-12 13:44:10 -07:00
Riley Davis f1272338cd re-use dispatch function 2017-01-12 13:30:53 -07:00
Riley Davis 3285a5ec29 add status to Comment. /comments/pending and /comments/rejected ONLY return the respective comments 2017-01-12 12:16:13 -07:00
gaba acf3ad5e28 Some conflicts were added. 2017-01-12 11:10:07 -08:00
gaba 15e7ef6368 Adds an error const. 2017-01-12 10:46:18 -08:00
gaba 873ec09f62 Merge branch 'master' into unique-username 2017-01-12 08:46:51 -08:00
Belen Curcio 3d65231db5 Progress 2017-01-12 13:27:37 -03:00
Belén Curcio b957e75d68 Merge branch 'master' into moderate-bio 2017-01-12 09:21:02 -03:00
Belen Curcio 4a4affad70 More Design Tweaks 2017-01-12 09:16:23 -03:00
Belen Curcio 7101d99187 Merge branch 'master' of github.com:coralproject/talk into design-pass-adm 2017-01-12 08:09:55 -03:00
David Jay 61bc87bafc Adding ALL moderation list. 2017-01-11 17:35:09 -05:00
Riley Davis 18b35e38a8 Merge pull request #227 from coralproject/flagged-queue-bugfix
Fix flagged comment queue bug
2017-01-11 14:20:03 -07:00
Wyatt Johnson 6ca7e92171 Merge branch 'master' into flagged-queue-bugfix 2017-01-11 13:48:09 -07:00
David Jay 09e3889793 Updating action type from to for consistency. 2017-01-11 15:42:56 -05:00
David Jay 2a1ea01827 Merge pull request #226 from coralproject/dev-start
Added dev-start and --config flag
2017-01-11 15:14:59 -05:00
Wyatt Johnson 10aaa037dc Added dev-start and --config flag 2017-01-11 11:37:06 -07:00
David Jay 30c7539cde Merge branch 'moderate-bio' of github.com:coralproject/talk into moderate-bio 2017-01-11 13:14:44 -05:00
David Jay e8dc319a9a Merge branch 'master' of github.com:coralproject/talk into moderate-bio 2017-01-11 13:12:07 -05:00
David Jay 98a6ba5f1f Merge branch 'master' into moderate-bio 2017-01-11 12:32:25 -05:00
Wyatt Johnson 5f215a95f9 Merge pull request #222 from coralproject/hide-passwords
Cleaned user auth response
2017-01-11 10:22:28 -07:00
David Jay 9d7810f48c Merge branch 'master' into moderate-bio 2017-01-10 19:48:09 -05:00
Wyatt Johnson 7a21879628 Merge branch 'master' into hide-passwords 2017-01-10 14:53:26 -07:00
Kim Gardner 2e4256bf4a Merge pull request #223 from coralproject/users-bug-fix
Making user action item_type 'users' for consistency.
2017-01-10 16:11:22 -05:00
David Jay 11f18c23cb Merge branch 'master' into users-bug-fix 2017-01-10 14:13:31 -05:00
Belen Curcio 3a510168d3 design progress 2017-01-10 16:12:15 -03:00
Wyatt Johnson 8519d07c67 Cleaned user auth response 2017-01-10 12:00:06 -07:00
David Jay d9147da8fc Making user action item_type 'users' for consistency. 2017-01-10 13:59:36 -05:00
Kim Gardner e87a471eb9 Merge pull request #201 from coralproject/frontenddocs
Frontend Docs
2017-01-10 11:44:03 -05:00
Kim Gardner 6cf4011cde Merge branch 'master' into frontenddocs 2017-01-10 11:40:03 -05:00
Belen Curcio ca369dcb4a Header version 2017-01-10 12:01:48 -03:00
Belen Curcio a4e6638217 Tab bar, removing extra mdl js. It adds more events 2017-01-10 10:55:03 -03:00
Riley Davis f76177c1db Merge pull request #214 from coralproject/bad-words
Bad usernames
2017-01-09 18:13:18 -07:00
Wyatt Johnson 7283feab6b Merge branch 'master' into bad-words 2017-01-09 16:44:19 -07:00
David Jay f957e3c8ce Closing suspend user modal once user is suspended and e-mail is sent. 2017-01-09 18:33:12 -05:00
David Jay 8fa7561e20 Updating item_type for consitency. 2017-01-09 18:27:54 -05:00
David Jay 01d9fc313e Merge branch 'master' of github.com:coralproject/talk into moderate-bio 2017-01-09 18:20:44 -05:00
David Jay 296b941ee8 Merge pull request #221 from coralproject/email-confirm
Email Confirmation Resend Endpoint + Fixes to mailer
2017-01-09 18:19:53 -05:00
David Jay 8601562418 Improving extraction of user e-mail address. 2017-01-09 18:18:19 -05:00
David Jay a2704e3099 Updating user email address. 2017-01-09 18:15:25 -05:00
Wyatt Johnson 586ce941e5 Merge branch 'master' into email-confirm 2017-01-09 16:10:46 -07:00
Wyatt Johnson ec3a114e65 Added missing endpoint + fix for mailer 2017-01-09 15:50:46 -07:00
David Jay 9809403701 Pluralizing user item_type for consistency. 2017-01-09 17:43:39 -05:00
Riley Davis b6eaf210fe don't use regex on existing lists 2017-01-09 15:41:47 -07:00
Belen Curcio be293a03de Header 2017-01-09 19:17:21 -03:00
Belen Curcio 4f4ecd8dfb header 2017-01-09 18:07:02 -03:00
David Jay b8fc2c9cc7 Addressing CSRF and user status issues. 2017-01-09 15:39:08 -05:00
riley 7bc848b801 make tests pass 2017-01-09 10:47:28 -07:00
Belen Curcio 226af72ac9 Adding Roboto 2017-01-09 12:54:28 -03:00
Belen Curcio 1acc92a1f2 Header Changes 2017-01-09 12:45:41 -03:00
David Jay 0f24ab6b28 Merge branch 'master' of github.com:coralproject/talk into moderate-bio 2017-01-06 15:56:03 -05:00
gaba 976f0762b3 Password lenght of 8. 2017-01-06 17:42:42 -03:00
David Jay ec020df488 Adding account flags tab. 2017-01-06 15:42:19 -05:00
David Jay d5a82f04c2 Fixing issues with e-mail and status update. 2017-01-06 15:35:32 -05:00
Riley Davis bba597f086 even more lint 2017-01-06 13:28:48 -07:00
Riley Davis abd2332745 lint 2017-01-06 13:23:51 -07:00
David Jay 9e651a9292 Adding actions to suspend users and send e-mail. 2017-01-06 15:16:23 -05:00
Riley Davis a3bb8e2f41 remove logs. missed some errors 2017-01-06 13:11:37 -07:00
Riley Davis 01db900c40 move defined errors into top-level errors.js file 2017-01-06 13:00:22 -07:00
gaba 4dbc73f9ab Add a test on already used display name. 2017-01-06 16:57:25 -03:00
Riley Davis 6a61f47ddf Merged branch master into bad-words 2017-01-06 12:45:41 -07:00
gaba 712e436f58 Cleans error on email address or name. We need to go through all the errors and manage them better with translations. 2017-01-06 16:38:44 -03:00
Riley Davis 72cd78da8c use wordlist service 2017-01-06 12:35:08 -07:00
David Jay 565e34e09d Adding endpoint for admins to send transactional e-mail. 2017-01-06 14:29:50 -05:00
gaba 8a1eedb0b2 Merge branch 'master' into unique-username 2017-01-06 14:12:46 -03:00
Wyatt Johnson bf04034085 Merge pull request #213 from coralproject/issue-191
Banned users should not be able to report/flag anything from the Front End
2017-01-06 10:03:14 -07:00
Gabriela Rodríguez Berón 1e445efaec Merge branch 'master' into issue-191 2017-01-06 13:33:25 -03:00
Gabriela Rodríguez Berón 466cb289c3 Merge pull request #215 from coralproject/email-confirm
Email Confirmation and Fixes
2017-01-06 13:31:40 -03:00
Wyatt Johnson 4c5b85a2ae Fixed session bug 2017-01-05 19:02:06 -07:00
David Jay e510f99754 Adding style to suspension modal. 2017-01-05 20:51:40 -05:00
Wyatt Johnson 4eb7c72ae2 Merge branch 'master' into email-confirm 2017-01-05 18:45:06 -07:00
Wyatt Johnson cabe546ecf Added email send on new user if enabled for confirming 2017-01-05 17:19:38 -07:00
Wyatt Johnson be72ebf2a1 Added tests + model implementation. 2017-01-05 17:01:41 -07:00
Riley Davis 368b431893 pre-validate usernames on the fe 2017-01-05 15:26:42 -07:00
David Jay a468fe4c28 Adding language and e-mail input to suspend user modal. 2017-01-05 16:51:37 -05:00
gaba d9ea5977cd Unique displayName for the User. 2017-01-05 18:24:05 -03:00
David Jay 890ab810f3 Activating SuspendUserModal and updating language for clarity. 2017-01-05 14:50:21 -05:00
Riley Davis 7e7e8891b4 display errors when trying to sign up with a bad username 2017-01-05 12:47:39 -07:00
gaba ac6fba2286 Clean user state on logout. Allow flagging only if not banned. 2017-01-05 15:34:27 -03:00
David Jay 9ba0176b3c Seperating username and bio flags 2017-01-04 16:11:31 -05:00
David Jay 7a239c02ab Limiting to user actions. 2017-01-04 15:56:52 -05:00
David Jay 216b21251d Styling UserActions Component. 2017-01-04 15:33:41 -05:00
gaba ab825512b9 Adding missing translation. Not sure how to translate streams. 2017-01-04 16:46:19 -03:00
David Jay c1e3580ef7 Resolving error in moderation list. 2017-01-04 14:46:06 -05:00
David Jay 0a064c0e11 Merge branch 'master' of github.com:coralproject/talk into moderate-bio 2017-01-04 14:33:27 -05:00
Riley Davis ce46b4dbe0 Merge pull request #212 from coralproject/add-csurf-and-cookie
Moves the packages to dependencies.
2017-01-04 13:32:37 -06:00
gaba 864b08135a Moves the packages to dependencies. 2017-01-04 16:24:17 -03:00
David Jay 3fa0460f34 Sending actions to ModerationList. 2017-01-04 14:20:00 -05:00
Gabriela Rodríguez Berón 7d5be04be3 Merge pull request #210 from coralproject/init-settings
fetch settings on mod queue load
2017-01-04 15:52:32 -03:00
David Jay f792aeccd2 Adding user display. 2017-01-04 13:39:10 -05:00
Riley Davis 12f6bb1ffe fetch settings on mod queue load 2017-01-04 11:14:23 -07:00
Gabriela Rodríguez Berón 57f9fd8474 Merge pull request #204 from coralproject/csrf
CSRF
2017-01-04 14:32:12 -03:00
Wyatt Johnson 298e1e8d73 Updates to user cli + e2e + tests
- Updates to before + beforeEach for mongooose
- Removed reference to dotenv from cli in e2e, should use
  NODE_ENV=test instead.
- Changed test port from 30?? to 3000 to be consistent with what
  nightwatch was expecting
2017-01-04 10:16:51 -07:00
gaba 5f7d48a7f9 This was not needed. 2017-01-04 13:17:16 -03:00
gaba 3718efce1e Adds cookie parser 2017-01-04 12:47:19 -03:00
gaba ccb9fe3165 Adds DELETE and PUT. 2017-01-04 12:40:25 -03:00
gaba 7b9dbb5afd Review changes. 2017-01-04 12:24:01 -03:00
Gabriela Rodríguez Berón 2755cc61e4 Merge branch 'master' into csrf 2017-01-04 10:56:37 -03:00
Wyatt Johnson a7680ae5d9 Initial pass at email confirmation 2017-01-03 17:35:58 -07:00
David Jay b517adab56 Moving commentList to ModerationList 2017-01-03 16:57:53 -05:00
gaba 0fd6f4b44e Working on the tests & sending CSRF 2017-01-03 18:01:21 -03:00
David Jay 5c5a7f5ba5 Switching to central call for mod queue to support current reducer structure. 2017-01-03 15:13:09 -05:00
David Jay dd7bdb8a26 Adding actions for fetching user moderation queue. 2017-01-03 15:05:45 -05:00
David Jay 800382132d Merge branch 'master' of github.com:coralproject/talk into moderate-bio 2017-01-03 14:29:05 -05:00
gaba e2a032d4a5 Merge branch 'master' into csrf 2017-01-03 16:25:05 -03:00
Riley Davis 7363fcc5dc Merge pull request #205 from coralproject/bug_203
Fixes Bug #203
2017-01-03 12:47:49 -06:00
Riley Davis d4dc381aca Merge branch 'master' into bug_203 2017-01-03 12:29:48 -06:00
Gabriela Rodríguez Berón 6e54ba7f4c Merge pull request #209 from coralproject/admin-not-loading
make the admin screen load
2017-01-03 15:29:17 -03:00
gaba 950340d5e7 Fix lint. 2017-01-03 15:18:39 -03:00
Gabriela Rodríguez Berón 43846651dd Merge branch 'master' into bug_203 2017-01-03 14:33:57 -03:00
Riley Davis a1def6f950 missing state for the wordlist. 2017-01-03 10:33:25 -07:00
Gabriela Rodríguez Berón b7380978f5 Merge pull request #206 from coralproject/open-source-docs
Open source docs
2017-01-03 12:47:06 -03:00
David Erwin 5e4564c7ea Merge branch 'master' into open-source-docs 2017-01-03 10:29:41 -05:00
Gabriela Rodríguez Berón f5374dc914 Merge pull request #207 from coralproject/fix-test
Fix test on Suspected words
2017-01-03 11:45:30 -03:00
Wyatt Johnson 0dde122e9b Merge branch 'master' into fix-test 2017-01-03 07:41:44 -07:00
Gabriela Rodríguez Berón fd2b01d5ed Merge pull request #208 from coralproject/fix/dialog-unmount
Check if dialog is present before removing listeners
2017-01-03 11:40:31 -03:00
Dan Zajdband 759e74a88d coral-admin: Fixed check for component unmount removing a listener when there is no object 2017-01-03 10:46:55 -03:00
gaba 5c6df6d481 Fix tests when flagging because suspect word. 2017-01-03 09:37:02 -03:00
gaba a02ad69568 Adds specific fields to the metadata in the action when flagging because suspect word. 2017-01-03 09:36:20 -03:00
gaba 111e83a1e6 Removes plugin csrf 2017-01-03 08:41:28 -03:00
gaba 036ed8ac78 Removes wrong csrf. 2017-01-03 08:41:28 -03:00
David Erwin f786daaec9 Merged branch open-source-docs into open-source-docs 2016-12-27 11:50:42 -05:00
David Erwin 9a608fd02e Spell stuff right and clean up 2016-12-27 11:50:34 -05:00
David Erwin 1ca6736146 Merge branch 'master' into open-source-docs 2016-12-27 11:46:54 -05:00
David Erwin 349f8003a7 Merge branch 'master' into csrf 2016-12-27 11:46:42 -05:00
David Erwin c86148c0f1 Add links to INSTALL.md 2016-12-27 11:39:54 -05:00
David Erwin b0e4b79a6a Merge pull request #197 from coralproject/addINSTALL
Adds a very simple INSTALL file for developers.
2016-12-27 11:33:58 -05:00
David Erwin da37bb32f1 Updating for style and completeness 2016-12-27 11:28:01 -05:00
David Erwin 0219f1916e Link contributing.md from readme.md 2016-12-27 10:59:08 -05:00
David Erwin ef40cf37fe Update contribution guide 2016-12-27 10:55:16 -05:00
gaba d35875145c Changes field for metadata. Needs to look at this test better. 2016-12-23 15:38:45 -08:00
gaba 8e5e75ed6f It works for user signin/signup and posting comments. 2016-12-23 14:57:39 -08:00
gaba 6892ef85f8 Sending the CSRF to the POST request. 2016-12-23 13:56:42 -08:00
Kim Gardner 8b5b9e856b Merge branch 'master' into addINSTALL 2016-12-23 16:13:36 -05:00
Kim Gardner ac66a7bb0e Merge branch 'master' into csrf 2016-12-23 16:12:53 -05:00
Belén Curcio 6052cac1d3 Merge pull request #196 from coralproject/map-dispatch-mod-queue
create mapStateToProps in ModerationQueue
2016-12-23 18:12:32 -03:00
Kim Gardner 93c5e336b2 Merge branch 'master' into addINSTALL 2016-12-23 16:12:18 -05:00
gaba 8e6aa23d32 More CSRF in front-end. 2016-12-23 13:07:16 -08:00
Belen Curcio 8d992f7809 merg 2016-12-23 16:13:36 -03:00
Belen Curcio 168248f975 Presentational and Container Components added 2016-12-23 16:09:46 -03:00
Belén Curcio 964c1cb3d2 Linting 2016-12-23 12:06:40 -03:00
Belén Curcio 9766215231 Rename FRONTEND.md to README.md 2016-12-23 11:51:46 -03:00
Belén Curcio 3b1731e809 Update FRONTEND.md 2016-12-23 10:56:15 -03:00
Belén Curcio 1538690cad Update FRONTEND.md 2016-12-23 10:55:30 -03:00
Belén Curcio ca9ef44202 links 2016-12-23 10:52:32 -03:00
Belén Curcio 3dfa4969e5 Update IMMUTABLEJS.md 2016-12-23 10:50:16 -03:00
Belén Curcio 92a8fc5a93 links 2016-12-23 10:49:45 -03:00
Belén Curcio 018447f00e Update DEBUG.md 2016-12-23 10:43:59 -03:00
Belen Curcio b36109f0a1 Áddind debug 2016-12-23 10:39:03 -03:00
gaba ec3e9d1cd4 Merge branch 'master' into csrf 2016-12-22 14:23:01 -08:00
gaba 54d0096dd1 More CSRF in forms. 2016-12-22 14:17:09 -08:00
Riley Davis 6cca99e75e Merge branch 'master' into map-dispatch-mod-queue 2016-12-22 12:25:51 -07:00
Riley Davis 92b51ef1fe Merge branch 'master' into addINSTALL 2016-12-22 12:25:08 -07:00
gaba 39222759f9 Adds links between README and INSTALL. 2016-12-22 08:31:00 -08:00
gaba 075fe7f6b4 Merge branch 'master' into csrf 2016-12-21 16:03:59 -08:00
gaba 54bad908b3 Apply changes from review. 2016-12-21 16:01:16 -08:00
gaba 7f86c936ad Adds a very simple INSTALL file for devs. 2016-12-21 13:17:04 -08:00
Riley Davis 50112d3166 create mapStateToProps 2016-12-21 12:22:54 -07:00
gaba 7562edc168 Removes console log. 2016-12-21 08:52:39 -08:00
gaba 29250f2bbc Fixed unpromised rejection in test. 2016-12-20 18:01:28 -08:00
gaba 9c9597ae8c Persist the session in tests through chai agent. 2016-12-20 17:52:55 -08:00
gaba 16a4daaeb3 Merge branch 'master' into csrf 2016-12-20 13:46:08 -08:00
gaba e16bc8f7ad Adds agent to persist session in tests. 2016-12-20 12:18:30 -08:00
gaba f49ca9f89a PAss the token around. 2016-12-19 18:11:11 -08:00
David Jay dff8413404 Adding pending users and actions to moderation queue call. 2016-12-19 20:34:27 -05:00
David Jay 20505acfbe Updating tests for pending users endpoint. 2016-12-19 20:27:58 -05:00
David Jay 845d274b9b Creatind endpoint to return pending users and action summaries. 2016-12-19 19:54:20 -05:00
gaba 4172066c94 Silly commit. 2016-12-16 19:44:01 -08:00
gaba dd032521c0 Merge branch 'master' into csrf 2016-12-16 17:55:49 -08:00
gaba d8d30e512b Only on POST check for CSRF. 2016-12-16 16:02:45 -08:00
gaba b75408b067 Merge branch 'master' into csrf 2016-12-15 15:11:30 -08:00
gaba 670567b8b3 One more expect in the test. 2016-12-15 15:08:01 -08:00
gaba eb98a41bd5 Merge branch 'master' into csrf 2016-12-15 15:04:36 -08:00
gaba 7b8131d4b0 Use session instead of cookies. 2016-12-15 15:01:05 -08:00
gaba 4a76e43309 Merge branch 'master' into csrf 2016-12-15 14:25:08 -08:00
gaba 70c86bf8f5 Merge branch 'master' into csrf 2016-12-15 13:54:22 -08:00
gaba e76d6e4694 Merge branch 'master' into csrf 2016-12-15 13:23:18 -08:00
gaba 369ed3fc29 Adds csrf protection to some routes. 2016-12-15 13:12:03 -08:00
gaba fc042779c2 Adds csurf to and cookie-parser to manage CRSF protection. 2016-12-15 12:56:34 -08:00
417 changed files with 23854 additions and 7079 deletions
+2
View File
@@ -1 +1,3 @@
node_modules
test
.git
+1 -1
View File
@@ -17,7 +17,7 @@
"no-template-curly-in-string": [1],
"no-unsafe-negation": [1],
"array-callback-return": [1],
"eqeqeq": [2],
"eqeqeq": [2, "smart"],
"no-eval": [2],
"no-global-assign": [2],
"no-implied-eval": [2],
+3 -2
View File
@@ -3,11 +3,12 @@ npm-debug.log*
dist
!dist/coral-admin
dist/coral-admin/bundle.js
tests/e2e/reports
test/e2e/reports
.DS_Store
*.iml
*.swp
dump.rdb
.env
gaba.cfg
*.cfg
.idea/
coverage/
+5
View File
@@ -0,0 +1,5 @@
{
"verbose": true,
"ignore": ["test/*", "client/*", "dist/*"],
"ext": "js,json,graphql"
}
-3
View File
@@ -1,3 +0,0 @@
language: node_js
node_js:
- 4
+64 -83
View File
@@ -1,95 +1,76 @@
# Contribution Guide
# Contributor's Guide
We're very excited that you're interested in contributing to Talk! There is much to do. Before you begin, please review this document to get a sense of the practices and philosophies that hold this project together.
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
You can view what the Coral Team is working on next here https://www.pivotaltracker.com/n/projects/1863625.
You can view product ideas and our longer term roadmap here https://trello.com/b/ILND751a/talk.
## Doing the Work
## Contribute to the documentation
Clear docs are a prerequisite for a successful open source project. We value non-code and code contributions equally.
We are here to make it as seamless as possible to contribute to Talk. The following lists are meant to make it straightforward to perform the mechanics of working on the project so you can focus your energy toward writing and reviewing content.
We are looking for _documentarians_ to:
* make clarity, grammar and completeness updates,
* create new / missing sections, and
* take the lead in making sections, or the over all structure better.
### But how?
* Our public docs site can be updated [here](https://github.com/coralproject/docs).
* [Let us know](https://github.com/coralproject/talk/wiki/Contact-Us) if you'd like permission to update our wiki.
* Update any of our .md docs files by following [this guide](https://github.com/coralproject/talk/wiki/Forking,-Branching-and-Merging).
* Our API docs need to be kept honest. [Update them here](https://github.com/coralproject/talk/blob/master/docs/swagger.yaml).
## Integrate into your environment
First, [set up a dev environment](https://github.com/coralproject/talk/blob/master/INSTALL.md). Please let us know how this goes!
Talk is designed to integrate into existing environments in a variety of ways:
* [Auth integrations](https://github.com/coralproject/talk/wiki/Security#authentication-strategies)
* [Push assets into Talk](https://github.com/coralproject/talk/blob/master/routes/api/assets/index.js)
* Monitoring Hooks (coming in 2017)
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.
## Write some code
First, [set up a dev environment](https://github.com/coralproject/talk/blob/master/INSTALL.md). Please let us know how this goes!
### Build a New Feature / Plugin
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.
To get an idea of where the Coral Team is going, see:
* our [product/design Trello board](https://trello.com/b/ILND751a/talk),
* our [current stories](https://www.pivotaltracker.com/n/projects/1863625), and
* our [issues](https://github.com/coralproject/talk/issues).
### Code Reviews
Examples:
One of the most valuable aspects of working in software. It is something that should challenge the reviewer and author alike. It is a way of focusing knowledge, experience and opinions for the benefit of the project and the participants.
Code reviews are a collaboration to make _the work_ as good as it can be. Code reviews are not a good venue for providing direct instruction to _the author._ Focus on positive, incremental improvements that can be made on the work at hand.
Please take your time when writing and reviewing code. Here are some fundamental questions to open up a reviewing headspace.
**Is the code clear, efficient and a pleasure to read?**
Somewhere at the intersection of good variable names, well laid out file structures, consistent formatting and appropriate comments lies beautiful code. Code is language spoken to at least two very distinct audiences, the computer that interprets it and the developer who encounters it. Both should be at the front of your mind when reviewing code.
Thinking like a computer, you could ask:
* Is the code using memory efficiently?
* Is data being moved around unnecessarily?
* Are multiple network requests being made where fewer would do?
* Is there excess processing happening in a synchronous flow that may disrupt user experience?
* Are there large libraries included for small gains?
Then, returning to your human roots... Is the code readable?
* Can I understand what is happening here (and maybe even why) by simply opening up the file, starting at the top and reading downward?
* Do comments convey clear, full thoughts in a narrative language that provides background for the code choices?
* Are the files separated logically such that each one contains a clear concept of code?
* [Add An Emoji Button to Comments](https://github.com/coralproject/talk/wiki/Add-An-Emoji-Button-to-Comments)
**Is the API documentation up to date? Are all client calls written against the docs?**
### Work on the Core
We use [swagger](https://github.com/coralproject/talk/blob/master/swagger.yaml) to track our API documentation.
There is always more work to be done to make an application more stable, scaleable and secure.
* If APIs are created or updated, is the swagger.yml file up to date? There's nothing more frustrating than trying to develop against docs that are out of date or wrong. We need to be meticulous here as it's the little differences that can cause the most frustration and tricky bugs.
* If client code calls APIs, are they written against the swagger.yml file? Are all return codes handled?
If you see issues in the code or have ideas on how we may improve Talk, please consider:
**Is there sufficient test coverage?**
Our tests folder is set up to mirror the code folders: [https://github.com/coralproject/talk/tree/master/tests](https://github.com/coralproject/talk/tree/master/tests)
* Can you a sense of the logic behind the code by reading the tests?
* Can you see both what should happen and what should _never, ever_ be allowed to happen?
* Are there future cases that are guarded against via the creation of unit tests (aka, making sure things are typed, specifically checking for all values that will be used, etc...)?
### Forking, Branching and Merging
Talk follows the _master as tip_ repo structure. `master` is the bleeding edge. It should be _as stable as possible_ but may suffer instabilities, generally during times that fundamental architectural elements are added.
Releases are _tagged_ off the master branch.
Contributions to Talk follow this process. There are a lot of steps, but mechanically following these steps will standardize communication, help stop errors and let you focus on your contribution.
* At the outset of a piece of work, a branch or fork is made from master.
* The work is done in that fork.
* As soon as the work has taken shape, a PR is created for discussion. (If the PR is created for review before it's ready to merge, please make that clear in the description/title.)
* At least one other contributor to the project must review all code (see Code Reviews below.)
* If there are merge conflicts with master, merge master into the branch.
* Ensure that [circleci](https://circleci.com/) passes all tests for your branch. (If you have forked and do not have circleci set up, you and the reviewer should independently ensure that all the of Continuous Integration steps pass before merging.)
* If merge conflicts exist with `master`, merge `master` into your branch and re-run CI before merging into master.
* Merge to master, but _you're not quite done yet!_
* Deploy master to staging (or have a core member do so.)
* Ensure that all your changes are working on staging.
* Have your reviewer verify the same.
* ... aaaand the work is delivered!
## Continuous Integration
We use circleci to run our ci: [https://circleci.com/gh/coralproject/talk](https://circleci.com/gh/coralproject/talk)
Our pipeline will _test_, _lint_, and _build_ all pushes to the repo.
Any branch not passing CI will not be merged into master.
If you're working in a fork, please run each of the steps locally before submitting a PR.
## Coding Style
### API Design
When building APIs, we follow these principles:
* Follow [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) principles for basic operations.
* Avoid routing yourself into a corner, for example, by putting a variable other than an object's id directly after an object.
* Put non-required, flexible variables into query params, required/identity based values in request params.
* [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" ]
+94
View File
@@ -0,0 +1,94 @@
# Installation
## Requirements
### System
- Any flavour of Linux, OSX or Windows
- 1GB memory (minimum)
- 5GB storage (minimum)
## Installation From Source
### 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!_
### Installing
```bash
# Download the tarball containing the repository
curl -L https://github.com/coralproject/talk/tarball/master -o coralproject-talk.tar.gz
# Untar that file and change to that directory
tar xpf coralproject-talk.tar.gz
mv coralproject-talk-* coralproject-talk
cd coralproject-talk
# Install package dependancies
yarn
# Build static files
yarn build
```
### Running
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:
```bash
yarn start
```
You can see other scripts we've made available by consulting the `package.json`
file under the `scripts` key including:
- `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
## Installation From Docker Hub
### Requirements
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
```
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
```
+25 -17
View File
@@ -1,38 +1,46 @@
# 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
### Product Roadmap
You can view what the Coral Team is working on next here https://www.pivotaltracker.com/n/projects/1863625.
You can view product ideas and our longer term roadmap here https://trello.com/b/ILND751a/talk.
See our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md)!
## Usage
### Installation
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!
### Configuration
The Talk application requires specific configuration options to be available
inside the environment in order to run, those variables are listed here:
The Talk application looks for the following configuration values either as environment variables:
- `TALK_MONGO_URL` (*required*) - the database connection string for the MongoDB database.
- `TALK_REDIS_URL` (*required*) - the database connection string for the Redis database.
- `TALK_SESSION_SECRET` (*required*) - a random string which will be used to
secure cookies.
- `TALK_FACEBOOK_APP_ID` (*required*) - the Facebook app id for your Facebook
Login enabled app.
- `TALK_FACEBOOK_APP_SECRET` (*required*) - the Facebook app secret for your
Facebook Login enabled app.
- `TALK_ROOT_URL` (*required*) - root url of the installed application externally
available in the format: `<scheme>://<host>` without the path.
- `TALK_SMTP_EMAIL` (*required*) - the address to send emails from using the
- `TALK_FACEBOOK_APP_ID` (*required for login via fb*) - the Facebook app id for your Facebook
Login enabled app.
- `TALK_FACEBOOK_APP_SECRET` (*required for login via fb*) - the Facebook app secret for your
Facebook Login enabled app.
- `TALK_SMTP_EMAIL` (*required for email*) - the address to send emails from using the
SMTP provider.
- `TALK_SMTP_USERNAME` (*required*) - username of the SMTP provider you are using.
- `TALK_SMTP_PASSWORD` (*required*) - password for the SMTP provider you are using.
- `TALK_SMTP_HOST` (*required*) - SMTP host url with format `smtp.domain.com`.
- `TALK_SMTP_PORT` (*required*) - SMTP port.
- `TALK_SMTP_USERNAME` (*required for email*) - username of the SMTP provider you are using.
- `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.
### License
+76 -18
View File
@@ -5,8 +5,13 @@ const path = require('path');
const helmet = require('helmet');
const passport = require('./services/passport');
const session = require('express-session');
const enabled = require('debug').enabled;
const RedisStore = require('connect-redis')(session);
const redis = require('./services/redis');
const csrf = require('csurf');
const errors = require('./errors');
const graph = require('./graph');
const apollo = require('graphql-server-express');
const app = express();
@@ -42,10 +47,11 @@ const session_opts = {
rolling: true,
saveUninitialized: false,
resave: false,
unset: 'destroy',
name: 'talk.sid',
cookie: {
secure: false,
maxAge: 18000000, // 30 minutes for expiry.
maxAge: 36000000, // 1 hour for expiry.
},
store: new RedisStore({
ttl: 1800,
@@ -73,6 +79,51 @@ app.use(session(session_opts));
app.use(passport.initialize());
app.use(passport.session());
//==============================================================================
// GraphQL Router
//==============================================================================
// GraphQL endpoint.
app.use('/api/v1/graph/ql', apollo.graphqlExpress(graph.createGraphOptions));
// Only include the graphiql tool if we aren't in production mode.
if (app.get('env') !== 'production') {
// Interactive graphiql interface.
app.use('/api/v1/graph/iql', apollo.graphiqlExpress({
endpointURL: '/api/v1/graph/ql'
}));
// GraphQL documention.
app.get('/admin/docs', (req, res) => {
res.render('admin/docs');
});
}
//==============================================================================
// CSRF MIDDLEWARE
//==============================================================================
if (process.env.TEST_MODE === 'unit') {
// Add this fake test token in the event we are in unit test mode, and don't
// include the CSRF protection.
app.locals.csrfToken = 'UNIT_TESTS';
} else {
// Setup route middlewares for CSRF protection.
// Default ignore methods are GET, HEAD, OPTIONS
app.use(csrf({}));
app.use((req, res, next) => {
res.locals.csrfToken = req.csrfToken();
next();
});
}
//==============================================================================
// ROUTES
//==============================================================================
@@ -83,40 +134,47 @@ app.use('/', require('./routes'));
// ERROR HANDLING
//==============================================================================
const ErrNotFound = new Error('Not Found');
ErrNotFound.status = 404;
// Catch 404 and forward to error handler.
app.use((req, res, next) => {
next(ErrNotFound);
next(errors.ErrNotFound);
});
// General error handler. Respond with the message and error if we have it while
// returning a status code that makes sense.
app.use('/api', (err, req, res, next) => {
if (err !== ErrNotFound) {
if (app.get('env') !== 'test') {
if (err !== errors.ErrNotFound) {
if (app.get('env') !== 'test' || enabled('talk:errors')) {
console.error(err);
}
}
res.status(err.status || 500);
res.json({
message: err.message,
error: app.get('env') === 'development' ? err : {}
});
if (err instanceof errors.APIError) {
res.status(err.status).json({
message: err.message,
error: err
});
} else {
res.status(500).json({});
}
});
app.use('/', (err, req, res, next) => {
if (err !== ErrNotFound) {
if (err !== errors.ErrNotFound) {
console.error(err);
}
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: app.get('env') === 'development' ? err : {}
});
if (err instanceof errors.APIError) {
res.status(err.status);
res.render('error', {
message: err.message,
error: app.get('env') === 'development' ? err : {}
});
} else {
res.render('error', {
message: err.message,
error: app.get('env') === 'development' ? err : {}
});
}
});
module.exports = app;
+1 -1
View File
@@ -29,5 +29,5 @@
"as": "POSTMARK"
}],
"image": "heroku/nodejs",
"success_url": "/admin/setup"
"success_url": "/admin/install"
}
+14 -17
View File
@@ -1,31 +1,28 @@
#!/usr/bin/env node
// 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
// entrypoint for the entire application.
require('env-rewrite').rewrite();
/**
* Module dependencies.
*/
const program = require('commander');
const pkg = require('../package.json');
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
// const util = require('./util');
const program = require('./commander');
program
.version(pkg.version)
.command('serve', 'serve the application')
.command('assets', 'interact with assets')
.command('settings', 'work with the application settings')
.command('setup', 'setup the application')
.command('jobs', 'work with the job queues')
.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();
}
/**
* 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');
}
});
+5 -9
View File
@@ -4,14 +4,13 @@
* Module dependencies.
*/
const program = require('commander');
const pkg = require('../package.json');
const program = require('./commander');
const parseDuration = require('parse-duration');
const Table = require('cli-table');
const Asset = require('../models/asset');
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([
@@ -22,7 +21,7 @@ util.onshutdown([
* Lists all the assets registered in the database.
*/
function listAssets() {
Asset
AssetModel
.find({})
.sort({'created_at': 1})
.then((asset) => {
@@ -56,7 +55,7 @@ function refreshAssets(ageString) {
const ageMs = parseDuration(ageString);
const age = new Date(now - ageMs);
Asset.find({
AssetModel.find({
$or: [
{
scraped: {
@@ -86,9 +85,6 @@ function refreshAssets(ageString) {
// Setting up the program command line arguments.
//==============================================================================
program
.version(pkg.version);
program
.command('list')
.description('list all the assets in the database')
+8 -4
View File
@@ -4,9 +4,10 @@
* Module dependencies.
*/
const program = require('commander');
const program = require('./commander');
const scraper = require('../services/scraper');
const util = require('../util');
const mailer = require('../services/mailer');
const util = require('./util');
const mongoose = require('../services/mongoose');
const kue = require('../services/kue');
@@ -19,13 +20,16 @@ util.onshutdown([
*/
function processJobs() {
// Start the processor.
// Start the scraper processor.
scraper.process();
// Start the mail processor.
mailer.process();
// The scraper only needs to shutdown when the scraper has actually been
// started.
util.onshutdown([
() => scraper.shutdown()
() => kue.Task.shutdown()
]);
}
+17 -25
View File
@@ -1,18 +1,19 @@
#!/usr/bin/env node
const app = require('../app');
const debug = require('debug')('talk:server');
const program = require('./commander');
const http = require('http');
const init = require('../init');
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.
*/
const port = normalizePort(process.env.TALK_PORT || (process.env.NODE_ENV === 'test' ? '3011' : '3000'));
const port = normalizePort(process.env.TALK_PORT || '3000');
app.set('port', port);
@@ -77,34 +78,22 @@ function onListening() {
let bind = typeof addr === 'string'
? `pipe ${ addr}`
: `port ${ addr.port}`;
debug(`Listening on ${ bind}`);
console.log(`Listening on ${ bind}`);
}
/**
* Start the app.
*/
function startApp() {
init().then(() => {
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
})
.catch((err) => {
console.error(err);
util.shutdown(1);
});
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
}
/**
* Module dependencies.
*/
const program = require('commander');
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
@@ -119,15 +108,18 @@ startApp();
// Enable job processing on the thread if enabled.
if (program.jobs) {
// Start the processor.
// Start the scraper processor.
scraper.process();
// Start the mail processor.
mailer.process();
}
// Define a safe shutdown function to call in the event we need to shutdown
// because the node hooks are below which will interrupt the shutdown process.
// Shutdown the mongoose connection, the app server, and the scraper.
util.onshutdown([
() => program.jobs ? scraper.shutdown() : null,
() => program.jobs ? kue.Task.shutdown() : null,
() => mongoose.disconnect(),
() => server.close()
]);
-45
View File
@@ -1,45 +0,0 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
const program = require('commander');
const mongoose = require('../services/mongoose');
const Setting = require('../models/setting');
const util = require('../util');
// Register the shutdown criteria.
util.onshutdown([
() => mongoose.disconnect()
]);
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
program
.command('init')
.description('initilizes the talk settings')
.action(() => {
const defaults = {id: '1', moderation: 'pre'};
Setting
.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true})
.then(() => {
console.log('Created settings object.');
util.shutdown();
})
.catch((err) => {
console.error(`failed to create the settings object ${JSON.stringify(err)}`);
util.shutdown(1);
});
});
program.parse(process.argv);
// If there is no command listed, output help.
if (!process.argv.slice(2).length) {
program.outputHelp();
util.shutdown();
}
Executable
+197
View File
@@ -0,0 +1,197 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
const program = require('./commander');
const inquirer = require('inquirer');
const mongoose = require('../services/mongoose');
const SettingModel = require('../models/setting');
const SettingsService = require('../services/settings');
const SetupService = require('../services/setup');
const UsersService = require('../services/users');
const util = require('./util');
const errors = require('../errors');
// Register the shutdown criteria.
util.onshutdown([
() => mongoose.disconnect()
]);
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
program
.description('runs the setup wizard to setup the application')
.option('--defaults', 'apply defaults for config instead of prompting')
.parse(process.argv);
//==============================================================================
// Setup the application
//==============================================================================
const performSetup = () => {
if (program.defaults) {
return SettingsService
.init()
.then(() => {
console.log('Settings created.');
console.log('\nTalk is now installed!');
util.shutdown();
})
.catch((err) => {
console.error(err);
util.shutdown(1);
});
}
// 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);
});
};
// Start tthe setup process.
performSetup();
+155 -116
View File
@@ -4,101 +4,139 @@
* Module dependencies.
*/
const program = require('commander');
const pkg = require('../package.json');
const prompt = require('prompt');
const User = require('../models/user');
const program = require('./commander');
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) => {
if (input && input.length >= len) {
return true;
}
return msg;
};
// Regeister the shutdown criteria.
util.onshutdown([
() => mongoose.disconnect()
]);
function getUserCreateAnswers(options) {
if (options.flag_mode) {
let user = {
email: options.email,
password: options.password,
confirmPassword: options.password,
username: options.name,
roles: []
};
if (options.role && UserModel.USER_ROLES.indexOf(options.role) > -1) {
user.roles = [options.role];
}
return Promise.resolve(user);
}
return inquirer.prompt([
{
name: 'email',
message: 'Email',
format: 'email',
validate: validateRequired('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;
});
}
},
{
name: 'username',
message: 'Username',
filter: (username) => {
return UsersService
.isValidUsername(username)
.catch((err) => {
throw err.message;
});
}
},
{
name: 'roles',
message: 'User Role',
type: 'checkbox',
choices: UserModel.USER_ROLES
}
]);
}
/**
* Prompts for input and registers a user based on those.
*/
function createUser(options) {
return new Promise((resolve, reject) => {
if (options.flag_mode) {
return resolve({
email: options.email,
password: options.password,
displayName: options.name,
role: options.role
});
}
prompt.start();
prompt.get([
{
name: 'email',
description: 'Email',
format: 'email',
required: true
},
{
name: 'password',
description: 'Password',
hidden: true,
required: true
},
{
name: 'confirmPassword',
description: 'Confirm Password',
hidden: true,
required: true
},
{
name: 'displayName',
description: 'Display Name',
required: true
},
{
name: 'role',
description: 'User Role',
required: false
}
], (err, result) => {
if (err) {
return reject(err);
getUserCreateAnswers(options)
.then((answers) => {
if (answers.password !== answers.confirmPassword) {
return Promise.reject(new Error('Passwords do not match'));
}
if (result.password !== result.confirmPassword) {
return reject(new Error('Passwords do not match'));
}
return answers;
})
.then((answers) => {
return UsersService
.createLocalUser(answers.email.trim(), answers.password.trim(), answers.username.trim())
.then((user) => {
console.log(`Created user ${user.id}.`);
resolve(result);
});
})
.then((result) => {
return User.createLocalUser(result.email.trim(), result.password.trim(), result.displayName.trim())
.then((user) => {
console.log(`Created user ${user.id}.`);
return User
.addRoleToUser(user.id, result.role.trim())
.then(() => {
console.log(`Added the admin ${result.role.trim()} to User ${user.id}.`);
util.shutdown();
if (answers.roles.length > 0) {
return Promise.all(answers.roles.map((role) => {
return UsersService
.addRoleToUser(user.id, role)
.then(() => {
console.log(`Added the role ${role} to User ${user.id}.`);
});
}));
}
});
})
.then(() => {
util.shutdown();
})
.catch((err) => {
console.error(err);
util.shutdown();
});
});
}
/**
* Deletes a user.
*/
function deleteUser(userID) {
User
UserModel
.findOneAndRemove({
id: userID
})
@@ -116,44 +154,34 @@ function deleteUser(userID) {
* Changes the password for a user.
*/
function passwd(userID) {
prompt.start();
prompt.get([
inquirer.prompt([
{
name: 'password',
description: 'Password',
hidden: true,
required: true
message: 'Password',
type: 'password',
validate: validateRequired('Password is required')
},
{
name: 'confirmPassword',
description: 'Confirm Password',
hidden: true,
required: true
message: 'Confirm Password',
type: 'password',
validate: validateRequired('Confirm Password is required')
}
], (err, result) => {
if (err) {
console.error(err);
util.shutdown();
return;
])
.then((answers) => {
if (answers.password !== answers.confirmPassword) {
return Promise.reject(new Error('Password mismatch'));
}
if (result.password !== result.confirmPassword) {
console.error(new Error('Password mismatch'));
util.shutdown(1);
return;
}
User
.changePassword(userID, result.password)
.then(() => {
console.log('Password changed.');
util.shutdown();
})
.catch((err) => {
console.error(err);
util.shutdown(1);
});
return UsersService.changePassword(userID, answers.password);
})
.then(() => {
console.log('Password changed.');
util.shutdown();
})
.catch((err) => {
console.error(err);
util.shutdown(1);
});
}
@@ -164,7 +192,7 @@ function updateUser(userID, options) {
const updates = [];
if (options.email && typeof options.email === 'string' && options.email.length > 0) {
let q = User.update({
let q = UserModel.update({
'id': userID,
'profiles.provider': 'local'
}, {
@@ -177,11 +205,11 @@ function updateUser(userID, options) {
}
if (options.name && typeof options.name === 'string' && options.name.length > 0) {
let q = User.update({
let q = UserModel.update({
'id': userID
}, {
$set: {
displayName: options.name
username: options.name
}
});
@@ -204,13 +232,13 @@ function updateUser(userID, options) {
* Lists all the users registered in the database.
*/
function listUsers() {
User
UsersService
.all()
.then((users) => {
let table = new Table({
head: [
'ID',
'Display Name',
'Username',
'Profiles',
'Roles',
'Status',
@@ -221,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,
@@ -244,7 +272,7 @@ function listUsers() {
* @param {String} srcUserID id of the user to which is the source of the merge
*/
function mergeUsers(dstUserID, srcUserID) {
User
UsersService
.mergeUsers(dstUserID, srcUserID)
.then(() => {
console.log(`User ${srcUserID} was merged into user ${dstUserID}.`);
@@ -262,7 +290,14 @@ function mergeUsers(dstUserID, srcUserID) {
* @param {String} role the role to add
*/
function addRole(userID, role) {
User
if (UserModel.USER_ROLES.indexOf(role) === -1) {
console.error(`Role '${role}' is not supported. Supported roles are ${UserModel.USER_ROLES.join(', ')}.`);
util.shutdown(1);
return;
}
UsersService
.addRoleToUser(userID, role)
.then(() => {
console.log(`Added the ${role} role to User ${userID}.`);
@@ -280,7 +315,14 @@ function addRole(userID, role) {
* @param {String} role the role to remove
*/
function removeRole(userID, role) {
User
if (UserModel.USER_ROLES.indexOf(role) === -1) {
console.error(`Role '${role}' is not supported. Supported roles are ${UserModel.USER_ROLES.join(', ')}.`);
util.shutdown(1);
return;
}
UsersService
.removeRoleFromUser(userID, role)
.then(() => {
console.log(`Removed the ${role} role from User ${userID}.`);
@@ -297,8 +339,8 @@ function removeRole(userID, role) {
* @param {String} userID id of the user to ban
*/
function ban(userID) {
User
.setStatus(userID, 'banned', '')
UsersService
.setStatus(userID, 'BANNED')
.then(() => {
console.log(`Banned the User ${userID}.`);
util.shutdown();
@@ -314,8 +356,8 @@ function ban(userID) {
* @param {String} userUD id of the user to remove the role from
*/
function unban(userID) {
User
.setStatus(userID, 'active', '')
UsersService
.setStatus(userID, 'ACTIVE')
.then(() => {
console.log(`Unban the User ${userID}.`);
util.shutdown();
@@ -331,7 +373,7 @@ function unban(userID) {
* @param {String} userID the ID of a user to disable
*/
function disableUser(userID) {
User
UsersService
.disableUser(userID)
.then(() => {
console.log(`User ${userID} was disabled.`);
@@ -348,7 +390,7 @@ function disableUser(userID) {
* @param {String} userID the ID of a user to enable
*/
function enableUser(userID) {
User
UsersService
.enableUser(userID)
.then(() => {
console.log(`User ${userID} was enabled.`);
@@ -364,9 +406,6 @@ function enableUser(userID) {
// Setting up the program command line arguments.
//==============================================================================
program
.version(pkg.version);
program
.command('create')
.option('--email [email]', 'Email to use')
+56
View File
@@ -0,0 +1,56 @@
const pkg = require('../package.json');
const dotenv = require('dotenv');
const fs = require('fs');
const program = require('commander');
// 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
// entrypoint for the entire application.
require('env-rewrite').rewrite();
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
const parseArgs = require('minimist')(process.argv.slice(2), {
alias: {
'c': 'config'
},
string: [
'config',
'pid'
],
default: {
'config': null,
'pid': null
}
});
/**
* 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'}));
Object.keys(envConfig).forEach((k) => {
process.env[k] = envConfig[k];
});
}
/**
* 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);
}
module.exports = program
.version(pkg.version)
.option('-c, --config [path]', 'Specify the configuration file to load')
.option('--pid [path]', 'Specify a path to output the current PID to');
+94
View File
@@ -0,0 +1,94 @@
const debug = require('debug')('talk:util');
const fs = require('fs');
const util = module.exports = {};
/**
* Stores an array of functions that should be executed in the event that the
* application needs to shutdown.
* @type {Array}
*/
util.toshutdown = [];
/**
* Calls all the shutdown functions and then ends the process.
* @param {Number} [defaultCode=0] default return code upon sucesfull shutdown.
*/
util.shutdown = (defaultCode = 0, signal = null) => {
if (signal) {
debug(`Reached ${signal} signal`);
}
debug(`${util.toshutdown.length} jobs now being called`);
Promise
.all(util.toshutdown.map((func) => func ? func(signal) : null).filter((func) => func))
.then(() => {
debug('Shutdown complete, now exiting');
process.exit(defaultCode);
})
.catch((err) => {
console.error(err);
process.exit(1);
});
};
/**
* Waits until an event is triggered by the node runtime and elevates a series
* of jobs to be ran in the event we need to shutdown.
* @param {Array} jobs Array of promise capable shutdown functions that are
* executed.
*/
util.onshutdown = (jobs) => {
debug(`${jobs.length} jobs registered to be called during shutdown`);
// Add the new jobs to shutdown to the object reference.
util.toshutdown = util.toshutdown.concat(jobs);
};
/**
* Register a PID file to be maintained for the lifespan of the process.
* @param {String} path path to the PID file to create
*/
util.pid = (path) => {
if (!/\//.test(path)) {
if (!/\.pid/.test(path)) {
path += '.pid';
}
path = `/tmp/${path}`;
}
const pid = `${process.pid.toString()}\n`;
fs.writeFile(path, pid, (err) => {
if (err) {
console.error(`Can't write PID file: ${err}`);
throw err;
}
// Add the cleanup for the fs onto the shutdown.
util.onshutdown([
() => new Promise((resolve, reject) => {
// Remove the pid file.
fs.unlink(path, (err) => {
if (err) {
return reject(err);
}
return resolve();
});
})
]);
});
};
// Attach to the SIGTERM + SIGINT handles to ensure a clean shutdown in the
// event that we have an external event. SIGUSR2 is called when the app is asked
// to be 'killed', same procedure here.
process.on('SIGTERM', () => util.shutdown(0, 'SIGTERM'));
process.on('SIGINT', () => util.shutdown(0, 'SIGINT'));
process.once('SIGUSR2', () => util.shutdown(0, 'SIGUSR2'));
+37 -4
View File
@@ -3,16 +3,49 @@ machine:
version: 7
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.
- yarn lint
database:
post:
# Initialize the settings in the database, this will create indicies for the
# database.
- ./bin/cli setup --defaults
- sleep 2
test:
override:
- MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml ./node_modules/.bin/mocha tests --reporter mocha-junit-reporter
- npm run lint
- npm run build
# Run the tests using the junit reporter.
- 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 ModerationQueue from 'containers/ModerationQueue/ModerationQueue';
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={ModerationQueue} />
<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});
};
+8 -5
View File
@@ -9,12 +9,15 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
export const checkLogin = () => dispatch => {
dispatch(checkLoginRequest());
coralApi('/auth')
.then(user => {
const isAdmin = !!user.roles.filter(i => i === 'admin').length;
dispatch(checkLoginSuccess(user, isAdmin));
return coralApi('/auth')
.then(result => {
const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length;
dispatch(checkLoginSuccess(result.user, isAdmin));
})
.catch(error => dispatch(checkLoginFailure(error)));
.catch(error => {
console.error(error);
dispatch(checkLoginFailure(`${error.translation_key}`));
});
};
// LogOut Actions
@@ -1,71 +0,0 @@
import coralApi from '../../../coral-framework/helpers/response';
import * as commentTypes from '../constants/comments';
import * as actionTypes from '../constants/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('/comments?status=rejected'),
coralApi('/comments?action_type=flag')
])
.then(([pending, rejected, flagged]) => {
/* Combine seperate calls into a single object */
flagged.comments.forEach(comment => comment.flagged = true);
return {
comments: [...pending.comments, ...rejected.comments, ...flagged.comments],
users: [...pending.users, ...rejected.users, ...flagged.users],
actions: [...pending.actions, ...rejected.actions, ...flagged.actions]
};
})
.then(({comments, users, actions}) => {
/* Post comments and users to redux store. Actions will be posted when they are needed. */
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});
});
};
};
// Create a new comment
export const createComment = (name, body) => {
return dispatch => {
const comment = {body, name};
return coralApi('/comments', {method: 'POST', comment})
.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};
};
+3 -2
View File
@@ -41,14 +41,15 @@ export const newPage = () => ({
type: COMMENTERS_NEW_PAGE
});
export const setRole = (id, role) => dispatch => {
export const setRole = (id, role) => (dispatch) => {
return coralApi(`/users/${id}/role`, {method: 'POST', body: {role}})
.then(() => {
return dispatch({type: SET_ROLE, id, role});
});
};
export const setCommenterStatus = (id, status) => dispatch => {
export const setCommenterStatus = (id, status) => (dispatch) => {
return coralApi(`/users/${id}/status`, {method: 'POST', body: {status}})
.then(() => {
return dispatch({type: SET_COMMENTER_STATUS, id, status});
+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);
}
+21 -5
View File
@@ -1,15 +1,31 @@
import coralApi from '../../../coral-framework/helpers/response';
import * as actions from '../constants/user';
import * as userTypes from '../constants/users';
/**
* Action disptacher related to users
*/
// change status of a user
export const userStatusUpdate = (status, userId, commentId) => {
return dispatch => {
dispatch({type: actions.UPDATE_STATUS_REQUEST});
return (dispatch) => {
dispatch({type: userTypes.UPDATE_STATUS_REQUEST});
return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
.then(res => dispatch({type: actions.UPDATE_STATUS_SUCCESS, res}))
.catch(error => dispatch({type: actions.UPDATE_STATUS_FAILURE, error}));
.then(res => dispatch({type: userTypes.UPDATE_STATUS_SUCCESS, res}))
.catch(error => dispatch({type: userTypes.UPDATE_STATUS_FAILURE, error}));
};
};
// change status of a user
export const sendNotificationEmail = (userId, subject, body) => {
return (dispatch) => {
return coralApi(`/users/${userId}/email`, {method: 'POST', body: {subject, body}})
.catch(error => dispatch({type: userTypes.USER_EMAIL_FAILURE, error}));
};
};
// 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}));
};
};
@@ -0,0 +1,22 @@
import React from 'react';
import styles from './ModerationList.css';
import BanUserButton from './BanUserButton';
import {FabButton} from 'coral-ui';
import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap';
const ActionButton = ({type = '', user, ...props}) => {
if (type === 'BAN') {
return <BanUserButton user={user} onClick={() => props.showBanUserDialog(props.user, props.id)} />;
}
return (
<FabButton
className={`${type.toLowerCase()} ${styles.actionButton}`}
cStyle={type.toLowerCase()}
icon={menuActionsMap[type].icon}
onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment}
/>
);
};
export default ActionButton;
@@ -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;
@@ -1,30 +1,38 @@
.dialog {
border: none;
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
width: 280px;
top: 10px;
border: none;
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
width: 500px;
top: 50%;
transform: translateY(-50%);
height: 184px;
padding: 20px;
h2 {
color: black;
font-size: 1.76em;
font-weight: 500;
margin: 0;
}
h3 {
color: black;
font-size: 1.4em;
font-weight: 500;
margin: 0;
}
}
.header {
margin-bottom: 20px;
}
.header h1, .separator h1{
text-align: center;
font-size: 1.2em;
}
.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;
@@ -143,5 +151,14 @@ input.error{
}
.cancel {
margin: 10px 0;
margin-right: 10px;
width: 47%;
}
.ban {
width: 47%;
}
.buttons {
margin: 20px 0;
}
@@ -1,45 +1,46 @@
import React from 'react';
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import Button from 'coral-ui/components/Button';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
const BanUserDialog = ({open, handleClose, onClickBanUser, user = {}}) => {
const {userName = '', userId = '', commentId = ''} = user;
return (
<Dialog className={styles.dialog} open={open} onClose={() => handleClose()} onCancel={() => handleClose()} title={lang.t('bandialog.ban_user')}>
<span className={styles.close} onClick={() => handleClose()}>×</span>
<div>
<div className={styles.header}>
<h3>
{lang.t('bandialog.ban_user')}
</h3>
</div>
<div className={styles.separator}>
<h4>
{lang.t('bandialog.are_you_sure', userName)}
</h4>
<i>
{lang.t('bandialog.note')}
</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={() => handleClose()} full>
{lang.t('bandialog.cancel')}
</Button>
<Button cStyle="black" onClick={() => onClickBanUser(userId, commentId)} full>
{lang.t('bandialog.yes_ban_user')}
</Button>
</div>
const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={lang.t('bandialog.ban_user')}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h2>{lang.t('bandialog.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{lang.t('bandialog.are_you_sure', user.name)}</h3>
<i>{lang.t('bandialog.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{lang.t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={() => handleBanUser({userId: user.id})} raised>
{lang.t('bandialog.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
);
BanUserDialog.propTypes = {
handleBanUser: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
};
export default BanUserDialog;
+20 -44
View File
@@ -2,40 +2,48 @@ import React from 'react';
import timeago from 'timeago.js';
import Linkify from 'react-linkify';
import styles from './CommentList.css';
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 {FabButton, Button} from 'coral-ui';
import {Icon} from 'coral-ui';
import ActionButton from './ActionButton';
const linkify = new Linkify();
// Render a single comment for the list
export default props => {
const Comment = props => {
const {comment, author} = props;
let authorStatus = author.status;
const links = linkify.getMatches(comment.body);
return (
<li tabIndex={props.index} className={`${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<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}>
<i className={`material-icons ${styles.avatar}`}>person</i>
<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>
<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) => getActionButton(action, i, props))}
{props.modActions.map(
(action, i) =>
<ActionButton
option={action}
key={i}
type='COMMENTS'
comment={comment}
user={author}
menuOptionsMap={props.menuOptionsMap}
onClickAction={props.onClickAction}
onClickShowBanDialog={props.onClickShowBanDialog}/>
)}
</div>
</div>
<div>
{authorStatus === 'banned' ?
<span className={styles.banned}><Icon name='error_outline'/> {lang.t('comment.banned_user')}</span> : null}
</div>
@@ -53,39 +61,7 @@ export default props => {
);
};
// Get the button of the action performed over a comment if any
const getActionButton = (action, i, props) => {
const {comment, author} = props;
const status = comment.status;
const flagged = comment.flagged;
const banned = (author.status === 'banned');
if (action === 'flag' && (status || flagged === true)) {
return null;
}
if (action === 'ban') {
return (
<Button
className='ban'
cStyle='black'
disabled={banned ? 'disabled' : ''}
onClick={() => props.onClickShowBanDialog(author.id, author.displayName, comment.id)}
key={i}
>
{lang.t('comment.ban_user')}
</Button>
);
}
return (
<FabButton
className={`${action} ${styles.actionButton}`}
cStyle={action}
icon={props.actionsMap[action].icon}
key={i}
onClick={() => props.onClickAction(props.actionsMap[action].status, comment)}
/>
);
};
export default Comment;
const linkStyles = {
backgroundColor: 'rgb(255, 219, 135)',
@@ -1,168 +0,0 @@
import React, {PropTypes} from 'react';
import styles from './CommentList.css';
import key from 'keymaster';
import Hammer from 'hammerjs';
import Comment from 'components/Comment';
// Each action has different meaning and configuration
const modActions = {
'reject': {status: 'rejected', icon: 'close', key: 'r'},
'approve': {status: 'accepted', icon: 'done', key: 't'},
'flag': {status: 'flagged', icon: 'flag', filter: 'Untouched'},
'ban': {status: 'banned', icon: 'not interested'}
};
// Renders a comment list and allow performing actions
export default class CommentList extends React.Component {
static propTypes = {
isActive: PropTypes.bool,
singleView: PropTypes.bool,
commentIds: PropTypes.arrayOf(PropTypes.string).isRequired,
comments: PropTypes.object.isRequired,
users: PropTypes.object.isRequired,
onClickAction: PropTypes.func,
modActions: PropTypes.arrayOf(PropTypes.string),
loading: PropTypes.bool,
// list of actions (flags, etc) associated with the comments
actions: PropTypes.shape({
ids: PropTypes.arrayOf(PropTypes.string)
}),
suspectWords: PropTypes.arrayOf(PropTypes.string)
}
constructor (props) {
super(props);
this.state = {active: null};
this.onClickAction = this.onClickAction.bind(this);
this.onClickShowBanDialog = this.onClickShowBanDialog.bind(this);
}
// remove key handlers before leaving
componentWillUnmount () {
this.unbindKeyHandlers();
}
// add key handlers and gestures
componentDidMount () {
this.bindKeyHandlers();
// this.bindGestures() // need to check whether we're on a mobile device or this throws an Error
}
// If entering to singleview and no active, active is the first eleement
componentWillReceiveProps (nextProps) {
if (nextProps.singleView && !this.state.active) {
this.setState({active: nextProps.commentIds[0]});
}
}
// Add swipe to approve or reject
bindGestures () {
const {modActions} = this.props;
this._hammer = new Hammer(this.base);
this._hammer.get('swipe').set({direction: Hammer.DIRECTION_HORIZONTAL});
if (modActions.indexOf('reject') !== -1) {
this._hammer.on('swipeleft', () => this.props.singleView && this.actionKeyHandler('Rejected'));
}
if (modActions.indexOf('approve') !== -1) {
this._hammer.on('swiperight', () => this.props.singleView && this.actionKeyHandler('Approved'));
}
}
// Add key handlers. Each action has one and added j/k for moving around
bindKeyHandlers () {
this.props.modActions.filter(action => modActions[action].key).forEach(action => {
key(modActions[action].key, 'commentList', () => this.props.isActive && this.actionKeyHandler(modActions[action].status));
});
key('j', 'commentList', () => this.props.isActive && this.moveKeyHandler('down'));
key('k', 'commentList', () => this.props.isActive && this.moveKeyHandler('up'));
key.setScope('commentList');
}
// Perform an action using the keys only if the comment is active
actionKeyHandler (action) {
if (this.props.isActive && this.state.active) {
this.onClickAction(action, this.state.active);
}
}
// move around with j/k
moveKeyHandler (direction) {
if (!this.props.isActive) {
return;
}
const {commentIds} = this.props;
const {active} = this.state;
// check boundaries
if (active === null || !commentIds.length) {
this.setState({active: commentIds[0]});
} else if (direction === 'up' && active !== commentIds[0]) {
this.setState({active: commentIds[commentIds.indexOf(active) - 1]});
} else if (direction === 'down' && active !== commentIds[commentIds.length - 1]) {
this.setState({active: commentIds[commentIds.indexOf(active) + 1]});
}
// scroll to the position
const index = Math.max(commentIds.indexOf(this.state.active), 0);
this.base.childNodes[index] && this.base.childNodes[index].focus();
}
unbindKeyHandlers () {
key.deleteScope('commentList');
}
// If we are performing an action over a comment (aka removing from the list) we need to select a new active.
// TODO: In the future this can be improved and look at the actual state to
// resolve since the content of the list could change externally. For now it works as expected
onClickAction (action, id, author_id) {
// activate the next comment
if (id === this.state.active) {
const {commentIds} = this.props;
if (commentIds[commentIds.length - 1] === this.state.active) {
this.setState({active: commentIds[commentIds.length - 2]});
} else {
this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.length - 1)]});
}
}
this.props.onClickAction(action, id, author_id);
}
onClickShowBanDialog(userId, userName, commentId) {
this.props.onClickShowBanDialog(userId, userName, commentId);
}
render () {
const {singleView, commentIds, comments, users, hideActive, key, suspectWords} = this.props;
const {active} = this.state;
return (
<ul
className={`${styles.list} ${singleView ? styles.singleView : ''}`} {...key}
id='commentList'
>
{commentIds.map((commentId, index) => {
const comment = comments[commentId];
const author = users[comment.author_id];
return <Comment
suspectWords={suspectWords}
comment={comment}
author={author}
key={index}
index={index}
onClickAction={this.onClickAction}
onClickShowBanDialog={this.onClickShowBanDialog}
modActions={this.props.modActions}
actionsMap={modActions}
isActive={commentId === active}
hideActive={hideActive} />;
})}
</ul>
);
}
}
@@ -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;
@@ -19,7 +19,6 @@
.inner {
width: 100vw;
height: 100vh;
background-color: #fff;
box-shadow: 2px 2px 5px 0px rgba(153,153,153,1);
padding: 20px;
@@ -37,7 +36,6 @@
@media (--big-viewport) {
.inner {
width: 600px;
height: 50vh;
border-radius: 4px;
}
}
@@ -36,19 +36,40 @@
.listItem {
border-bottom: 1px solid #e0e0e0;
padding: 16px;
font-size: 16px;
width: 100%;
max-width: 660px;
min-width: 400px;
margin: 0 auto;
padding: 16px 14px;
position: relative;
transition: box-shadow 200ms;
&: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;
}
.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;
}
@@ -71,25 +92,15 @@
.created {
color: #666;
font-size: 10px;
margin-left: 10px;
}
.actionButton {
transform: scale(.7);
margin: 0;
font-size: 13px;
margin-left: 40px;
}
.body {
margin-top: 20px;
flex: 1;
font-size: 1em;
color: rgba(0,0,0,.54);
}
.actions {
margin-left: 10px;
display: flex;
font-size: 0.88em;
color: black;
}
.flagged {
@@ -97,6 +108,12 @@
padding-top: 15px;
padding-left: 10px;
}
.flagCount{
font-size: 12px;
color: #d32f2f;
}
}
.empty {
@@ -143,3 +160,26 @@
margin-right: 5px;
}
}
.ban {
display: block;
text-align: center;
margin-top: 5px;
}
.banButton {
width: 114px;
letter-spacing: 1px;
i {
vertical-align: middle;
margin-right: 10px;
font-size: 14px;
}
}
.actionButton {
transform: scale(.8);
margin: 0;
}
@@ -0,0 +1,227 @@
import React, {PropTypes} from 'react';
import styles from './ModerationList.css';
import key from 'keymaster';
import Hammer from 'hammerjs';
import Comment from './Comment';
import User from './User';
import SuspendUserModal from './SuspendUserModal';
// Each action has different meaning and configuration
const menuOptionsMap = {
'reject': {status: 'REJECTED', icon: 'close', key: 'r'},
'approve': {status: 'ACCEPTED', icon: 'done', key: 't'},
'flag': {status: 'FLAGGED', icon: 'flag', filter: 'Untouched'},
'ban': {status: 'BANNED', icon: 'not interested'}
};
// Renders a comment list and allow performing actions
export default class ModerationList extends React.Component {
static propTypes = {
isActive: PropTypes.bool,
singleView: PropTypes.bool,
commentIds: PropTypes.arrayOf(PropTypes.string),
actionIds: PropTypes.arrayOf(PropTypes.string),
comments: PropTypes.object,
users: PropTypes.object.isRequired,
actions: PropTypes.object,
userStatusUpdate: PropTypes.func.isRequired,
suspendUser: PropTypes.func.isRequired,
// list of actions (flags, etc) associated with the comments
modActions: PropTypes.arrayOf(PropTypes.string).isRequired,
loading: PropTypes.bool,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired
}
state = {active: null, suspendUserModal: null, email: null};
// remove key handlers before leaving
componentWillUnmount () {
this.unbindKeyHandlers();
}
// add key handlers and gestures
componentDidMount () {
this.bindKeyHandlers();
// this.bindGestures() // need to check whether we're on a mobile device or this throws an Error
}
// If entering to singleview and no active, active is the first eleement
componentWillReceiveProps (nextProps) {
if (nextProps.singleView && !this.state.active) {
this.setState({active: nextProps.commentIds[0]});
}
}
// Add swipe to approve or reject
bindGestures () {
const {modActions} = this.props;
this._hammer = new Hammer(this.base);
this._hammer.get('swipe').set({direction: Hammer.DIRECTION_HORIZONTAL});
if (modActions.indexOf('reject') !== -1) {
this._hammer.on('swipeleft', () => this.props.singleView && this.actionKeyHandler('Rejected'));
}
if (modActions.indexOf('approve') !== -1) {
this._hammer.on('swiperight', () => this.props.singleView && this.actionKeyHandler('Approved'));
}
}
// Add key handlers. Each action has one and added j/k for moving around
bindKeyHandlers () {
const {modActions, isActive} = this.props;
modActions.filter(action => menuOptionsMap[action].key).forEach(action => {
key(menuOptionsMap[action].key, 'moderationList', () => isActive && this.actionKeyHandler(menuOptionsMap[action].status));
});
key('j', 'moderationList', () => isActive && this.moveKeyHandler('down'));
key('k', 'moderationList', () => isActive && this.moveKeyHandler('up'));
key.setScope('moderationList');
}
// Perform an action using the keys only if the comment is active
actionKeyHandler (action) {
if (this.props.isActive && this.state.active) {
this.onClickAction(action, this.state.active);
}
}
// move around with j/k
moveKeyHandler (direction) {
if (!this.props.isActive) {
return;
}
const {commentIds} = this.props;
const {active} = this.state;
// check boundaries
if (active === null || !commentIds.length) {
this.setState({active: commentIds[0]});
} else if (direction === 'up' && active !== commentIds[0]) {
this.setState({active: commentIds[commentIds.indexOf(active) - 1]});
} else if (direction === 'down' && active !== commentIds[commentIds.length - 1]) {
this.setState({active: commentIds[commentIds.indexOf(active) + 1]});
}
// scroll to the position
const index = Math.max(commentIds.indexOf(this.state.active), 0);
this.base.childNodes[index] && this.base.childNodes[index].focus();
}
unbindKeyHandlers () {
key.deleteScope('moderationList');
}
// If we are performing an action over a comment (aka removing from the list) we need to select a new active.
// TODO: In the future this can be improved and look at the actual state to
// resolve since the content of the list could change externally. For now it works as expected
onClickAction = (menuOption, id, action) => {
// activate the next comment
if (id === this.state.active) {
const moderationIds = this.getModerationIds();
if (moderationIds[moderationIds.length - 1] === this.state.active) {
this.setState({active: moderationIds[moderationIds.length - 2]});
} else {
this.setState({active: moderationIds[Math.min(moderationIds.indexOf(this.state.active) + 1, moderationIds.length - 1)]});
}
}
// Update the status right away if this is a comment
if (action.item_type === 'COMMENTS') {
this.props.updateCommentStatus(menuOption, id);
} else if (action.item_type === 'USERS') {
// If a user bio or name is rejected, bring up a dialog before suspending them.
if (menuOption === 'REJECTED') {
this.setState({suspendUserModal: action});
} else if (menuOption === 'ACCEPTED') {
this.props.userStatusUpdate('APPROVED', action.item_id);
}
}
}
onClickShowBanDialog = (userId, userName, commentId) => {
this.props.onClickShowBanDialog(userId, userName, commentId);
}
mapModItems = (itemId, index) => {
const {comments = {}, users, actions = {}, modActions, suspectWords, hideActive} = this.props;
const {active} = this.state;
// Because ids are unique, the id will either appear as an action or as a comment.
const item = comments[itemId] || actions[itemId];
let modItem;
if (item.body) {
// If the item is a comment...
const author = users[item.author_id];
modItem = <Comment
suspectWords={suspectWords}
comment={item}
author={author}
key={index}
index={index}
onClickAction={this.onClickAction}
onClickShowBanDialog={this.onClickShowBanDialog}
modActions={modActions}
menuOptionsMap={menuOptionsMap}
isActive={itemId === active}
hideActive={hideActive} />;
} else {
// If the item is an action...
const user = users[item.item_id];
modItem = user && <User
suspectWords={suspectWords}
action={item}
user={user}
key={index}
index={index}
onClickAction={this.onClickAction}
onClickShowBanDialog={this.onClickShowBanDialog}
modActions={modActions}
menuOptionsMap={menuOptionsMap}
isActive={itemId === active}
hideActive={hideActive} />;
}
return modItem;
}
getModerationIds = () => {
const {commentIds = [], actionIds = [], comments, actions} = this.props;
if (comments && actions) {
return [ ...commentIds, ...actionIds ].sort((a, b) => {
const itemA = comments[a] || actions[a];
const itemB = comments[b] || actions[b];
return itemB.updated_at - itemA.updated_at;
});
} else {
return comments ? commentIds : actionIds;
}
}
render () {
const {singleView, key, suspendUser} = this.props;
// Combine moderations and actions into a single stream and sort by most recently updated.
const moderationIds = this.getModerationIds();
return (
<ul
className={`${styles.list} ${singleView ? styles.singleView : ''}`} {...key}
id='moderationList'>
{moderationIds.map(this.mapModItems)}
<SuspendUserModal
action = {this.state.suspendUserModal}
onClose={() => this.setState({suspendUserModal:null})}
suspendUser={suspendUser} />
</ul>
);
}
}
@@ -0,0 +1,31 @@
.modalButtons {
display: flex;
justify-content: flex-end;
margin-top: 50px;
}
.emailInput {
border-radius: 3px;
width: 100%;
padding: 10px;
font-size: 14px;
}
.writeContainer {
margin-top: 20px;
}
.emailContainer {
display: flex;
}
.emailMessage {
font-weight: 800;
}
.title {
font-size: 20px;
font-weight: 800;
margin-bottom: 20px;
}
@@ -0,0 +1,106 @@
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
import React, {Component, PropTypes} from 'react';
import Modal from 'components/Modal';
import styles from './SuspendUserModal.css';
import {Button} from 'coral-ui';
const stages = [
{
title: 'suspenduser.title_0',
description: 'suspenduser.description_0',
options: {
'j': 'suspenduser.no_cancel',
'k': 'suspenduser.yes_suspend'
}
},
{
title: 'suspenduser.title_1',
description: 'suspenduser.description_1',
options: {
'j': 'bandialog.cancel',
'k': 'suspenduser.send'
}
}
];
class SuspendUserModal extends Component {
state = {email: '', stage: 0}
static propTypes = {
stage: PropTypes.number,
actionType: PropTypes.string,
onClose: PropTypes.func.isRequired,
suspendUser: PropTypes.func.isRequired
}
componentDidMount() {
const about = lang.t('suspenduser.username');
this.setState({email: lang.t('suspenduser.email', about)});
}
/*
* When an admin clicks to suspend a user a dialog is shown, this function
* handles the possible actions for that dialog.
*/
onActionClick = (stage, menuOption) => () => {
const {suspendUser, action} = this.props;
const {stage, email} = this.state;
const cancel = this.props.onClose;
const next = () => this.setState({stage: stage + 1});
const suspend = () => suspendUser(action.item_id, lang.t('suspenduser.email_subject'), email)
.then(this.props.onClose);
const suspendModalActions = [
[ cancel, next ],
[ cancel, suspend ]
];
return suspendModalActions[stage][menuOption]();
}
onEmailChange = (e) => this.setState({email: e.target.value})
render () {
const {action, onClose} = this.props;
if (!action) {
return null;
}
const {stage} = this.state;
const actionType = action.actionType;
const about = actionType === 'flag_bio' ? lang.t('suspenduser.bio') : lang.t('suspenduser.username');
return <Modal open={true} onClose={onClose}>
<div className={styles.title}>{lang.t(stages[stage].title, about)}</div>
<div className={styles.container}>
<div className={styles.description}>
{lang.t(stages[stage].description, about)}
</div>
{
stage === 1 &&
<div className={styles.writeContainer}>
<div className={styles.emailMessage}>{lang.t('suspenduser.write_message')}</div>
<div className={styles.emailContainer}>
<textarea
rows={5}
className={styles.emailInput}
value={this.state.email}
onChange={this.onEmailChange}/>
</div>
</div>
}
<div className={styles.modalButtons}>
{Object.keys(stages[stage].options).map((key, i) => (
<Button key={i} onClick={this.onActionClick(stage, i)}>
{lang.t(stages[stage].options[key], about)}
</Button>
))}
</div>
</div>
</Modal>;
}
}
export default SuspendUserModal;
const lang = new I18n(translations);
+50
View File
@@ -0,0 +1,50 @@
import React from 'react';
import styles from './ModerationList.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
import {Icon} from 'react-mdl';
import ActionButton from './ActionButton';
// Render a single comment for the list
const User = props => {
const {action, user} = props;
let userStatus = user.status;
// Do not display unless the user status is 'pending' or 'banned'.
// This means that they have already been reviewed and approved.
return (userStatus === 'PENDING' || userStatus === 'BANNED') &&
<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.username}</span>
</div>
<div className={styles.sideActions}>
<div className={`actions ${styles.actions}`}>
{props.modActions.map(
(action, i) =>
<ActionButton
type={action.toUpperCase()}
key={i}
user={user}
menuOptionsMap={props.menuOptionsMap}
onClickAction={props.onClickAction}
onClickShowBanDialog={props.onClickShowBanDialog}/>
)}
</div>
</div>
<div>
{userStatus === 'banned' ?
<span className={styles.banned}><Icon name='error_outline'/> {lang.t('comment.banned_user')}</span> : null}
</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 User;
const lang = new I18n(translations);
+42 -17
View File
@@ -1,40 +1,43 @@
.header {
background: #505050;
background-color: transparent;
box-shadow: none;
min-height: 58px;
}
.header > div {
position: relative;
padding: 0;
width: 1170px;
margin: 0 auto;
}
.active {
background: #232323;
background-color: #696969;
position: relative;
padding: 0;
min-width: 1280px;
margin: 0 auto;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
height: 58px;
}
.rightPanel {
position: absolute;
right: 0;
width: 170px;
position: absolute;
top: 0;
right: 0;
width: 170px;
height: 100%;
}
.rightPanel ul {
list-style: none;
line-height: 38px;
margin-right: 20px;
}
.rightPanel li {
display: inline-block;
float: right;
margin-left: 15px;
font-size: 15px;
font-weight: 500;
line-height: 33px;
}
.rightPanel .settings {
vertical-align: middle;
border-radius: 3px;
border: solid 1px #9e9e9e;
line-height: 10px;
line-height: 0;
}
.rightPanel .settings > div {
@@ -45,3 +48,25 @@
background: rgba(158, 158, 158, 0.69);
cursor: pointer;
}
.navLink {
padding: 0 20px;
font-size: 15px;
font-weight: 500;
background-color: transparent;
transition: background-color 200ms;
&.active {
background-color: #232323;
}
}
.nav {
overflow: hidden;
height: 58px !important;
}
.nav .navLink {
padding: 0 20px;
letter-spacing: 0.4px;
}
+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>
<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,4 +1,6 @@
.layout {
max-width: 1170px;
max-width: 1280px;
margin: 0 auto;
}
overflow: hidden;
background-color: #FAFAFA;
}
+19 -12
View File
@@ -1,21 +1,28 @@
.logo h1 {
color: #272727;
font-size: 20px;
margin: 0;
line-height: 60px;
padding: 0 20px;
color: #272727;
font-size: 20px;
margin: 0;
line-height: 60px;
padding-left: 13px;
margin-top: -4px;
}
.logo span {
display: inline-block;
margin-left: 10px;
font-size: 18px;
vertical-align: middle;
display: inline-block;
margin-left: 10px;
font-size: 18px;
vertical-align: middle;
font-weight: 500;
}
.logo {
background: #E5E5E5;
height: 100%;
background: #E5E5E5;
height: 100%;
width: 128px;
}
.base {
stroke: #E5E5E5;
height: 35px;
width: 35px;
}
+1 -1
View File
@@ -5,7 +5,7 @@ import {CoralLogo} from 'coral-ui';
export const Logo = () => (
<div className={styles.logo}>
<h1>
<CoralLogo stroke="#E5E5E5" />
<CoralLogo className={styles.base} />
<span>Talk</span>
</h1>
</div>
@@ -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';
+2
View File
@@ -2,6 +2,8 @@ export const CHECK_LOGIN_REQUEST = 'CHECK_LOGIN_REQUEST';
export const CHECK_LOGIN_SUCCESS = 'CHECK_LOGIN_SUCCESS';
export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE';
export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN';
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
@@ -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';
-3
View File
@@ -1,3 +0,0 @@
export const UPDATE_STATUS_REQUEST = 'UPDATE_STATUS_REQUEST';
export const UPDATE_STATUS_SUCCESS = 'UPDATE_STATUS_SUCCESS';
export const UPDATE_STATUS_FAILURE = 'UPDATE_STATUS_FAILURE';
@@ -0,0 +1,6 @@
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 CommentList from 'components/CommentList';
import CommentBox from 'components/CommentBox';
/**
* Renders a comment stream using a CommentList 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 CommentList
render ({comments, users}, {snackbar, snackbarMsg}) {
return (
<div className={styles.container}>
<CommentBox onSubmit={this.onSubmit} />
<CommentList 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,17 +1,96 @@
.roleButton {
display: block;
.container {
padding: 10px;
display: flex;
padding-bottom: 200px;
}
.searchInput {
display: block;
padding-left: 40px;
width: auto;
.leftColumn {
padding: 42px 56px;
width: 234px;
}
.mainContent {
width: calc(100% - 300px);
padding: 34px 14px;
box-sizing: border-box;
}
.roleButton {
display: block;
}
.searchBox {
background: white;
width: 100%;
padding: 9px;
border: 1px solid #ccc;
border-radius: 2px;
display: flex;
background: white;
box-sizing: border-box;
height: 40px;
i {
color: #A1A1A1
}
input {
display: block;
width: 100%;
height: 100%;
border: none;
font-size: 16px;
padding: 0 2px 0 15px;
box-sizing: border-box;
}
}
.email {
display: block;
display: block;
}
.dataTable {
width: 100%;
border-left: none;
border-right: none;
th {
font-size: 1.1em;
}
th:nth-child(2), th:nth-child(3) {
width: 100px;
}
}
.selectField {
position: relative;
width: 150px;
height: 36px;
background: #2c2c2c;
padding: 10px 15px;
box-sizing: border-box;
color: white;
border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
> div {
padding: 0;
}
i {
position: absolute;
top: 7px;
right: 7px;
}
input {
padding: 0;
font-size: 13px;
letter-spacing: 0.7px;
font-weight: 400;
}
&:hover {
cursor: pointer;
}
}
@@ -1,20 +1,19 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import {Grid, Cell} from 'react-mdl';
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/components/Pager';
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'),
@@ -33,17 +32,17 @@ const tableHeaders = [
const Community = ({isFetching, commenters, ...props}) => {
const hasResults = !isFetching && !!commenters.length;
return (
<Grid>
<Cell col={2}>
<div className={styles.container}>
<div className={styles.leftColumn}>
<form action="">
<div className={`mdl-textfield ${styles.searchBox}`}>
<label className="mdl-button mdl-js-button mdl-button--icon" htmlFor="commenters-search">
<div className={`${styles.searchBox}`}>
<label htmlFor="commenters-search">
<i className="material-icons">search</i>
</label>
<div className="">
<input
id="commenters-search"
className={`mdl-textfield__input ${styles.searchInput}`}
className={`${styles.searchInput}`}
type="text"
value={props.searchValue}
onKeyDown={props.onKeyDownHandler}
@@ -52,24 +51,25 @@ const Community = ({isFetching, commenters, ...props}) => {
</div>
</div>
</form>
</Cell>
<Cell col={6}>
</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}
page={props.page}
onNewPageHandler={props.onNewPageHandler}
/>
</Cell>
</Grid>
</div>
</div>
);
};
@@ -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">
@@ -52,19 +52,21 @@ class Table extends Component {
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.status || ''}
className={styles.selectField}
label={lang.t('community.status')}
onChange={status => this.onCommenterStatusChange(row.id, status)}>
<Option value={'active'}>{lang.t('community.active')}</Option>
<Option value={'banned'}>{lang.t('community.banned')}</Option>
<Option value={'ACTIVE'}>{lang.t('community.active')}</Option>
<Option value={'BANNED'}>{lang.t('community.banned')}</Option>
</SelectField>
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.roles[0] || ''}
className={styles.selectField}
label={lang.t('community.role')}
onChange={role => this.onRoleChange(row.id, role)}>
<Option value={''}>.</Option>
<Option value={'moderator'}>{lang.t('community.moderator')}</Option>
<Option value={'admin'}>{lang.t('community.admin')}</Option>
<Option value={'MODERATOR'}>{lang.t('community.moderator')}</Option>
<Option value={'ADMIN'}>{lang.t('community.admin')}</Option>
</SelectField>
</td>
</tr>
@@ -3,15 +3,8 @@ import {SelectField, Option} from 'react-mdl-selectfield';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import styles from './Configure.css';
import {
List,
ListItem,
ListItemContent,
ListItemAction,
Textfield,
Checkbox,
Icon
} from 'react-mdl';
import {Textfield, Checkbox} from 'react-mdl';
import {Card, Icon, Spinner} from 'coral-ui';
const TIMESTAMPS = {
weeks: 60 * 60 * 24 * 7,
@@ -35,10 +28,14 @@ const updateCharCount = (updateSettings, settingsError) => (event) => {
};
const updateModeration = (updateSettings, mod) => () => {
const moderation = mod === 'pre' ? 'post' : 'pre';
const moderation = mod === 'PRE' ? 'POST' : 'PRE';
updateSettings({moderation});
};
const updateEmailConfirmation = (updateSettings, verify) => () => {
updateSettings({requireEmailConfirmation: !verify});
};
const updateInfoBoxEnable = (updateSettings, infoBox) => () => {
const infoBoxEnable = !infoBox;
updateSettings({infoBoxEnable});
@@ -54,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) => {
@@ -71,111 +74,138 @@ const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => {
const CommentSettings = ({fetchingSettings, title, updateSettings, settingsError, settings, errors}) => {
if (fetchingSettings) {
/* maybe a spinner here at some point */
return <p>Loading settings...</p>;
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>
<div className={styles.commentSettingsSection}>
<h3>{title}</h3>
<List>
<ListItem className={`${styles.configSetting} ${settings.moderation === 'pre' ? styles.enabledSetting : styles.disabledSetting}`}>
<ListItemAction>
<Checkbox
onChange={updateModeration(updateSettings, settings.moderation)}
checked={settings.moderation === 'pre'} />
</ListItemAction>
<ListItemContent>
<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}>
<p className={settings.moderation === 'PRE' ? '' : styles.disabledSettingText}>
{lang.t('configure.enable-pre-moderation-text')}
</p>
</ListItemContent>
</ListItem>
<ListItem className={`${styles.configSetting} ${settings.charCountEnable ? styles.enabledSetting : styles.disabledSetting}`}>
<ListItemAction>
<Checkbox
onChange={updateCharCountEnable(updateSettings, settings.charCountEnable)}
checked={settings.charCountEnable} />
</ListItemAction>
<ListItemContent>
<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>
</ListItemContent>
</ListItem>
<ListItem threeLine className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? styles.enabledSetting : styles.disabledSetting}`}>
<ListItemAction>
<Checkbox
onChange={updateInfoBoxEnable(updateSettings, settings.infoBoxEnable)}
checked={settings.infoBoxEnable} />
</ListItemAction>
<ListItemContent>
{lang.t('configure.include-comment-stream')}
<p>
{lang.t('configure.include-comment-stream-desc')}
</p>
</ListItemContent>
</ListItem>
<ListItem className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? null : styles.hidden}`} >
<ListItemContent>
<Textfield
onChange={updateInfoBoxContent(updateSettings)}
value={settings.infoBoxContent}
label={lang.t('configure.include-text')}
rows={3}/>
</ListItemContent>
</ListItem>
<ListItem className={styles.configSettingInfoBox}>
<ListItemContent>
{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>
<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>
</ListItemContent>
</ListItem>
<ListItem className={styles.configSettingInfoBox}>
<ListItemContent>
{lang.t('configure.closed-comments-desc')}
<Textfield
onChange={updateClosedMessage(updateSettings)}
value={settings.closedMessage}
label={lang.t('configure.closed-comments-label')}
rows={3}/>
</ListItemContent>
</ListItem>
</List>
</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.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.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>
);
};
@@ -1,26 +1,29 @@
.container {
padding: 10px;
display: flex;
h3 {
color: black;
font-size: 1.76em;
font-weight: 500;
}
}
.leftColumn {
width: 300px;
padding: 42px 56px;
width: 234px;
}
.mainContent {
width: calc(70% - 300px)
}
.settingOption {
cursor: pointer;
width: calc(100% - 300px);
padding: 10px 14px;
box-sizing: border-box;
max-width: 718px;
}
.configSetting {
border: 1px solid #ccc;
border-radius: 4px;
height: 95px;
margin-bottom: 10px;
margin-bottom: 20px;
align-items: flex-start;
min-height: 100px;
}
.settingsError {
@@ -42,13 +45,13 @@
}
.configSettingInfoBox {
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
min-height: 100px;
margin-bottom: 20px;
cursor: pointer;
width: auto;
height: auto;
text-align: left;
overflow: visible;
}
.configSettingInfoBox p {
@@ -77,7 +80,7 @@
}
.charCountTexfieldEnabled {
border-color: #4caf50;
border-color: #00796b;
}
.charCountTexfield:focus {
@@ -85,11 +88,12 @@
}
.changedSave {
background-color:#4caf50;
background-color: #00796B;
color: white;
}
.copiedText {
color: #008000;
color: #00796b;
float: right;
padding: 12px;
font-size: 14px;
@@ -97,6 +101,7 @@
.copyButton {
float: right;
width: 200px;
}
.embedInput {
@@ -113,8 +118,12 @@
}
#bannedWordlist, #suspectWordlist {
width: 100%;
padding: 10px;
input {
width: 100%;
padding: 10px;
}
}
.wordlistHeader {
@@ -124,7 +133,7 @@
}
.enabledSetting {
border-left-color: #4caf50;
border-left-color: #00796b;
border-left-style: solid;
border-left-width: 7px;
}
@@ -136,3 +145,23 @@
.hidden {
display: none;
}
.saveBox {
margin-top: 38px;
}
.commentSettingsSection {
padding-bottom: 200px;
.action {
display: inline-block;
position: absolute;
top: 0;
left: 0;
padding: 20px;
}
.content {
display: inline-block;
padding-left: 30px;
}
}
@@ -1,27 +1,24 @@
import React from 'react';
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {
fetchSettings,
updateSettings,
saveSettingsToServer,
updateWordlist,
updateDomainlist
} from '../../actions/settings';
import {
List,
ListItem,
ListItemContent,
Button,
Icon
} from 'react-mdl';
import {Button, List, Item} from 'coral-ui';
import styles from './Configure.css';
import I18n from 'coral-framework/modules/i18n/i18n';
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 React.Component {
class Configure extends Component {
constructor (props) {
super(props);
@@ -30,6 +27,8 @@ class Configure extends React.Component {
changed: false,
errors: {}
};
this.changeSection = this.changeSection.bind(this);
}
componentWillMount = () => {
@@ -41,7 +40,7 @@ class Configure extends React.Component {
this.setState({changed: false});
}
changeSection = (activeSection) => () => {
changeSection(activeSection) {
this.setState({activeSection});
}
@@ -50,6 +49,11 @@ class Configure extends React.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));
@@ -76,7 +80,14 @@ class Configure extends React.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
@@ -99,7 +110,8 @@ class Configure extends React.Component {
}
render () {
const section = this.getSection(this.state.activeSection);
const {activeSection} = this.state;
const section = this.getSection(activeSection);
const showSave = Object.keys(this.state.errors).reduce(
(bool, error) => this.state.errors[error] ? false : bool, this.state.changed);
@@ -107,37 +119,40 @@ class Configure extends React.Component {
return (
<div className={styles.container}>
<div className={styles.leftColumn}>
<List>
<ListItem className={styles.settingOption}>
<ListItemContent
onClick={this.changeSection('comments')}
icon='settings'>{lang.t('configure.comment-settings')}</ListItemContent>
</ListItem>
<ListItem className={styles.settingOption}>
<ListItemContent
onClick={this.changeSection('embed')}
icon='code'>{lang.t('configure.embed-comment-stream')}</ListItemContent>
</ListItem>
<ListItem className={styles.settingOption}>
<ListItemContent
onClick={this.changeSection('wordlist')}
icon='settings'>{lang.t('configure.wordlist')}</ListItemContent>
</ListItem>
<List onChange={this.changeSection} activeItem={activeSection}>
<Item itemId='comments' icon="settings">
{lang.t('configure.comment-settings')}
</Item>
<Item itemId='embed' icon='code'>
{lang.t('configure.embed-comment-stream')}
</Item>
<Item itemId='wordlist' icon='settings'>
{lang.t('configure.wordlist')}
</Item>
</List>
<div className={styles.saveBox}>
{
showSave ?
<Button
raised
onClick={this.saveSettings}
className={styles.changedSave}>
<Icon name='check' /> {lang.t('configure.save-changes')}
</Button>
: <Button
raised
disabled>
{lang.t('configure.save-changes')}
</Button>
<Button
raised
onClick={this.saveSettings}
className={styles.changedSave}
icon='check'
full
>
{lang.t('configure.save-changes')}
</Button>
:
<Button
raised
disabled
icon='check'
full
>
{lang.t('configure.save-changes')}
</Button>
}
</div>
</div>
<div className={styles.mainContent}>
@@ -150,7 +165,9 @@ class Configure extends React.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);
@@ -2,11 +2,7 @@ import React, {Component} from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import styles from './Configure.css';
import {
List,
ListItem,
Button
} from 'react-mdl';
import {Button, Card} from 'coral-ui';
class EmbedLink extends Component {
@@ -29,21 +25,37 @@ 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>
<List>
<ListItem className={styles.configSettingEmbed}>
<div>
<Card shadow="2">
<p>{lang.t('configure.copy-and-paste')}</p>
<textarea rows={5} type='text' className={styles.embedInput} value={embedText} readOnly={true}/>
<Button raised colored className={styles.copyButton} onClick={this.copyToClipBoard}>
<Button raised className={styles.copyButton} onClick={this.copyToClipBoard} cStyle="black">
{lang.t('embedlink.copy')}
</Button>
<div className={styles.copiedText}>{this.state.copied && 'Copied!'}</div>
</ListItem>
</List>
</Card>
</div>
</div>
);
}
@@ -2,15 +2,13 @@ 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 'react-mdl';
import {Card} from 'coral-ui';
const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
<div>
<h3>{lang.t('configure.banned-words-title')}</h3>
<Card id={styles.bannedWordlist} shadow={2}>
<Card id={styles.bannedWordlist}>
<p className={styles.wordlistHeader}>{lang.t('configure.banned-word-header')}</p>
<p className={styles.wordlistDesc}>{lang.t('configure.banned-word-text')}</p>
<TagsInput
@@ -18,10 +16,11 @@ const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
inputProps={{placeholder: 'word or phrase'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => onChangeWordlist('banned', tags)} />
onChange={tags => onChangeWordlist('banned', tags)}
/>
</Card>
<h3>{lang.t('configure.suspect-words-title')}</h3>
<Card id={styles.suspectWordlist} shadow={2}>
<Card id={styles.suspectWordlist}>
<p className={styles.wordlistHeader}>{lang.t('configure.suspect-word-header')}</p>
<p className={styles.wordlistDesc}>{lang.t('configure.suspect-word-text')}</p>
<TagsInput
@@ -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;
@@ -32,4 +36,3 @@ export default connect(
mapStateToProps,
mapDispatchToProps
)(LayoutContainer);
@@ -0,0 +1,119 @@
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 {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 Component {
componentWillMount() {
const {toggleModal, singleView} = this.props;
this.props.fetchSettings();
key('s', () => singleView());
key('shift+/', () => toggleModal(true));
key('esc', () => toggleModal(false));
}
componentWillUnmount() {
key.unbind('s');
key.unbind('shift+/');
key.unbind('esc');
}
componentWillReceiveProps(nextProps) {
const {updateAssets} = this.props;
if(!isEqual(nextProps.data.assets, this.props.data.assets)) {
updateAssets(nextProps.data.assets);
}
}
render () {
const {data, moderation, settings, assets, ...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 (
<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 => ({
moderation: state.moderation.toJS(),
settings: state.settings.toJS(),
assets: state.assets.get('assets')
});
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 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,33 +0,0 @@
@custom-media --big-viewport (min-width: 780px);
.listContainer {
max-width: 860px;
margin: 0 auto;
}
.tabBar {
background: #9E9E9E;
}
.tab {
flex: 1;
color: white;
}
.showShortcuts {
position: absolute;
right: 130px;
display: flex;
align-items: center;
font-size: 13px;
span {
margin-left: 7px;
}
}
@media (--big-viewport) {
.tab {
flex: none;
}
}
@@ -1,168 +1,48 @@
import React from 'react';
import {connect} from 'react-redux';
import key from 'keymaster';
import ModerationKeysModal from 'components/ModerationKeysModal';
import CommentList from 'components/CommentList';
import BanUserDialog from 'components/BanUserDialog';
import {
updateStatus,
showBanUserDialog,
hideBanUserDialog,
fetchModerationQueueComments
} from 'actions/comments';
import {userStatusUpdate} from 'actions/users';
import {fetchSettings} from 'actions/settings';
import styles from './ModerationQueue.css';
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';
/*
* Renders the moderation queue as a tabbed layout with 3 moderation
* queues :
* * pending: filtered by status Untouched
* * rejected: filtered by status Rejected
* * flagged: with a flagged action on them
*/
class ModerationQueue extends React.Component {
constructor (props) {
super(props);
this.state = {activeTab: 'pending', singleView: false, modalOpen: false};
}
// Fetch comments and bind singleView key before render
componentWillMount () {
this.props.dispatch(fetchSettings());
this.props.dispatch(fetchModerationQueueComments());
key('s', () => this.setState({singleView: !this.state.singleView}));
key('shift+/', () => this.setState({modalOpen: true}));
key('esc', () => this.setState({modalOpen: false}));
}
// Unbind singleView key before unmount
componentWillUnmount () {
key.unbind('s');
key.unbind('shift+/');
key.unbind('esc');
}
// Hack for dynamic mdl tabs
componentDidMount () {
if (typeof componentHandler !== 'undefined') {
// FIXME: fix this hack
componentHandler.upgradeAllRegistered(); // eslint-disable-line no-undef
}
}
// Dispatch the update status action
onCommentAction (action, comment) {
// If not banning then change the status to approved or flagged as action = status
this.props.dispatch(updateStatus(action, comment));
}
showBanUserDialog (userId, userName, commentId) {
this.props.dispatch(showBanUserDialog(userId, userName, commentId));
}
hideBanUserDialog () {
this.props.dispatch(hideBanUserDialog(false));
}
banUser (userId, commentId) {
this.props.dispatch(userStatusUpdate('banned', userId, commentId))
.then(() => {
this.props.dispatch(fetchModerationQueueComments());
});
}
onTabClick (activeTab) {
this.setState({activeTab});
}
// Render the tabbed lists moderation queues
render () {
const {comments, users, settings} = this.props;
const {activeTab, singleView, modalOpen} = this.state;
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);
return (
<div>
<div className='mdl-tabs mdl-js-tabs mdl-js-ripple-effect'>
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<a href='#pending' onClick={() => this.onTabClick('pending')}
className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.pending')}</a>
<a href='#rejected' onClick={() => this.onTabClick('rejected')}
className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.rejected')}</a>
<a href='#flagged' onClick={() => this.onTabClick('flagged')}
className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.flagged')}</a>
</div>
<div className={`mdl-tabs__panel is-active ${styles.listContainer}`} id='pending'>
<CommentList
suspectWords={settings.settings.wordlist.suspect}
isActive={activeTab === 'pending'}
singleView={singleView}
commentIds={premodIds}
comments={comments.byId}
users={users.byId}
onClickAction={(action, comment) => this.onCommentAction(action, comment)}
onClickShowBanDialog={(userId, userName, commentId) => this.showBanUserDialog(userId, userName, commentId)}
modActions={['reject', 'approve', 'ban']}
loading={comments.loading} />
<BanUserDialog
open={comments.showBanUserDialog}
handleClose={() => this.hideBanUserDialog()}
onClickBanUser={(userId, commentId) => this.banUser(userId, commentId)}
user={comments.banUser}/>
</div>
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='rejected'>
<CommentList
suspectWords={settings.settings.wordlist.suspect}
isActive={activeTab === 'rejected'}
singleView={singleView}
commentIds={rejectedIds}
comments={comments.byId}
users={users.byId}
onClickAction={(action, comment) => this.onCommentAction(action, comment)}
modActions={['approve']}
loading={comments.loading} />
</div>
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='flagged'>
<CommentList
isActive={activeTab === 'rejected'}
suspectWords={settings.settings.wordlist.suspect}
singleView={singleView}
commentIds={flaggedIds}
comments={comments.byId}
users={users.byId}
onClickAction={(action, comment) => this.onCommentAction(action, comment)}
modActions={['reject', 'approve']}
loading={comments.loading} />
</div>
<ModerationKeysModal open={modalOpen}
onClose={() => this.setState({modalOpen: false})} />
</div>
</div>
);
}
}
const mapStateToProps = state => ({
actions: state.actions.toJS(),
settings: state.settings.toJS(),
comments: state.comments.toJS(),
users: state.users.toJS()
});
export default connect(mapStateToProps)(ModerationQueue);
import translations from 'coral-admin/src/translations';
const lang = new I18n(translations);
const ModerationQueue = ({activeTab = 'premod', ...props}) => {
const areComments = props.data[activeTab].length;
return (
<div id="moderationList">
<ul style={{paddingLeft: 0}}>
{
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>
);
};
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'}
};
@@ -4,11 +4,14 @@
}
.leftColumn {
width: 200px;
padding: 42px 56px;
width: 234px;
}
.mainContent {
width: calc(90% - 200px);
width: calc(100% - 300px);
padding: 34px 14px;
box-sizing: border-box;
}
.searchIcon {
@@ -18,11 +21,12 @@
}
.searchBox {
padding: 3px;
width: 100%;
padding: 9px;
border: 1px solid #ccc;
border-radius: 3px;
width: 90%;
border-radius: 2px;
display: flex;
background: white;
}
.searchBoxInput {
@@ -48,6 +52,22 @@
.streamsTable {
width: 100%;
border-left: none;
border-right: none;
a {
color: rgb(44, 44, 44);
font-weight: 500;
text-decoration: none;
}
th {
font-size: 1.1em;
}
th.status {
width: 100px;
}
}
.radio {
@@ -55,18 +75,20 @@
}
.statusMenu {
border-radius: 3px;
border-radius: 2px;
width: 10em;
text-align: center;
float: right;
border: 1px solid #ccc;
color: #fff;
cursor: pointer;
letter-spacing: 0.7px;
font-weight: 400;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
}
.statusMenuOpen {
padding: 10px;
background-color: #4caf50;
background-color: #268D81;
}
.statusMenuIcon {
@@ -75,9 +97,17 @@
.statusMenuClosed {
padding: 10px;
background-color: #000;
background-color: #262626;
}
.hidden {
display: none;
}
.radioGroup {
margin-top: 5px;
span {
margin-bottom: 7px;
display: inline-block;
}
}
@@ -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];
@@ -104,60 +104,66 @@ class Streams extends Component {
const {search, sort, filter} = this.state;
const {assets} = this.props;
return <div className={styles.container}>
<div className={styles.leftColumn}>
const assetsIds = assets.ids.map((id) => assets.byId[id]);
<div className={styles.searchBox}>
<Icon name='search' className={styles.searchIcon}/>
<input
type='text'
value={search}
className={styles.searchBoxInput}
onChange={this.onSearchChange}
placeholder={lang.t('streams.search')}/>
</div>
<div className={styles.optionHeader}>{lang.t('streams.filter-streams')}</div>
<div className={styles.optionDetail}>{lang.t('streams.stream-status')}</div>
return (
<div className={styles.container}>
<div className={styles.leftColumn}>
<div className={styles.searchBox}>
<Icon name='search' className={styles.searchIcon}/>
<input
type='text'
value={search}
className={styles.searchBoxInput}
onChange={this.onSearchChange}
placeholder={lang.t('streams.search')}/>
</div>
<div className={styles.optionHeader}>{lang.t('streams.filter-streams')}</div>
<div className={styles.optionDetail}>{lang.t('streams.stream-status')}</div>
<RadioGroup
name='status filter'
value={filter}
childContainer='div'
onChange={this.onSettingChange('filter')}>
onChange={this.onSettingChange('filter')}
className={styles.radioGroup}
>
<Radio value='all'>{lang.t('streams.all')}</Radio>
<Radio value='open'>{lang.t('streams.open')}</Radio>
<Radio value='closed'>{lang.t('streams.closed')}</Radio>
</RadioGroup>
<div className={styles.optionHeader}>{lang.t('streams.sort-by')}</div>
<RadioGroup
name='sort by'
value={sort}
childContainer='div'
onChange={this.onSettingChange('sort')}>
<Radio value='desc'>{lang.t('streams.newest')}</Radio>
<Radio value='asc'>{lang.t('streams.oldest')}</Radio>
</RadioGroup>
<RadioGroup
name='sort by'
value={sort}
childContainer='div'
onChange={this.onSettingChange('sort')}
className={styles.radioGroup}
>
<Radio value='desc'>{lang.t('streams.newest')}</Radio>
<Radio value='asc'>{lang.t('streams.oldest')}</Radio>
</RadioGroup>
</div>
{
assetsIds.length
? <div className={styles.mainContent}>
<DataTable className={styles.streamsTable} rows={assetsIds} onClick={this.goToModeration}>
<TableHeader name="title" cellFormatter={this.renderTitle}>{lang.t('streams.article')}</TableHeader>
<TableHeader name="publication_date" cellFormatter={this.renderDate}>
{lang.t('streams.pubdate')}
</TableHeader>
<TableHeader name="closedAt" cellFormatter={this.renderStatus} className={styles.status}>
{lang.t('streams.status')}
</TableHeader>
</DataTable>
<Pager
totalPages={Math.ceil((assets.count || 0) / limit)}
page={this.state.page}
onNewPageHandler={this.onPageClick} />
</div>
: <EmptyCard>{lang.t('streams.empty_result')}</EmptyCard>
}
</div>
<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 numeric name="publication_date" cellFormatter={this.renderDate}>
{lang.t('streams.pubdate')}
</TableHeader>
<TableHeader numeric name="closedAt" cellFormatter={this.renderStatus}>
{lang.t('streams.status')}
</TableHeader>
</DataTable>
<Pager
totalPages={Math.ceil((assets.count || 0) / limit)}
page={this.state.page}
onNewPageHandler={this.onPageClick}
/>
</div>
</div>;
);
}
}
@@ -166,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
}
}
}

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