Compare commits

...
994 Commits
Author SHA1 Message Date
David Erwin 35f01c2ecc Merge pull request #516 from coralproject/remove-rejected-comments
let ApolloClient know which types are interfaces
2017-04-20 20:08:50 -04:00
Riley Davis 945b0b5df8 Merge branch 'master' into remove-rejected-comments 2017-04-20 18:04:11 -06:00
Riley Davis a58e15c4c8 let ApolloClient know which types are interfaces 2017-04-20 18:00:06 -06:00
David Erwin 0dee09dd93 Merge pull request #515 from coralproject/mobilify-login-popup
Mobilify login popup
2017-04-20 17:42:00 -04:00
David Erwin e8c3bdc5b1 Merge branch 'master' into mobilify-login-popup 2017-04-20 17:28:54 -04:00
Riley Davis bcf31e5fa9 reporting a comment zooms the window 2017-04-20 15:19:19 -06:00
Riley Davis a2131c205a make the viewport non-scalable on mobile. make input font-size bigger to prevent mobile zoom effect 2017-04-20 15:14:15 -06:00
Riley Davis 4ae68b4bd3 make the login pop-up scale on mobile 2017-04-20 15:07:55 -06:00
David Erwin 267338c851 Merge pull request #514 from coralproject/premod-queue-fix
Premod queue fix
2017-04-20 16:57:43 -04:00
Riley Davis fddd8d63c7 Merge branch 'master' into premod-queue-fix 2017-04-20 14:53:51 -06:00
David Erwin bdd8ec3789 Merge pull request #505 from coralproject/default-resolve-type
Added defaultResolveType
2017-04-20 16:40:24 -04:00
Riley Davis 8708fcf2cd actually, we want to have the action_summaries on all requests, since a plugin could potentially pre-flag a pre-mod comment 2017-04-20 14:37:48 -06:00
David Erwin 1db168df8c Merge branch 'master' into default-resolve-type 2017-04-20 16:33:54 -04:00
Riley Davis 3dc2674f47 the pre-mod tab does not request action_summaries since there wouldn't be any 2017-04-20 14:33:49 -06:00
David Erwin 1ec30854c2 Merge pull request #510 from coralproject/login-popup
Login Popup
2017-04-20 16:10:18 -04:00
David Erwin 7c38b9b54f Merge branch 'master' into default-resolve-type 2017-04-20 16:03:38 -04:00
Chi Vinh Le 4519a550a2 Fix fresh login on profile 2017-04-21 02:27:26 +07:00
David Erwin 00e24ce88a Merge branch 'master' into login-popup 2017-04-20 15:15:51 -04:00
David Erwin 467a32038e Merge pull request #512 from coralproject/action-summary-fix
Action summary fix - Part Deux
2017-04-20 15:15:38 -04:00
Riley Davis 2d212393ff Merge branch 'master' into login-popup 2017-04-20 12:57:13 -06:00
Riley Davis d1729e5115 camel_case 2017-04-20 12:44:25 -06:00
David Erwin 85e552cf04 Merge branch 'master' into action-summary-fix 2017-04-20 14:40:16 -04:00
David Erwin 2c5f46b3ae Merge pull request #513 from coralproject/fix-ignored-user-error
Fix ignored users when not logged in
2017-04-20 14:39:24 -04:00
Chi Vinh Le 3ff9313b03 Fix ignored users when not logged in 2017-04-21 01:18:51 +07:00
Belén Curcio fc4b821f05 Merge branch 'master' into action-summary-fix 2017-04-20 15:04:09 -03:00
Chi Vinh Le 8fc2d5e8aa Merge branch 'login-popup' of ssh://github.com/coralproject/talk into login-popup 2017-04-21 00:54:45 +07:00
Chi Vinh Le f0e584f0b3 Restore login flow in ProfileContainer 2017-04-21 00:52:45 +07:00
Chi Vinh Le 6df2103da3 Remove obsolete offset and styling 2017-04-21 00:45:00 +07:00
David Erwin 900cb33122 Merge branch 'master' into default-resolve-type 2017-04-20 13:38:06 -04:00
David Erwin 6075317564 Merge pull request #511 from coralproject/build-fix
Added --force to rebuild native modules
2017-04-20 13:37:33 -04:00
David Erwin f3f651c562 Merge branch 'master' into build-fix 2017-04-20 13:33:45 -04:00
Belen Curcio 1ffa78dcfb Merge branch 'login-popup' of github.com:coralproject/talk into login-popup 2017-04-20 14:28:16 -03:00
David Erwin cbba3acf4a Merge pull request #508 from coralproject/action-label-fix
Quick fix action labels in Mobile
2017-04-20 13:27:37 -04:00
Chi Vinh Le f3f1cc53fe Fix safari popup resizing 2017-04-21 00:27:26 +07:00
David Erwin 0de87af07a Merge branch 'master' into login-popup 2017-04-20 13:25:00 -04:00
Belen Curcio c47047c3ff Classes 2017-04-20 14:23:44 -03:00
Belen Curcio c9207fd620 Merge branch 'login-popup' of github.com:coralproject/talk into login-popup 2017-04-20 14:19:04 -03:00
Belen Curcio 09cb518049 Merge branch 'master' of github.com:coralproject/talk into login-popup 2017-04-20 14:18:40 -03:00
Chi Vinh Le 5b7750f7a5 Reuse same store and action flows 2017-04-20 23:48:30 +07:00
Riley Davis 50774f1922 remove null checks 2017-04-20 10:04:28 -06:00
Riley Davis eda15538fe add kiwi's and wyatt's changes 2017-04-20 10:02:09 -06:00
Wyatt Johnson 50219922d5 Switched to lock at node 7.8 2017-04-20 09:35:49 -06:00
Chi Vinh Le b12d4ef4a2 WIP 2017-04-20 21:30:24 +07:00
Chi Vinh Le 348c01ae80 Fix linting errors 2017-04-20 20:58:45 +07:00
Chi Vinh Le 1b95fbd852 Remove SignInContainer from admin 2017-04-20 20:34:36 +07:00
Chi Vinh Le 3bf2187688 Fix crashing admin when using framework 2017-04-20 18:45:55 +07:00
Kiwi 51b866cd65 Merge branch 'master' into default-resolve-type 2017-04-20 16:55:04 +07:00
Kiwi b7ffbdb762 Merge branch 'master' into action-label-fix 2017-04-20 16:48:12 +07:00
Riley Davis ffdec225c7 fix RespectButton component code 2017-04-19 22:02:57 -06:00
Riley Davis 59fbff3caf fix how FlagActionSummaries works in the embed 2017-04-19 21:15:03 -06:00
Wyatt Johnson 53f7ab86c0 Merge branch 'master' into build-fix 2017-04-19 18:16:12 -06:00
Wyatt Johnson 4ac71c884b added --force to rebuild native modules 2017-04-19 18:10:16 -06:00
Belen Curcio 6155a46e71 Sign up popup 2017-04-19 18:10:09 -03:00
Belen Curcio 13965e1111 Adding actions, routes and login popup 2017-04-19 17:04:49 -03:00
Riley Davis 7b4e8ef204 Merge pull request #499 from coralproject/flag-detail
Flag detail
2017-04-19 13:38:01 -06:00
Riley Davis 1abafb7037 Merge branch 'master' into flag-detail 2017-04-19 11:21:55 -06:00
Riley Davis 1535f10450 Merge branch 'master' into action-label-fix 2017-04-19 11:11:21 -06:00
Riley Davis 82927b77be Merge pull request #507 from coralproject/premod-load-collapse
Fix collapsing UI
2017-04-19 09:57:23 -06:00
Riley Davis b34cd2d4c1 Merge branch 'master' into premod-load-collapse 2017-04-19 09:50:30 -06:00
Riley Davis bd0852bf68 Merge pull request #506 from coralproject/fix-load-more-premod
Fix load more in premod queue
2017-04-19 09:48:40 -06:00
Belén Curcio c72a5c6df9 Merge branch 'master' into action-label-fix 2017-04-19 12:05:59 -03:00
Belen Curcio 9ddc6c35fd Action labels default.css, temporary fix 2017-04-19 11:59:07 -03:00
Chi Vinh Le f724353cf0 Fix collapsing UI 2017-04-19 16:38:02 +07:00
Chi Vinh Le 7698bdf930 Fix load more in premod queue 2017-04-19 16:35:53 +07:00
Kiwi 1750510d34 Merge pull request #504 from coralproject/valid-notification-actions
Return a value for notification actions
2017-04-19 16:12:40 +07:00
Wyatt Johnson 2eda1cb1e4 Added defaultResolveType 2017-04-18 14:50:57 -06:00
Chi Vinh Le 4ce4c74fd9 Return a value for notification actions 2017-04-19 01:09:59 +07:00
Belén Curcio bfacc8f2f7 Merge pull request #479 from coralproject/plugin_examples
Hooks and Context for Comment Plugins And Off-Topic Plugin
2017-04-18 13:52:30 -03:00
Belén Curcio ad15b38a5d Merge branch 'master' into plugin_examples 2017-04-18 13:46:44 -03:00
Wyatt Johnson fd6282077f Adjusted test to conform to new form 2017-04-17 16:05:44 -06:00
Riley Davis b5210f3727 more translations 2017-04-17 14:42:18 -06:00
Riley Davis cc6882168b add translations 2017-04-17 14:31:45 -06:00
Riley Davis a5d11a05d1 Merge branch 'master' into flag-detail 2017-04-17 14:00:32 -06:00
Gabriela Rodríguez Berón 7526d64bc0 Merge pull request #493 from coralproject/143520971-stream-settings
Configure stream settings
2017-04-17 12:45:34 -07:00
Gabriela Rodríguez Berón 04a4918fd5 Update ConfigureStreamContainer.js 2017-04-17 12:35:29 -07:00
Gabriela Rodríguez Berón 371e3b951d Merge branch 'master' into 143520971-stream-settings 2017-04-17 12:25:40 -07:00
gaba f66e12be18 Merge branch 'master' into 143520971-stream-settings 2017-04-17 12:07:19 -07:00
gaba fb010bfa67 Rename settings of the state to dirtySettings and add TO DO item to be sure we refactor this next time we do a refactor. 2017-04-17 11:51:24 -07:00
Belen Curcio e729a673a3 Merge branch 'master' of github.com:coralproject/talk into plugin_examples 2017-04-17 14:17:42 -03:00
Riley Davis d6b2d6d876 Merge branch 'master' into flag-detail 2017-04-14 15:38:15 -06:00
Riley Davis 55cdc98303 add some proptypes 2017-04-14 15:36:29 -06:00
Riley Davis d5ff9d3eeb show the details 2017-04-14 15:32:06 -06:00
Wyatt Johnson 404301bd43 Merge pull request #501 from coralproject/docs-update
Added note for production based deploy
2017-04-14 15:22:45 -06:00
Wyatt Johnson 8c478d1c13 Merge branch 'master' into docs-update 2017-04-14 15:18:44 -06:00
Wyatt Johnson bee27da725 Added note for production based deploy 2017-04-14 15:16:42 -06:00
David Erwin a873034288 Merge pull request #498 from coralproject/docs-update
Docs update
2017-04-14 17:16:26 -04:00
Wyatt Johnson 8164fa7813 Merge branch 'master' into docs-update 2017-04-14 15:07:02 -06:00
David Erwin d1bc747009 Merge pull request #500 from coralproject/username-profanity-fix
Username Profanity Fix
2017-04-14 17:05:27 -04:00
Wyatt Johnson e028e30afd Added line about SSL certificates 2017-04-14 15:04:35 -06:00
Riley Davis ff3cffdc8f load actions 2017-04-14 14:59:51 -06:00
Wyatt Johnson d4fc8a474d Should resolve bug relating to usernames being set as "undefined" 2017-04-14 14:57:13 -06:00
Wyatt Johnson a19fe02782 Added setup/usage docs 2017-04-14 14:49:23 -06:00
Gabriela Rodríguez Berón 81e040397e Merge branch 'master' into docs-update 2017-04-14 12:09:45 -07:00
Riley Davis 07ea548d46 Merge branch 'master' into flag-detail 2017-04-14 12:56:11 -06:00
Gabriela Rodríguez Berón 99b0f8f2ad Merge pull request #497 from coralproject/docs-metrics
EXTRA-SHORT: A note on the from/to parameters.
2017-04-14 11:29:08 -07:00
Wyatt Johnson cb652dbb86 Merge branch 'master' into docs-update 2017-04-14 12:23:43 -06:00
gaba fa944927e4 A note on the from/to parameters. 2017-04-14 10:41:46 -07:00
Gabriela Rodríguez Berón fb6ba37366 Merge branch 'master' into docs-metrics 2017-04-14 10:40:28 -07:00
gaba 9cf31e0bfa A note on the from/to parameters. 2017-04-14 10:37:05 -07:00
Wyatt Johnson 061e775227 Spell checks + doc updates 2017-04-14 10:26:06 -06:00
Kim Gardner 258321201b Merge pull request #496 from coralproject/add_translations
Add translations
2017-04-13 20:15:07 -04:00
Kim Gardner 71e1c01552 Merge branch 'master' into add_translations 2017-04-13 20:10:19 -04:00
Kim Gardner 1fc0bf6ec1 Merge pull request #494 from coralproject/git-client-ignore
ignore node_modules in plugins
2017-04-13 20:09:52 -04:00
Kim Gardner e58cf8e933 Merge branch 'master' into git-client-ignore 2017-04-13 20:05:13 -04:00
Kim Gardner 7e34bc1d13 Merge pull request #477 from gobengo/ignore
Ignore User
2017-04-13 20:05:02 -04:00
Kim Gardner 193ed8c863 Merge branch 'master' into ignore 2017-04-13 20:00:59 -04:00
Wyatt Johnson 642a9b87ca Merge branch 'master' into git-client-ignore 2017-04-13 17:33:49 -06:00
gaba d9c8882d8f More translations in the configuration. 2017-04-13 16:19:21 -07:00
gaba 902071c0fe Adds title of most flagged in dashboard. 2017-04-13 16:11:25 -07:00
Riley Davis 018e88f7cf Merge pull request #495 from coralproject/143357607-close-stream
Bug on closing the stream
2017-04-13 15:28:43 -06:00
gaba c395bc8849 Adds closedAt to state... and check that closedTimeout settings exists for the Asset. 2017-04-13 14:21:45 -07:00
Riley Davis bd782b98ab Merge branch 'master' into 143520971-stream-settings 2017-04-13 15:17:45 -06:00
Riley Davis d5552df41f ignore node_modules in plugins 2017-04-13 14:46:11 -06:00
Wyatt Johnson 7aa262e97f Added new cluster style deployment docs 2017-04-13 13:46:37 -06:00
gaba fa72a661ad Going back to previous setting state as I do not want it to re-render. 2017-04-13 12:43:24 -07:00
Wyatt Johnson 90db03bc29 rearranged docs 2017-04-13 13:40:13 -06:00
Riley Davis 6aaca9130a saving my place 2017-04-13 12:56:48 -06:00
Wyatt Johnson 7bcd336f51 Merge pull request #492 from coralproject/plugins-install-fix
Ignore lifecycle hooks during plugin install
2017-04-13 12:56:45 -06:00
gaba 9dae72dd8a Good catch. Use setState. 2017-04-13 11:44:08 -07:00
Benjamin Goering 118da885fd Merge branch 'master' of github.com:coralproject/talk into ignore_rb2 2017-04-13 12:12:10 -05:00
gaba abc7aa7b30 Uh... wrong element. 2017-04-12 18:39:56 -07:00
Belen Curcio 7969985f59 Merge branch 'master' of github.com:coralproject/talk into plugin_examples 2017-04-12 22:27:46 -03:00
Belen Curcio 22fc8555ad Extending typeDefs with Off-Topic Plugin 2017-04-12 22:26:30 -03:00
Belen Curcio d9d3f5e85b Tests and cleanup, 2 to go 2017-04-12 21:17:07 -03:00
Belen Curcio 32defa66e6 Clean up 2017-04-12 21:04:47 -03:00
Belen Curcio 2e37512851 Deleting useless constructor 2017-04-12 20:54:11 -03:00
Belen Curcio 78286d3374 Running CommentBox reducers and triggering actions from Plugins! 2017-04-12 20:51:18 -03:00
Wyatt Johnson 941db338d0 Updates for plugin deployment 2017-04-12 16:51:20 -06:00
Belen Curcio 7424fc9774 Create comments with Tags 2017-04-12 19:42:39 -03:00
Wyatt Johnson d5016dff58 Updates to metadata service docs 2017-04-12 16:37:56 -06:00
Belen Curcio 7fca451263 Immutable plugins 2017-04-12 19:21:55 -03:00
Wyatt Johnson dbc95db805 Updates for docker images 2017-04-12 15:15:51 -06:00
Gabriela Rodríguez Berón 42a1026f9c Merge branch 'master' into 143520971-stream-settings 2017-04-12 14:12:15 -07:00
Wyatt Johnson 9e16e58d19 Merge branch 'master' into docs-update 2017-04-12 15:10:22 -06:00
gaba e04f0f7070 Moves settings into the state for the stream. Adds premodlinks to the query. 2017-04-12 14:08:55 -07:00
Wyatt Johnson 2fcfaa8a99 More updates 2017-04-12 15:08:07 -06:00
Wyatt Johnson f7ee08bd3a Ignore lifecycle hooks during plugin install 2017-04-12 14:39:11 -06:00
Wyatt Johnson 31960d67f3 Added more to docs 2017-04-12 14:25:43 -06:00
David Erwin b3ce4507a3 Merge pull request #476 from coralproject/metadata
MetadataService
2017-04-12 16:02:39 -04:00
David Erwin c7e1943272 Merge branch 'master' into metadata 2017-04-12 15:57:24 -04:00
Belen Curcio c0913d8e98 Disable postButton and enable 2017-04-12 16:55:43 -03:00
Belen Curcio e8d9f8aab8 fix createComment Mutations 2017-04-12 16:51:41 -03:00
Kim Gardner 3e882452db Merge pull request #491 from coralproject/fix-broken-links
Fix broken links to Stories page
2017-04-12 15:45:01 -04:00
Kim Gardner 29fe5e2901 Fix broken links to Stories page 2017-04-12 15:40:34 -04:00
Wyatt Johnson 6c0e00155f Initial update 2017-04-12 13:29:01 -06:00
Belen Curcio 3837c316d5 Merge branch 'plugin_examples' of github.com:coralproject/talk into plugin_examples 2017-04-12 16:25:48 -03:00
Belen Curcio 336dd261ca createCommentInput 2017-04-12 16:23:13 -03:00
Kim Gardner 29aacdc00e Merge pull request #490 from coralproject/fix-more-buttons
Fix load more buttons
2017-04-12 15:18:06 -04:00
Kim Gardner d892d1327d Merge branch 'master' into fix-more-buttons 2017-04-12 15:12:27 -04:00
Kim Gardner 309b41a3cf Merge pull request #485 from coralproject/143520139-permalink-show-all
Clicking '# comments' tab refresh all the comments back
2017-04-12 15:11:42 -04:00
gaba cbde4cc8bc Removes handleClick as it is working now with the changeTab (as the viewAllComments was added). /cc @kiwi 2017-04-12 11:58:41 -07:00
Chi Vinh Le 81bd983f0b Handle empty stream case 2017-04-13 01:34:27 +07:00
Chi Vinh Le 92205a2c6f Fix load more buttons 2017-04-13 01:17:46 +07:00
gaba 48795b29bf Removes isAdmin for refetching when changing tabs. 2017-04-12 10:41:15 -07:00
gaba f4f8e5053f Remove the binding in the constructor. 2017-04-12 10:12:00 -07:00
Gabriela Rodríguez Berón 51774323de Merge branch 'master' into 143520139-permalink-show-all 2017-04-12 10:10:17 -07:00
Belen Curcio e256c4a014 Using Arrays instead of objects for Hooks 2017-04-12 13:50:56 -03:00
Belen Curcio 0254086c28 Using Arrays instead of objects for Hooks 2017-04-12 13:45:42 -03:00
Wyatt Johnson 191026ecf2 Merge branch 'master' into metadata 2017-04-12 10:37:12 -06:00
Belén Curcio 19f402ddc8 Merge branch 'master' into plugin_examples 2017-04-12 11:28:08 -03:00
Wyatt Johnson c19e2979fc Merge pull request #489 from coralproject/fix-double-quote-typos
Double quotes should be single
2017-04-12 08:14:02 -06:00
Kim Gardner a19d6544a7 Don't need to escape single quotes within double quotes 2017-04-12 10:05:21 -04:00
Kim Gardner d74cdd938f Double quotes should be single 2017-04-12 09:51:32 -04:00
Wyatt Johnson d4aa2011ff Merge pull request #488 from coralproject/bump-version-update-description
Bump version and update description
2017-04-12 07:39:12 -06:00
Kim Gardner 8e76a34631 Bump version and update description 2017-04-12 08:45:19 -04:00
Belen Curcio ac632a69d2 unregister hooks names 2017-04-12 09:40:18 -03:00
Belen Curcio 72c698231b unregister hooks 2017-04-12 09:33:24 -03:00
Kim Gardner 143ce795c3 Merge branch 'master' into 143520139-permalink-show-all 2017-04-12 08:14:39 -04:00
Belen Curcio 83eb6492c2 Styles 2017-04-12 08:54:50 -03:00
Belén Curcio 7590cee986 Merge branch 'master' into plugin_examples 2017-04-12 08:52:03 -03:00
Kim Gardner 358743b3ab Merge pull request #486 from coralproject/styling-fixes
Styling fixes
2017-04-12 07:37:14 -04:00
Belén Curcio f313d2a7d2 Merge branch 'master' into plugin_examples 2017-04-12 08:32:39 -03:00
Belén Curcio 1f862cce74 Merge branch 'master' into 143520139-permalink-show-all 2017-04-12 08:27:37 -03:00
Kim Gardner 6f4d591d0b Merge branch 'master' into styling-fixes 2017-04-12 07:23:56 -04:00
Kim Gardner c084c92c31 Merge pull request #487 from coralproject/validator-fix
Fixed plugin validation
2017-04-12 07:23:05 -04:00
Belen Curcio 1a678a0cb3 Removing username -- with lintin 2017-04-12 08:21:39 -03:00
Belen Curcio cc0f757746 Removing username 2017-04-12 08:16:02 -03:00
Belén Curcio 2ecf64ae3e Merge branch 'master' into plugin_examples 2017-04-12 08:09:49 -03:00
Wyatt Johnson 6ac4bcf796 Fixed plugin validation 2017-04-11 22:25:13 -06:00
Belen Curcio 2e3021e66b Linting 2017-04-11 22:17:04 -03:00
Belen Curcio 63b630833a Fake Comment Style 2017-04-11 22:12:10 -03:00
Belen Curcio e4aa98b87a Hidding login as 2017-04-11 22:00:55 -03:00
gaba f900727455 Move the method to the class, changes name and removes wrapper. 2017-04-11 17:57:56 -07:00
Belen Curcio 3c6ce7a80f QuestionBox 2017-04-11 21:56:43 -03:00
Gabriela Rodríguez Berón f44798361e Merge branch 'master' into 143520139-permalink-show-all 2017-04-11 15:06:22 -07:00
gaba 56dedb551f Adds click to showAll and refresh query to the '# comments' tab. 2017-04-11 15:02:04 -07:00
Kim Gardner b15fd7acbf Merge pull request #484 from coralproject/plugin-fixes
Fixed bugs with plugin install
2017-04-11 17:56:13 -04:00
Kim Gardner 1dcfa269b3 Merge branch 'master' into plugin-fixes 2017-04-11 17:51:18 -04:00
Kim Gardner 36dff1afb8 Merge pull request #483 from coralproject/143202269-suspend-user-email
Adds missing template.
2017-04-11 17:51:02 -04:00
Benjamin Goering fbcea2de6d Merge branch 'master' into ignore 2017-04-11 14:49:56 -07:00
Kim Gardner 3307bf5ef5 Merge branch 'master' into 143202269-suspend-user-email 2017-04-11 17:46:58 -04:00
Kim Gardner f6cb97e29c Merge pull request #482 from coralproject/flash-messages
Flash Messages fix
2017-04-11 17:46:44 -04:00
Benjamin Goering 247583b858 Merge branch 'ignore' of github.com:gobengo/talk into ignore 2017-04-11 14:33:10 -07:00
Benjamin Goering 8cf2130e25 Update spanish translation for commentIsIgnored 2017-04-11 14:31:46 -07:00
Gabriela Rodríguez Berón 295f4ddec2 Merge branch 'master' into ignore 2017-04-11 14:26:53 -07:00
Kim Gardner 7ab8dbf3c6 Merge branch 'master' into flash-messages 2017-04-11 17:22:02 -04:00
Kim Gardner 4ce37a4bbb Merge pull request #480 from coralproject/infobox-style
Infobox Style
2017-04-11 17:21:53 -04:00
Benjamin Goering a7be2772c3 IgnoredCommentTombstone body text is more i18n 2017-04-11 14:20:39 -07:00
Benjamin Goering 847ad2263b Split IgnoreUserWizard into own file 2017-04-11 14:20:39 -07:00
Benjamin Goering eb1c1db09b Ignore users queries are excludeIgnored: Boolean instead of notIgnoredBy: String 2017-04-11 14:20:39 -07:00
Benjamin Goering 12e383ef13 refetch streamQuery on stopIgnoringUser 2017-04-11 14:20:39 -07:00
Benjamin Goering a43e713ac0 Move TopRightMenu down 5px 2017-04-11 14:20:38 -07:00
Benjamin Goering 0dff670b70 Slot component wraps in inline element, not block (which can affect other styles) 2017-04-11 14:20:38 -07:00
Benjamin Goering 58ffd61884 Move TopRightMenu component out of Comment and into own file 2017-04-11 14:20:38 -07:00
Benjamin Goering 4d2e5ed81f Comments by logged in user dont show TopRightMenu + IgnoreUserWizard 2017-04-11 14:20:38 -07:00
Benjamin Goering 7dfe12b874 Users cannot ignore themselves at service layer 2017-04-11 14:20:37 -07:00
Benjamin Goering 58736586e4 Asset commentCount and totalCommentCount can be personalized by notIgnoredBy 2017-04-11 14:20:37 -07:00
Benjamin Goering b561e9f643 Comment replyCount edge can now be filtered by notIgnoredBy 2017-04-11 14:20:37 -07:00
Benjamin Goering 873da1cea9 Embed doesn't show comments from ignored users 2017-04-11 14:20:37 -07:00
Benjamin Goering c7d8c40770 Rename embed's My Comments tab to 'My profile' 2017-04-11 14:19:34 -07:00
Benjamin Goering 80658f51d1 Handle errors when ignoring a user 2017-04-11 14:19:34 -07:00
Benjamin Goering 2656141d59 Add stopIgnoringUser mutation and Embed uses it 2017-04-11 14:19:34 -07:00
Benjamin Goering c59ddb72a6 Manage ignored users UI 2017-04-11 14:19:34 -07:00
Benjamin Goering 70ec5d5f54 Remove font-size: 100% for all elements in embed-stream 2017-04-11 14:19:33 -07:00
Benjamin Goering bc3ec84180 IgnoredCommentTombstone renders for replies too 2017-04-11 14:19:33 -07:00
Benjamin Goering 803db345ac TopRightMenu no longer shows a list of choices, just IgnoreUserWizard 2017-04-11 14:19:33 -07:00
Benjamin Goering 990b9794e9 When a user is ignored, replace their comments with a tombstone 2017-04-11 14:19:33 -07:00
Benjamin Goering a9412c17c9 Ignore User UI actually sends mutation 2017-04-11 14:19:32 -07:00
Benjamin Goering 545f176bb8 Add ignoreUser mutation and myIgnoredUsers root query 2017-04-11 14:19:32 -07:00
Benjamin Goering d3d7045227 IgnoreUserWizard steps and proper chevron 2017-04-11 14:19:32 -07:00
Benjamin Goering 2dc882a12e Start UI for TopRightMenu and IgnoreUserWizard on Comments 2017-04-11 14:19:32 -07:00
Wyatt Johnson 3a3b89b03b Fixed bugs with plugin install 2017-04-11 14:59:02 -06:00
Gabriela Rodríguez Berón 152a9417b2 Merge branch 'master' into plugin_examples 2017-04-11 13:56:27 -07:00
Kim Gardner 313e368565 Merge branch 'master' into flash-messages 2017-04-11 15:28:00 -04:00
Belén Curcio 8c627c5a7b Merge branch 'master' into 143202269-suspend-user-email 2017-04-11 16:25:44 -03:00
Kim Gardner 7838347861 Merge branch 'master' into infobox-style 2017-04-11 15:23:58 -04:00
gaba c5520e3fb6 Adds missing template. 2017-04-11 12:22:05 -07:00
Belén Curcio 972cce4e8f Merge pull request #481 from coralproject/143202225-search-community
Fix search box in Community Admin.
2017-04-11 16:21:09 -03:00
Belen Curcio 93ca0bf79d fixed flash messages 2017-04-11 16:14:02 -03:00
gaba a7493af979 Fix search box in Community Admin. 2017-04-11 11:58:19 -07:00
Belen Curcio 9335856474 Links style 2017-04-11 15:55:18 -03:00
Belen Curcio a27044b691 Ádding style for the infoBox 2017-04-11 15:49:10 -03:00
Belén Curcio a77a623e94 Merge branch 'master' into plugin_examples 2017-04-11 15:13:51 -03:00
Belen Curcio 5d81429673 Clean up 2017-04-11 14:47:39 -03:00
Belen Curcio 536b2ed371 Liniting 2017-04-11 13:46:27 -03:00
Belen Curcio aeaa9ae80f Adding preSubmit hoojs 2017-04-11 12:42:48 -03:00
Belen Curcio 014c23bb95 Support for hooks, preSubmit and postSubmit 2017-04-11 12:41:18 -03:00
Kim Gardner 826f051175 Merge pull request #472 from coralproject/fix-respect-button-bugs
Fix quick mutations and multiple respects
2017-04-11 11:29:39 -04:00
Kim Gardner 4c1ae22765 Merge branch 'master' into fix-respect-button-bugs 2017-04-11 11:24:23 -04:00
Kim Gardner b7c2861a28 Merge pull request #475 from coralproject/fix-fetch-more
Adjust fetchMore for apollo 1.0
2017-04-11 11:24:12 -04:00
Kim Gardner 97666df1e0 Merge branch 'master' into fix-fetch-more 2017-04-11 11:18:19 -04:00
Kim Gardner 34486028e5 Merge pull request #478 from coralproject/disable-profanity-username-check
Temporarily disable username profanity check
2017-04-11 11:18:07 -04:00
David Erwin 2c8d03bd8d Temporarily disable username profanity check 2017-04-11 11:05:55 -04:00
Belen Curcio 6aad4411d9 Styling for plugins 2017-04-11 11:57:08 -03:00
Belen Curcio 917be12667 Merge branch 'master' of github.com:coralproject/talk into plugin_examples 2017-04-11 11:55:12 -03:00
Kim Gardner b2ee1f0c57 Merge branch 'master' into fix-respect-button-bugs 2017-04-11 10:48:41 -04:00
Belen Curcio e888161fe4 off topic plugins, and examples 2017-04-11 11:23:23 -03:00
Wyatt Johnson f648d923a3 Merge branch 'master' into metadata 2017-04-10 13:38:24 -06:00
Wyatt Johnson 8e33d25a28 Some bug fixes + validation updates 2017-04-10 12:12:07 -06:00
Chi Vinh Le b4e8648177 Adjust fetchMore for apollo 1.0 2017-04-10 21:16:07 +07:00
Kim Gardner bcf31cd19a Merge pull request #474 from coralproject/losowsky-patch-1
Took the Trello board out of the doc
2017-04-10 07:44:03 -04:00
Andrew Losowsky 63850acf73 Took the Trello board out of the doc
We haven't been using that. We use google doc instead, which isn't public because it contains a lot of WaPo notes.
2017-04-07 19:25:49 -04:00
Wyatt Johnson 735aa6fbd3 Added new Metadata Service and models 2017-04-07 16:40:30 -06:00
David Erwin 0a0f82cd5a Merge pull request #464 from coralproject/optional-google-fonts
do not load google fonts in embed
2017-04-07 13:50:39 -04:00
Belen Curcio aef0f433c9 Slots and off topic plugin 2017-04-07 14:12:51 -03:00
Belen Curcio c6189e8b3a Slots and off topic plugin 2017-04-07 14:05:23 -03:00
Riley Davis ca04db80dc Merge branch 'master' into optional-google-fonts 2017-04-07 10:42:20 -06:00
Kim Gardner 029b00033e Merge pull request #473 from coralproject/import-fix
Fixed support for imports
2017-04-07 12:27:49 -04:00
Wyatt Johnson a4d631546a Fixed support for imports 2017-04-07 09:37:56 -06:00
Chi Vinh Le dfe8e88faa Fix quick mutations and multiple respects 2017-04-07 21:40:36 +07:00
Belén Curcio 79d9e24cb6 Merge pull request #455 from coralproject/plugin
Plugin Architecture and Respect Button Plugin
2017-04-06 22:12:06 -03:00
Wyatt Johnson 5a4441e309 Adjusted loaders, webpack 2017-04-06 17:42:50 -06:00
Belen Curcio 5245dfaef9 Rolling back since its not needed 2017-04-06 19:44:26 -03:00
Belen Curcio 8b9e69e8a6 Merge branch 'plugin' of github.com:coralproject/talk into plugin 2017-04-06 19:06:34 -03:00
Belen Curcio 16f15c5faf Adding todo 2017-04-06 19:05:46 -03:00
Wyatt Johnson 4377a48e03 Fixes to validation 2017-04-06 16:02:31 -06:00
Belen Curcio 3bd3bca09a Ádding proptypes to RespectButton 2017-04-06 19:02:02 -03:00
Belen Curcio a79a5128c9 plugins default json 2017-04-06 18:57:11 -03:00
Belen Curcio 27408e1fa1 Linting and Patch 2017-04-06 18:49:12 -03:00
Belen Curcio 5e2d64709c Linting and Patch 2017-04-06 18:46:49 -03:00
Belen Curcio f779a55096 Merge branch 'master' of github.com:coralproject/talk into plugin 2017-04-06 18:33:17 -03:00
Belen Curcio a2df5df58c Merge branch 'plugin' of github.com:coralproject/talk into plugin 2017-04-06 18:30:51 -03:00
Belen Curcio e1ebba5b1c Ádding PropTypes to Slot 2017-04-06 18:26:40 -03:00
Belen Curcio ad8c6cc140 Refator plugin loaders 2017-04-06 18:26:24 -03:00
Belen Curcio ae5bcf6900 getActionSummary exp 2017-04-06 18:15:02 -03:00
Wyatt Johnson b2563ca766 Merge pull request #469 from coralproject/plugin-validation
Added joi plugin validation
2017-04-06 15:10:20 -06:00
Riley Davis acb5686c66 Merge branch 'master' into optional-google-fonts 2017-04-06 15:10:05 -06:00
Wyatt Johnson 54518af2a5 Merge branch 'master' into plugin-validation 2017-04-06 15:04:24 -06:00
Kim Gardner 3b24e3c045 Merge pull request #470 from coralproject/plugins-default-json
Added plugins.default.json and TALK_PLUGINS_JSON
2017-04-06 17:04:11 -04:00
Chi Vinh Le 449152d400 When using updateQueries no refetch is needed 2017-04-07 03:43:14 +07:00
Belén Curcio 10836f7918 Merge branch 'master' into plugin 2017-04-06 17:04:38 -03:00
Chi Vinh Le e6ada54d0e Fix optimistic ui 2017-04-07 02:55:54 +07:00
Kim Gardner 770a4e6fe2 Fix typo 2017-04-06 14:38:16 -04:00
Wyatt Johnson 4bc7848e24 comment updates 2017-04-06 12:00:36 -06:00
Wyatt Johnson 943d4cf98a Spelling 2017-04-06 12:00:00 -06:00
Wyatt Johnson a9c88f52ce Optionally load plugins config from environment 2017-04-06 11:58:18 -06:00
Wyatt Johnson fb8b33b2eb Added plugins.default.json 2017-04-06 11:43:18 -06:00
Wyatt Johnson 28ce77a002 Moved validation to one place 2017-04-06 10:56:32 -06:00
Riley Davis 9cab34da73 Merge branch 'master' into optional-google-fonts 2017-04-06 10:40:04 -06:00
Wyatt Johnson 1f367e666b Added joi plugin validation 2017-04-06 10:39:36 -06:00
Chi Vinh Le e92fdc97bb Refetch Me after facebook login 2017-04-06 22:49:20 +07:00
David Erwin e70cfdfd00 Merge pull request #459 from coralproject/support-lazy-assets
Added support for a plugin which disables lazy assets
2017-04-06 10:54:32 -04:00
David Erwin 58f296ef88 Merge branch 'support-lazy-assets' of https://github.com/coralproject/talk into support-lazy-assets 2017-04-06 10:49:30 -04:00
David Erwin 79d5c28d00 Pass asset_id in title route 2017-04-06 10:47:56 -04:00
Chi Vinh Le 7c2b16521a Merge branch 'master' into plugin
Conflicts:
	.eslintignore
	.gitignore
	graph/resolvers/index.js
	plugins.json
2017-04-06 21:22:40 +07:00
Wyatt Johnson 26fff5b950 Merge branch 'master' into support-lazy-assets 2017-04-06 08:21:49 -06:00
David Erwin 2b23a6625f Merge pull request #466 from coralproject/stream-refetch-tweak
only refetch the comments if you're an admin
2017-04-06 10:13:46 -04:00
David Erwin 6f8b375519 Merge branch 'master' into stream-refetch-tweak 2017-04-06 10:03:00 -04:00
David Erwin 73344fa528 Merge pull request #467 from coralproject/fix-collapsing-comments
Fix collapsing comment tree
2017-04-06 10:02:29 -04:00
David Erwin 6117dd3358 Merge branch 'master' into fix-collapsing-comments 2017-04-06 09:57:35 -04:00
Chi Vinh Le 31cdf964f7 Simple camelCase for slot naming 2017-04-06 20:08:41 +07:00
Chi Vinh Le 025df1918d Move slot config to index 2017-04-06 20:06:38 +07:00
Chi Vinh Le 92146f326f camelCase for slot names 2017-04-06 19:55:24 +07:00
Chi Vinh Le a018a8f9bb Fix lint and git ignores 2017-04-06 19:45:09 +07:00
Chi Vinh Le 5cb9fdccf9 Refactor plugin 2017-04-06 19:26:29 +07:00
Chi Vinh Le b64e46c007 Improve performance of RespectButton 2017-04-06 18:31:02 +07:00
Chi Vinh Le 4d51fb276d Fix collapsing comment tree 2017-04-06 17:51:36 +07:00
Chi Vinh Le be64905426 Fix graph not importing plugin resolvers 2017-04-06 17:45:34 +07:00
Chi Vinh Le f91f2b143f Fix undefined client 2017-04-06 17:17:29 +07:00
Chi Vinh Le d32c2f5177 Fix collapsing comment tree 2017-04-06 16:44:50 +07:00
Wyatt Johnson 990fe34873 Merge branch 'master' into support-lazy-assets 2017-04-05 16:21:16 -06:00
Riley Davis 7b7686bc79 Merge branch 'master' into optional-google-fonts 2017-04-05 16:10:41 -06:00
Riley Davis ca2782ba06 Merge branch 'master' into stream-refetch-tweak 2017-04-05 15:40:48 -06:00
David Erwin bbf0839890 Merge pull request #465 from coralproject/graph-fix
Fixed for resolver plugin syntax
2017-04-05 17:00:16 -04:00
Wyatt Johnson dfcb661c21 Syntax 2017-04-05 14:47:27 -06:00
Riley Davis e4e2ff03b3 only refetch the comments if you're an admin 2017-04-05 14:41:06 -06:00
Wyatt Johnson c5ce22c5e6 Fixed for syntax 2017-04-05 14:36:35 -06:00
Kim Gardner 44aa98eb28 Merge branch 'master' into optional-google-fonts 2017-04-05 16:05:13 -04:00
David Erwin 5dbeb695a4 Merge pull request #463 from coralproject/facebook-auth-plugin
Facebook Auth Plugin
2017-04-05 15:31:53 -04:00
Wyatt Johnson a270aef42e fixed license 2017-04-05 13:07:56 -06:00
Wyatt Johnson 2bbbf2bbf7 Fixed bug in plugin 2017-04-05 12:33:24 -06:00
Chi Vinh Le b0effe8731 Merge with 'master' 2017-04-06 01:26:48 +07:00
Riley Davis b24ee81e34 remove Lato from google fonts 2017-04-05 12:14:51 -06:00
Chi Vinh Le baf5a34001 Fully working redux+apollo powered plugin 2017-04-06 00:58:16 +07:00
Chi Vinh Le 582f799b6c Revert "Merge"
This reverts commit d9835a17d3, reversing
changes made to 18d6846f4e.
2017-04-06 00:53:21 +07:00
Wyatt Johnson 21eb532c6e Added postinstall hook 2017-04-05 11:49:22 -06:00
Chi Vinh Le 9505ffdedd Revert "to Master"
This reverts commit eef64f5f1b.
2017-04-06 00:44:12 +07:00
Wyatt Johnson 8a06118d94 Added coral facebook auth plugin, some circ dep fixes 2017-04-05 11:39:54 -06:00
Belen Curcio eef64f5f1b to Master 2017-04-05 12:12:33 -03:00
Belen Curcio d9835a17d3 Merge 2017-04-05 12:08:07 -03:00
Wyatt Johnson 877372814a Merge branch 'master' into support-lazy-assets 2017-04-05 08:14:31 -06:00
David Erwin 97727118dd Merge pull request #461 from coralproject/report-apollo-errors
Implement apollo reporter middleware
2017-04-05 09:30:57 -04:00
David Erwin 74596b9e08 Merge branch 'master' into report-apollo-errors 2017-04-05 09:24:41 -04:00
David Erwin 9c3450ebad Merge pull request #462 from coralproject/adjust-editorconfig
Adjust .editorconfig
2017-04-05 09:22:57 -04:00
Chi Vinh Le 4aa8b5f234 Adjust .editorconfig 2017-04-05 19:49:36 +07:00
Chi Vinh Le 18d6846f4e Implement apollo reporter middleware 2017-04-05 19:37:10 +07:00
Chi Vinh Le 4a49a910aa Remove plugin love 2017-04-05 19:33:50 +07:00
Chi Vinh Le 18a29bf0df Restructure frontend plugins 2017-04-05 19:33:28 +07:00
Chi Vinh Le b3d1053fd7 Get rid of plugin actions in the framework 2017-04-05 18:57:12 +07:00
Chi Vinh Le f6a5d9f721 Remove context for plugins 2017-04-05 18:46:54 +07:00
Chi Vinh Le 765bee40b4 Update apollo 2017-04-05 18:46:16 +07:00
Chi Vinh Le 528f5738c8 Implement apollo reporter middleware 2017-04-05 11:18:45 +07:00
David Erwin a4ec278125 Merge pull request #458 from coralproject/docker-plugins
Plugin Router/Passport/Deployment
2017-04-04 16:56:02 -04:00
Chi Vinh Le 6e2fd0851e Better graphQL id 2017-04-04 18:30:41 +07:00
Chi Vinh Le bdc3a79b27 Adjust respect button styles 2017-04-04 13:04:58 +07:00
Chi Vinh Le 5609715e77 Support object in plugins.json 2017-04-04 13:03:46 +07:00
Wyatt Johnson 2546902bcf Still grab the url when we have the id 2017-04-03 16:59:31 -06:00
Wyatt Johnson 52948a4165 Added support for a plugin which disables lazy assets 2017-04-03 16:51:14 -06:00
Wyatt Johnson c1dac11a2b Updated docs, hook names 2017-04-03 16:11:34 -06:00
Wyatt Johnson 8d3524dd61 Added support for auth based plugins 2017-04-03 16:06:38 -06:00
Wyatt Johnson c5098a3e91 Added docs 2017-04-03 15:37:31 -06:00
Wyatt Johnson febe264320 Added route support 2017-04-03 15:31:51 -06:00
Wyatt Johnson 368838df98 Spelling 2017-04-03 15:16:05 -06:00
Wyatt Johnson b5280b1221 Added support for plugin relative imports 2017-04-03 13:02:32 -06:00
Wyatt Johnson 9c806cb6e4 Added support for plugin upgrades 2017-04-03 11:58:54 -06:00
Wyatt Johnson 881f79a571 Adjusted error handling 2017-04-03 08:59:28 -06:00
Wyatt Johnson d4c7dac945 Adjusted plugins.json format, added docs 2017-04-03 08:53:40 -06:00
Chi Vinh Le 96dcd5f74a Rename module to getModule 2017-04-03 14:56:53 +07:00
Chi Vinh Le 37230ce7fa Add more docs to plugins loader 2017-04-03 14:48:16 +07:00
Chi Vinh Le 24aba5084b Only bind action creators once 2017-04-03 14:41:12 +07:00
Chi Vinh Le 512405b3f0 Fix module loading 2017-04-03 14:41:11 +07:00
Chi Vinh Le 62c4072631 Lazy load all plugin parts 2017-04-03 14:25:06 +07:00
Chi Vinh Le 73e2b899d8 Use custom plugins loader 2017-04-03 14:19:51 +07:00
Chi Vinh Le e0540a5031 Better comments 2017-04-03 12:11:30 +07:00
Chi Vinh Le e0a3e8a3c1 Merge branch 'plugin' into plugin-kiwi
Conflicts:
	plugins/coral-plugin-respect/index.js
2017-04-03 12:10:07 +07:00
Chi Vinh Le a2f1f8018f Defer loading plugin components 2017-04-03 12:03:28 +07:00
Wyatt Johnson c221dbb4a0 Added credits 2017-03-31 16:45:52 -06:00
Wyatt Johnson 1b8489165b Merge branch 'master' into docker-plugins 2017-03-31 16:41:34 -06:00
Wyatt Johnson f1239b4b19 Added support for local plugin dep install 2017-03-31 16:37:24 -06:00
Wyatt Johnson c4edab9bbd cleaned up cli 2017-03-31 16:09:56 -06:00
Wyatt Johnson 0108673566 Added remote plugin reconciliation 2017-03-31 15:54:39 -06:00
Kim Gardner 59c1656da3 Merge pull request #457 from coralproject/update-readme
Update readme
2017-03-31 16:06:55 -04:00
Kim Gardner f74d6071df update readme 2017-03-31 16:01:54 -04:00
Kim Gardner b93f1e2a7d Merge pull request #446 from coralproject/change-permalink-behavior
Change permalink behavior
2017-03-31 15:57:32 -04:00
Riley Davis 8196cb9c24 load all the replies in one go 2017-03-31 13:37:28 -06:00
Chi Vinh Le a5e463ea0c Fix outdated comment 2017-04-01 00:54:35 +07:00
Chi Vinh Le cc2af70962 Rename getSlotComponents to getSlotElements 2017-04-01 00:52:00 +07:00
Chi Vinh Le 5a5f1bc508 Refractor plugins helper 2017-04-01 00:48:59 +07:00
Chi Vinh Le 56a1503e3a Read plugin typeDefs from graphql file 2017-04-01 00:48:17 +07:00
Belen Curcio c4decbd844 Adding eslinrc 2017-03-31 14:31:44 -03:00
Belen Curcio 0a77fe92bd Adding translations and fully working respect button and styles 2017-03-31 14:23:03 -03:00
riley ba4f39aecd Merge branch 'master' into change-permalink-behavior 2017-03-31 10:10:34 -06:00
Wyatt Johnson 1da5c570bf Fixed syntax error 2017-03-31 09:41:18 -06:00
Belen Curcio 004e8d5fe9 Using wrapResponse 2017-03-31 12:37:11 -03:00
Riley Davis ca4c79681b Merge pull request #456 from coralproject/prevent-collapse-from-css
prevent Talk from collapsing from custom css
2017-03-31 09:36:04 -06:00
Wyatt Johnson e5da8354d6 Added frozen lockfile support 2017-03-31 09:34:38 -06:00
Riley Davis 093c13c103 add polling to permalink page 2017-03-31 09:16:21 -06:00
Riley Davis 72c1774cf3 prevent Talk from collapsing when a height is set to the body on the parent page 2017-03-31 09:12:09 -06:00
Belen Curcio 173e1c16ea Plugin now support statefull components 2017-03-30 22:51:16 -03:00
Belen Curcio e1c4b69038 Plugin now support statefull components 2017-03-30 22:10:24 -03:00
Belen Curcio 1ee49f76f6 Styles, utils and state 2017-03-30 20:08:53 -03:00
Belen Curcio 3d245c2029 Working respect count 2017-03-30 19:41:40 -03:00
Wyatt Johnson 78d072a04a Fixed server plugi 2017-03-30 16:28:06 -06:00
Belen Curcio cd08987502 postRespect 2017-03-30 19:17:22 -03:00
Belen Curcio b7e00f1da4 Merge branch 'plugin' of github.com:coralproject/talk into plugin 2017-03-30 19:05:35 -03:00
Wyatt Johnson 492d919053 Added babelrc to plugin 2017-03-30 15:59:49 -06:00
Wyatt Johnson 5fec7c3a11 Added support for resolveType post hooks 2017-03-30 15:55:41 -06:00
Belen Curcio 2d99206b51 Plugin api, state, actions, context 2017-03-30 18:25:25 -03:00
Wyatt Johnson da4eceafe9 Fix for deploy script 2017-03-30 14:19:42 -06:00
Riley Davis a1bb0cd55f Merge branch 'master' into change-permalink-behavior 2017-03-30 12:59:06 -06:00
Belen Curcio 1bb3ed2399 importer renamed, and properties changed 2017-03-30 14:52:17 -03:00
Wyatt Johnson 126387becc Adjusted docker build script 2017-03-30 11:37:18 -06:00
Riley Davis 87ff9306de Merge remote-tracking branch 'origin/change-permalink-behavior' into change-permalink-behavior 2017-03-30 10:51:16 -06:00
Riley Davis fd968b2070 position permalink popup given different comment scenarios 2017-03-30 10:50:58 -06:00
Belen Curcio 79cbdaf92b Merge branch 'plugin' of github.com:coralproject/talk into plugin 2017-03-30 13:33:00 -03:00
Belén Curcio 5fe96cf47c Merge branch 'master' into plugin 2017-03-30 13:22:29 -03:00
Kiwi b9f0633365 Merge pull request #454 from coralproject/markdown-infobox
Markdown InfoBox
2017-03-30 23:10:14 +07:00
Belen Curcio 52b6ce06e6 Linting 2017-03-30 13:07:32 -03:00
Belén Curcio 0ec582e411 Merge branch 'master' into markdown-infobox 2017-03-30 13:04:07 -03:00
Belen Curcio a7def6fc2b Adding the state of plugins to the main 2017-03-30 12:57:05 -03:00
Belen Curcio 7efbdc8b59 Plugins can trigger own actions with core reducers 2017-03-30 12:40:04 -03:00
Belen Curcio 679962168c Plugins can trigger own actions with core reducers 2017-03-30 12:24:19 -03:00
Chi Vinh Le 4c79f45556 Support Markdown in Infobox 2017-03-30 22:19:02 +07:00
Riley Davis f7f0a9d99e Merge pull request #453 from coralproject/upgrade-react
Upgrade React
2017-03-30 08:31:32 -06:00
David Erwin a22d7af8bb Merge branch 'master' into change-permalink-behavior 2017-03-30 10:20:58 -04:00
Belen Curcio 17a8f48b4f Plugins have access to ParentComponent Properties 2017-03-30 11:15:20 -03:00
Belen Curcio ab6f455ec7 Plugins: Reducers and Actions - Working 2017-03-30 11:10:11 -03:00
Chi Vinh Le 4d89db931e Integrate MarkdownEditor 2017-03-30 20:49:18 +07:00
Chi Vinh Le 2d0208046b Implement MarkdownEditor 2017-03-30 20:49:18 +07:00
Chi Vinh Le e878d1ebfd Upgrade React 2017-03-30 17:13:02 +07:00
Belen Curcio 659c63e5cb Adds graph functionality for plugins 2017-03-29 22:14:56 -03:00
Belen Curcio 9df334866d Adds graph functionality for plugins 2017-03-29 19:11:43 -03:00
Riley Davis f37c768662 only highlight the actual comment permalinked 2017-03-29 15:21:41 -06:00
Riley Davis 3007ba68f8 load more replies on a permalinked comment 2017-03-29 15:12:33 -06:00
Belen Curcio 4ca2a48bc9 typeDefs for respect added 2017-03-29 17:40:29 -03:00
Belen Curcio 635df54a2c Extended context 2017-03-29 17:21:31 -03:00
Belen Curcio cfb28c4c2a Merge 2017-03-29 16:42:59 -03:00
David Erwin 1e80991614 Merge pull request #445 from coralproject/dynamic-email-templates
hot-load email templates during development
2017-03-29 15:38:42 -04:00
Wyatt Johnson 0dc105405c Added new dockerfile for plugin development 2017-03-29 13:37:36 -06:00
Belen Curcio 5dc9811e06 Stable Version with reducers and actions 2017-03-29 16:24:09 -03:00
Wyatt Johnson ebcb3b4b02 Merge branch 'master' into dynamic-email-templates 2017-03-29 13:09:31 -06:00
Riley Davis f11a11cf67 show view more button on a permalinked comment if applicable 2017-03-29 13:04:28 -06:00
Kim Gardner c5d36a9f19 Merge branch 'master' into change-permalink-behavior 2017-03-29 14:49:21 -04:00
Kim Gardner f80f253b98 Merge pull request #452 from coralproject/bump-version-1-4-0
Bump version to 1.4.0
2017-03-29 14:49:02 -04:00
Kim Gardner 71d2baa010 Bump version to 1.4.0 2017-03-29 14:43:19 -04:00
Kim Gardner 55973859ef Merge branch 'master' into change-permalink-behavior 2017-03-29 14:29:16 -04:00
Kim Gardner 6548a76bfb Merge pull request #451 from coralproject/username-fix
Fixes issues with wordlist and usernames
2017-03-29 14:28:57 -04:00
riley 009b381744 Merge branch 'master' into dynamic-email-templates 2017-03-29 12:28:14 -06:00
riley 3e00a4e842 Merge branch 'master' into change-permalink-behavior 2017-03-29 12:27:52 -06:00
Kim Gardner 57c1e58bc3 Merge branch 'master' into username-fix 2017-03-29 14:10:24 -04:00
Kim Gardner 0726603a4e Merge pull request #447 from coralproject/total-counts
show the total comment count instead of the parent count
2017-03-29 14:10:05 -04:00
Kim Gardner 9d59c95cdf Merge branch 'master' into total-counts 2017-03-29 13:55:21 -04:00
Kiwi 771f99cee8 Merge pull request #450 from coralproject/rm_duplicate_css
Remove duplicate material.css
2017-03-30 00:52:28 +07:00
Belen Curcio 6bb9d23fdc Stable Version with reducers and actions 2017-03-29 14:46:45 -03:00
Belen Curcio 520392829b Stable Version with reducers and actions 2017-03-29 14:45:59 -03:00
Chi Vinh Le 00a74f05cb Use standard mdl theme 2017-03-30 00:44:26 +07:00
Wyatt Johnson 8680d6cab8 Fixes issues with wordlist and usernames
- Ensure that the wordlist can't contain emoji's
- Ensure that username checks user the correct tokenizer
2017-03-29 10:57:12 -06:00
Riley Davis 184b6c00a6 Merge branch 'master' into total-counts 2017-03-29 10:06:41 -06:00
Riley Davis 870fcac3b1 Merge branch 'master' into dynamic-email-templates 2017-03-29 09:31:24 -06:00
Chi Vinh Le d80ed467f1 Remove duplicate material.css 2017-03-29 21:41:22 +07:00
Kim Gardner 8435358879 Merge pull request #448 from pablocubico/patch-1
Found some typos on the spanish side :)
2017-03-29 07:53:24 -04:00
Pablo Cúbico 5092b849b3 Found some typos on the spanish side :) 2017-03-28 23:22:12 -03:00
Riley Davis daa6ce60ab show the total comment count instead of the parent count 2017-03-28 15:41:27 -06:00
Riley Davis b7ce669cfc add translations 2017-03-28 14:46:01 -06:00
Riley Davis e75663f5b9 Merge branch 'master' into change-permalink-behavior 2017-03-28 14:31:53 -06:00
Riley Davis 4e60689fa4 view a permalink thread in isolation 2017-03-28 14:31:43 -06:00
Belen Curcio 2513ef2df5 add plugins 2017-03-28 15:56:24 -03:00
Belen Curcio b76fa94354 Stable version 2017-03-28 14:21:06 -03:00
Belen Curcio 1a115e4f2c Stable version 2017-03-28 14:20:12 -03:00
Wyatt Johnson e7c0c5b638 adjusted path 2017-03-28 09:54:27 -06:00
Belén Curcio 4ff683b3af Merge branch 'master' into dynamic-email-templates 2017-03-28 11:20:22 -03:00
David Erwin 4f1f85d127 Merge pull request #443 from coralproject/community-flags
Community flags
2017-03-28 10:19:37 -04:00
Gabriela Rodríguez Berón 155be523ff Merge branch 'master' into community-flags 2017-03-28 07:14:30 -07:00
Belen Curcio 697d2f0ec1 Importer, Slots and suport for multiple plugins and props! 2017-03-28 09:58:33 -03:00
Riley Davis 3a47dd4c4b Merge branch 'master' into dynamic-email-templates 2017-03-27 12:44:52 -06:00
Riley Davis 2ec5b243fb hot-load email templates during development 2017-03-27 12:43:03 -06:00
Kim Gardner 0769a836d1 Merge pull request #444 from coralproject/fix-typo
Fix typo
2017-03-27 14:16:11 -04:00
Kim Gardner 68584cf2cf Fix typo 2017-03-27 13:59:45 -04:00
Belen Curcio e85ebba21b Working plugin importer 2017-03-27 13:02:57 -03:00
Kim Gardner 8a8700ebb2 Merge pull request #442 from coralproject/dep-upgrade
Package upgrades
2017-03-27 11:19:05 -04:00
Kim Gardner 542a261e5e Merge branch 'master' into dep-upgrade 2017-03-27 11:15:11 -04:00
Kim Gardner d539e23c00 Merge pull request #441 from coralproject/auto-close-comment-toggle
Auto close comment toggle
2017-03-27 11:07:40 -04:00
Belén Curcio a4dabd0308 Merge branch 'master' into dep-upgrade 2017-03-27 08:41:17 -03:00
Riley Davis c35043f799 Merge branch 'master' into auto-close-comment-toggle 2017-03-24 16:50:47 -06:00
gaba a6fa9a22c3 It refresh the page after editing name. 2017-03-24 13:57:10 -07:00
Kim Gardner a1f200de37 Merge pull request #436 from coralproject/translations
Translations
2017-03-24 16:46:21 -04:00
gaba a2551a499f Adds a few more font-sizes. 2017-03-24 12:58:45 -07:00
gaba 680919dd0e Making more sense. 2017-03-24 11:53:51 -07:00
Gabriela Rodríguez Berón c51a4ff228 Merge branch 'master' into translations 2017-03-24 11:33:16 -07:00
gaba b5d86a3380 Merge branch 'master' into community-flags 2017-03-24 11:19:30 -07:00
gaba 9eaed90318 Revert "Revert "Story 138187767 - Moderate Flag User Names""
This reverts commit 903aa23f19.
2017-03-24 11:15:42 -07:00
Riley Davis 2b90f2f588 Merge branch 'master' into auto-close-comment-toggle 2017-03-24 11:33:17 -06:00
Riley Davis 8114608626 put the mongoose update opts in the right place 2017-03-24 11:33:06 -06:00
Kim Gardner 596b7f538a Merge pull request #439 from coralproject/highlighter-wordz
highlight suspect, banned words and links
2017-03-24 13:19:26 -04:00
Wyatt Johnson 228fdeac4d Removed root .babelrc file, upgrades
- Upgraded packages
- Fixed sourcemaps
- Moved .babelrc config into client folder
- Created .babelrc in tests folder for client tests
2017-03-24 11:12:57 -06:00
Kim Gardner 67da144391 Merge branch 'master' into highlighter-wordz 2017-03-24 12:58:24 -04:00
Kim Gardner deb1d558fe Merge pull request #438 from coralproject/keep-on-keeping-on
always show moderation link to admins
2017-03-24 12:55:50 -04:00
riley 701994f8f7 pass lint 2017-03-24 10:42:31 -06:00
riley 5d5ad2585d autoclose an article toggle 2017-03-24 10:40:12 -06:00
Wyatt Johnson 05efbc4ded Updated lockfile 2017-03-24 10:15:03 -06:00
gaba 323749915a CSS alignments 2017-03-24 08:59:43 -07:00
gaba 84f1d3ece2 Use findONeAndUpdate and the moderation queue for account flags should only give back pending accounts. 2017-03-23 16:02:25 -07:00
riley 6b6d3d8bbb highlight suspect, banned words and links 2017-03-23 16:09:36 -06:00
gaba b9c816285d Fix params for moduserflaggedquery. 2017-03-23 14:48:15 -07:00
Belen Curcio 3be9b13691 First draft of plugins working :) 2017-03-23 16:59:11 -03:00
Riley Davis 7f1b993ea6 always show moderation link to admins 2017-03-23 13:04:37 -06:00
Riley Davis c1baccae5a Merge pull request #435 from coralproject/reset-password-redirect
redirect to the parent url when resetting your password
2017-03-23 12:59:31 -06:00
Riley Davis 984514d7eb check whitelist before redirecting 2017-03-23 12:46:20 -06:00
Riley Davis 67e04bf156 Merge branch 'master' into reset-password-redirect 2017-03-23 10:29:05 -06:00
Belen Curcio 2e80d2a36e fixing and separating broken tests /client /server 2017-03-23 12:27:30 -03:00
David Erwin 7e65ba6371 Merge pull request #437 from coralproject/revert-370-story-138187767-mod-flag-names
Revert "Story 138187767  - Moderate Flag User Names"
2017-03-23 11:22:28 -04:00
David Erwin 903aa23f19 Revert "Story 138187767 - Moderate Flag User Names" 2017-03-23 11:18:55 -04:00
David Erwin 03aebd7840 Merge pull request #432 from coralproject/my-comments-ux
My Comments / Profile Tab UX
2017-03-23 10:57:40 -04:00
David Erwin 53ba313f57 Merge branch 'master' into my-comments-ux 2017-03-23 10:53:47 -04:00
David Erwin da8e632fd3 Merge pull request #433 from coralproject/design-more
Design UX - #141430305
2017-03-23 10:53:32 -04:00
David Erwin 6c83b2e60d Merge branch 'master' into design-more 2017-03-23 10:49:26 -04:00
Belen Curcio 341a7320f9 Adding dynamic imports 2017-03-23 11:48:32 -03:00
David Erwin ae457f1951 Merge branch 'master' into my-comments-ux 2017-03-23 10:40:00 -04:00
David Erwin 907f7173fb Merge pull request #430 from coralproject/admin-ux
Admin UX
2017-03-23 10:39:18 -04:00
David Erwin a01c6f0f78 Merge branch 'master' into admin-ux 2017-03-23 09:04:36 -04:00
Belen Curcio 23833886bb missing tab name 2017-03-23 09:51:26 -03:00
Belen Curcio 464539d6a3 missing tab name 2017-03-23 09:50:18 -03:00
Riley Davis 375ff08584 Merge branch 'master' into reset-password-redirect 2017-03-22 18:45:40 -06:00
Gabriela Rodríguez Berón 849be3d5fd Merge branch 'master' into translations 2017-03-22 17:31:28 -07:00
gaba 35c8e1b5f9 Check spanish translations 2017-03-22 17:27:43 -07:00
gaba 4cbd6ebad2 Mooore translations. 2017-03-22 16:21:40 -07:00
Gabriela Rodríguez Berón ae96cb1e44 Merge branch 'master' into my-comments-ux 2017-03-22 16:18:45 -07:00
Riley Davis 4b0d0fed53 unused variable 2017-03-22 17:10:11 -06:00
Riley Davis fb2f8ef074 redirect to the parent url when resetting your password 2017-03-22 16:49:34 -06:00
Riley Davis c6401a2d08 Merge pull request #434 from coralproject/asset-url
asset_url and canonical link tags
2017-03-22 15:51:08 -06:00
Riley Davis 7af16471cd if the asset_url is not present, attempt to find the canonical link tag. if not, warn the user and strip the query params. 2017-03-22 15:41:06 -06:00
gaba 27a8c994a8 Fixing more translations 2017-03-22 13:19:08 -07:00
Belen Curcio 0bae6fe76c design tweaks 2017-03-22 17:06:41 -03:00
Belén Curcio 1e54a997d6 Merge branch 'master' into my-comments-ux 2017-03-22 16:25:06 -03:00
Belen Curcio 85d235bf37 Merge branch 'my-comments-ux' of github.com:coralproject/talk into my-comments-ux 2017-03-22 16:23:00 -03:00
Riley Davis d8e92968a5 Merge branch 'master' into admin-ux 2017-03-22 13:22:21 -06:00
Belen Curcio 4f50e8f19c Removing tests for now until we find a better solution for this 2017-03-22 16:19:05 -03:00
gaba 78e7951ad7 Merge branch 'master' into translations 2017-03-22 12:11:52 -07:00
Gabriela Rodríguez Berón 82068cd20e Merge pull request #370 from coralproject/story-138187767-mod-flag-names
Story 138187767  - Moderate Flag User Names
2017-03-22 12:10:43 -07:00
gaba 72e714a42c Translations into json 2017-03-22 11:59:33 -07:00
gaba 54015691fe Merge branch 'master' into translations 2017-03-22 11:46:09 -07:00
gaba d9af09d2a3 Email templates 2017-03-22 11:43:03 -07:00
Belén Curcio ae672d038c Merge branch 'master' into my-comments-ux 2017-03-22 15:37:48 -03:00
Belen Curcio 5729b03656 my-comments-ux 2017-03-22 15:35:40 -03:00
gaba 046cea5043 Merge branch 'master' into story-138187767-mod-flag-names 2017-03-22 11:33:25 -07:00
Gabriela Rodríguez Berón d7ccfa4eda Merge pull request #431 from coralproject/remove-express-dep
remove express dep from mailer service
2017-03-22 11:32:36 -07:00
gaba ac6a116896 Only the name for the template. 2017-03-22 11:21:41 -07:00
David Erwin 49e4486b75 Merge pull request #426 from coralproject/no-reply-closed-streams
hide reply button if stream is closed
2017-03-22 11:08:33 -07:00
Riley Davis 555b4517d8 move email templates into services/mailer. remove express app dep to render files 2017-03-22 12:00:30 -06:00
Belen Curcio 71415877b3 comment tweaks 2017-03-22 14:04:08 -03:00
gaba 9782cf0b9d Mail SendSimple will not use app in the future but render from ejs. 2017-03-22 10:02:59 -07:00
Belen Curcio cd27ffe86a wip 2017-03-22 13:37:44 -03:00
Belen Curcio 6a5f667336 hover, menu order 2017-03-22 13:20:26 -03:00
Riley Davis b51cbebe1d Merge branch 'master' into story-138187767-mod-flag-names 2017-03-22 09:57:29 -06:00
David Erwin 2016546ca3 Merge pull request #426 from coralproject/no-reply-closed-streams
hide reply button if stream is closed
2017-03-22 10:53:15 -04:00
David Erwin 63fca6ba4c Merge branch 'master' into no-reply-closed-streams 2017-03-22 10:45:02 -04:00
gaba 7522bd9cd6 Sends a mail to the suspended user. 2017-03-21 15:08:46 -07:00
David Erwin 9cf974d098 Merge pull request #427 from coralproject/view-more-no-worky
Comment Stream Bugfix
2017-03-21 17:59:40 -04:00
David Erwin 072d332027 Merge branch 'master' into view-more-no-worky 2017-03-21 17:54:37 -04:00
Riley Davis 80ff090c45 better tracking of new/old comments 2017-03-21 15:52:26 -06:00
Riley Davis a61b784d2b Merge pull request #425 from coralproject/reCAPTCHA-docs
Added reCAPTCHA docs
2017-03-21 15:03:42 -06:00
Riley Davis 8804b5b63f Merge branch 'master' into reCAPTCHA-docs 2017-03-21 14:59:59 -06:00
David Erwin d7adf0c792 Merge pull request #423 from coralproject/stream-sorting
sort the stories by date
2017-03-21 16:47:27 -04:00
Riley Davis 442e384298 Merge branch 'master' into no-reply-closed-streams 2017-03-21 13:21:01 -06:00
Riley Davis afaf9dd26d Merge branch 'master' into stream-sorting 2017-03-21 13:20:53 -06:00
Riley Davis 94619ca291 actually pass the asset_id instead of undefined 2017-03-21 12:30:27 -06:00
Belén Curcio b9fc1a6186 Merge branch 'master' into view-more-no-worky 2017-03-21 15:08:27 -03:00
David Erwin 481c22b7dd Merge pull request #429 from coralproject/documentation-fix
Fixed plugin examples
2017-03-21 13:44:57 -04:00
Gabriela Rodríguez Berón b9960c3cc7 Merge branch 'master' into story-138187767-mod-flag-names 2017-03-21 10:42:48 -07:00
gaba a09d41053f It uses Button instead of FabButton from coral-ui. 2017-03-21 10:36:21 -07:00
Riley Davis c8dc3c5ad2 disable replies too 2017-03-21 10:27:11 -06:00
Wyatt Johnson e5a2d627df Fixed plugin examples 2017-03-21 10:03:24 -06:00
Riley Davis ffc5379e11 Merge branch 'master' into stream-sorting 2017-03-21 09:12:04 -06:00
Riley Davis f5d1e0df8f Merge branch 'master' into no-reply-closed-streams 2017-03-21 09:11:54 -06:00
Riley Davis 894ea634c9 Merge branch 'master' into view-more-no-worky 2017-03-21 09:09:57 -06:00
Wyatt Johnson 033502626c Merge pull request #428 from coralproject/load-more-fix
Fixed bug in loading more comments
2017-03-21 08:07:00 -06:00
Riley Davis 9bfc4b5815 how did this get duplicated? 2017-03-20 16:26:52 -06:00
Riley Davis 546dd63d8c why did only this get changed? 2017-03-20 16:21:01 -06:00
gaba e5bac88bb5 Merge branch 'master' into story-138187767-mod-flag-names 2017-03-20 15:02:38 -07:00
Wyatt Johnson 20348d5f05 Fixed bug in loading more comments 2017-03-20 15:58:33 -06:00
Riley Davis 0383d86021 load more replies if there are currently no replies 2017-03-20 15:09:45 -06:00
riley fb503798e8 use the reply id so we don't get weird behavior. fix a console warning 2017-03-20 14:46:59 -06:00
gaba be2ecf0838 Adds user and message to flagaction to display in FE. 2017-03-20 13:38:11 -07:00
gaba 9e76ca5d27 Fix CSS. 2017-03-20 12:37:21 -07:00
riley d7e554a055 hide reply button if stream is closed 2017-03-20 13:01:38 -06:00
Wyatt Johnson eceddf109a Added reCAPTCHA docs 2017-03-20 12:35:22 -06:00
gaba ac8c73c4e9 Merge branch 'master' into story-138187767-mod-flag-names 2017-03-20 10:47:04 -07:00
David Erwin 9ccdaec063 Merge pull request #422 from coralproject/plugin-patch
Update Dockerfile
2017-03-20 10:47:03 -07:00
Wyatt Johnson 69ad11adf5 Update Dockerfile 2017-03-20 10:47:03 -07:00
David Erwin af93953b17 Merge pull request #421 from coralproject/plugins
Server Graph Plugins
2017-03-20 10:47:02 -07:00
Riley Davis a7908f3399 Merge pull request #418 from coralproject/recaptcha-fix
Added new pageData action/reducer
2017-03-20 10:47:01 -07:00
David Erwin 17deea5b96 Merge branch 'master' into plugins 2017-03-20 10:47:01 -07:00
Riley Davis 09ec2dba94 combine destructuring 2017-03-20 10:47:00 -07:00
Wyatt Johnson 46ecf0c06f Renamed pageData -> config 2017-03-20 10:46:59 -07:00
Wyatt Johnson b35170484e Merge branch 'master' into recaptcha-fix 2017-03-20 10:46:59 -07:00
Wyatt Johnson 8eac8d6890 Merge branch 'master' into recaptcha-fix 2017-03-20 10:46:58 -07:00
Wyatt Johnson dfffbbe619 bug fix 2017-03-20 10:46:58 -07:00
Wyatt Johnson 5768d30a7a Added new pageData action/reducer 2017-03-20 10:46:57 -07:00
gaba 2fff47c753 Merge branch 'master' into plugins 2017-03-20 10:46:30 -07:00
Kim Gardner 74a561a770 Merge pull request #420 from coralproject/view-more-bug
show different copy per # of replies
2017-03-20 10:45:32 -07:00
Riley Davis 9d64639f8e differentiate between top- and comment-level LoadMore 2017-03-20 10:45:31 -07:00
Riley Davis 316aa12481 add spanish translations 2017-03-20 10:45:31 -07:00
Kim Gardner 369d867e77 Merge pull request #419 from coralproject/dashboard-reload-bugfix
dashboard refresh fix
2017-03-20 10:45:30 -07:00
Riley Davis 73c59cb137 show different copy per # of replies 2017-03-20 10:45:30 -07:00
Riley Davis 517ede1977 dashboard reload fix 2017-03-20 10:45:29 -07:00
Kim Gardner 5514f45ab3 Merge pull request #412 from coralproject/recaptcha-support
recaptcha + RL support
2017-03-20 10:45:28 -07:00
gaba 2a66a78d41 Merge branch 'master' into recaptcha-support 2017-03-20 10:37:18 -07:00
Kim Gardner 61a2f1ed36 Merge pull request #417 from coralproject/dashboard-bug
move CountdownTimer to its own component
2017-03-20 10:27:31 -07:00
Riley Davis d2e2eef046 do a null check. 2017-03-20 10:27:31 -07:00
gaba a2833538a7 move CountdownTimer to its own component 2017-03-20 10:26:12 -07:00
Kim Gardner ecb1c51a9f Merge pull request #415 from coralproject/config-shuffle
move pre-mod links
2017-03-20 10:21:59 -07:00
Kim Gardner 1a83ffe482 Merge branch 'master' into config-shuffle 2017-03-20 10:21:59 -07:00
Kim Gardner 2c2cbb720b Merge pull request #416 from coralproject/login-error-fix
correctly handle error when not logged in
2017-03-20 10:21:58 -07:00
Riley Davis 92055c7814 correctly handle error when not logged in 2017-03-20 10:21:58 -07:00
riley e975d6d809 move pre-mod links 2017-03-20 10:21:57 -07:00
Kim Gardner 8459c31b42 Merge pull request #414 from coralproject/streams-to-stories
change Stream > Story sometimes
2017-03-20 10:21:56 -07:00
Riley Davis 63c3d02329 change Stream > Story sometimes 2017-03-20 10:21:56 -07:00
Riley Davis dedcb87435 Merge branch 'master' into recaptcha-support 2017-03-20 10:21:55 -07:00
Kim Gardner 22753e1e14 Merge pull request #411 from coralproject/remove-like-widget
remove like widget for now
2017-03-20 10:21:54 -07:00
gaba 7232d09eb3 Merge branch 'master' into remove-like-widget 2017-03-20 07:47:37 -07:00
Riley Davis 5c767b6aa6 Merge branch 'master' into recaptcha-support 2017-03-20 07:42:31 -07:00
riley 1ff5ab2e3e remove like widget for now 2017-03-20 07:42:31 -07:00
David Erwin 75cb83014b Merge pull request #406 from coralproject/asset-comment-metrics
Asset comment metrics
2017-03-20 07:42:30 -07:00
David Erwin abd0b51441 Removing log 2017-03-20 07:42:29 -07:00
David Erwin c55b4d22d7 Language update 2017-03-20 07:42:29 -07:00
gaba a71b8a4ae4 Merge branch 'master' into asset-comment-metrics 2017-03-20 07:41:12 -07:00
Kim Gardner 4ff6c08d4d Merge pull request #407 from coralproject/hide-dashboard-cta
save dismissed timer note to localStorage
2017-03-20 07:39:31 -07:00
Kim Gardner 6af8ef7838 Merge branch 'master' into hide-dashboard-cta 2017-03-20 07:39:31 -07:00
Kim Gardner 465e2012c5 Merge pull request #410 from coralproject/firefox-mod-buttons
button text was wrapping in firefox
2017-03-20 07:39:30 -07:00
Kim Gardner 01db845814 Merge branch 'master' into firefox-mod-buttons 2017-03-20 07:39:29 -07:00
Kim Gardner aba9488aab Merge pull request #413 from coralproject/reporting-messaging
indentation and flag -> report
2017-03-20 07:39:28 -07:00
Riley Davis 337b6accec remove fakeurl 2017-03-20 07:39:28 -07:00
riley 39e69f3bdc button text was wrapping in firefox 2017-03-20 07:39:27 -07:00
riley 21acb0d1d7 indentation and flag -> report 2017-03-20 07:39:27 -07:00
Riley Davis 2124ca0f34 save dismissed timer note to localStorage 2017-03-20 07:39:26 -07:00
gaba 6f3cb34817 Merge branch 'master' into asset-comment-metrics 2017-03-20 07:39:00 -07:00
David Erwin 1d061ce359 Merge branch 'master' into asset-comment-metrics 2017-03-20 07:36:23 -07:00
Riley Davis f005907894 add some translations 2017-03-20 07:36:23 -07:00
Riley Davis 2c4bf52f6b add Activity widget 2017-03-20 07:36:22 -07:00
riley 8348e1e462 Merge branch 'master' into asset-comment-metrics 2017-03-20 07:36:22 -07:00
Riley Davis c8a0ded4ad try to just add recaptcha manually
use react-recaptcha

remove recaptcha file
2017-03-20 07:36:21 -07:00
riley ca07406b1a Merge branch 'master' into recaptcha-support 2017-03-20 07:36:20 -07:00
Riley Davis 6b332fbde1 Merge pull request #404 from coralproject/keyboard-shortcut-scroll
Scrolling to keep selected comments in view.
2017-03-20 07:36:19 -07:00
Riley Davis f8b3c5752e Merge branch 'master' into keyboard-shortcut-scroll 2017-03-20 07:36:19 -07:00
Riley Davis 46e8d30e22 scrollIntoView will work in more browsers 2017-03-20 07:36:18 -07:00
David Erwin 28b24ebb09 Fix bug in example 2017-03-20 07:36:17 -07:00
David Jay ae4bae6cd2 Scrolling to keep selected comments in view. 2017-03-20 07:36:17 -07:00
Wyatt Johnson a7c946f9af Fixed bug in docs 2017-03-20 07:36:16 -07:00
Wyatt Johnson 32af28bfdc Fixed typo 2017-03-20 07:36:16 -07:00
Wyatt Johnson 86f0ed80ea Added docs and added new context based plugins. 2017-03-20 07:36:15 -07:00
Wyatt Johnson 7bf1510a8b Changed before/after to pre/post 2017-03-20 07:36:15 -07:00
gaba 258fd8b25a Initial implementation at plugins 2017-03-20 07:36:06 -07:00
David Erwin 2ee6335328 Merge pull request #409 from coralproject/revert-version-pin
Removed the version pin
2017-03-20 07:33:00 -07:00
Wyatt Johnson 894395bc65 Resolved #357 2017-03-20 07:32:59 -07:00
David Erwin ebf675bf2c Merge pull request #405 from coralproject/asset-comment-total-count
Added support for total comment count
2017-03-20 07:32:58 -07:00
David Erwin 30380ed5d7 Merge branch 'master' into asset-comment-total-count 2017-03-20 07:32:58 -07:00
David Erwin 408a74ad4d Merge pull request #408 from coralproject/widen-notifications
make notifications wider
2017-03-20 07:32:57 -07:00
Riley Davis 09e7f483d1 Merge pull request #403 from coralproject/bugfix-ban-dialog-language
Hiding rejected note on ban dialog for rejected comments.
2017-03-20 07:32:56 -07:00
David Erwin fe6e6fbd2f Merge branch 'master' into widen-notifications 2017-03-20 07:32:56 -07:00
Riley Davis eab5832457 Merge branch 'master' into bugfix-ban-dialog-language 2017-03-20 07:32:55 -07:00
David Jay a243310ecd Merge branch 'master' into bugfix-ban-dialog-language 2017-03-20 07:32:54 -07:00
gaba be351e46fd Merge branch 'master' into bugfix-ban-dialog-language 2017-03-20 07:32:06 -07:00
Riley Davis e5c108f8fb make notifications wider 2017-03-20 07:29:09 -07:00
David Erwin 2f86b9eecf Merge branch 'master' into bugfix-ban-dialog-language 2017-03-20 07:29:09 -07:00
Wyatt Johnson fcc93963c4 Added support for total comment count 2017-03-20 07:29:08 -07:00
Riley Davis 117b73adf7 Merge pull request #398 from coralproject/widget-auto-update
Auto-update Dashboard
2017-03-20 07:29:07 -07:00
Riley Davis 587ee82787 Merge branch 'master' into widget-auto-update 2017-03-20 07:29:07 -07:00
Riley Davis 66efbf89d4 Merge pull request #401 from coralproject/remove-notification-warning
incorrectly using dispatch now that we do pym directly
2017-03-20 07:29:06 -07:00
David Jay 88538d7b64 Merge branch 'master' into remove-notification-warning 2017-03-20 07:29:05 -07:00
David Jay 2d968126ab Merge branch 'master' into remove-notification-warning 2017-03-20 07:29:05 -07:00
Riley Davis db38bcb477 modify to light grey 2017-03-20 07:29:04 -07:00
gaba b2234bc3c9 Merge branch 'master'
Conflicts:
	client/coral-admin/src/containers/Dashboard/Widget.css
2017-03-20 07:28:17 -07:00
Riley Davis d71ca8d3f9 Merge pull request #396 from coralproject/open-closed-stream-buttons
fix some firefox display things
2017-03-20 07:27:20 -07:00
Riley Davis e0d3ad32e2 Merge branch 'master' into open-closed-stream-buttons 2017-03-20 07:27:20 -07:00
David Erwin 5f5d33931e Merge branch 'master' into open-closed-stream-buttons 2017-03-20 07:27:19 -07:00
Riley Davis b94401a8cf make only the title clickable 2017-03-20 07:27:19 -07:00
David Jay 1fd74068cd Merge branch 'master' into open-closed-stream-buttons 2017-03-20 07:27:18 -07:00
Riley Davis 1dd0b79a3e Merge pull request #392 from coralproject/mod-queue-load-more
Load More button in mod queue
2017-03-20 07:27:17 -07:00
Riley Davis 90298436c6 Merge branch 'master' into widget-auto-update 2017-03-20 07:27:17 -07:00
David Jay 203776d07c Merge branch 'mod-queue-load-more' of github.com:coralproject/talk into mod-queue-load-more 2017-03-20 07:27:16 -07:00
David Jay a5e6689f81 Merge pull request #402 from coralproject/autoreject-on-ban
Autoreject on ban
2017-03-20 07:27:15 -07:00
David Jay a78648fc0d Merge branch 'master' into mod-queue-load-more 2017-03-20 07:27:15 -07:00
Belén Curcio 6d43969e3b Merge branch 'master' into autoreject-on-ban 2017-03-20 07:27:14 -07:00
Riley Davis aec0f678c0 Merge branch 'master' into autoreject-on-ban 2017-03-20 07:27:13 -07:00
gaba ff71908a10 Merge branch 'master' into mod-queue-load-more 2017-03-20 07:26:00 -07:00
David Erwin a430ca65c2 Merge branch 'master' into widget-auto-update 2017-03-20 07:24:17 -07:00
David Erwin 96cd4056c3 Merge pull request #400 from coralproject/flag-username-reset
Resetting report menu on close.
2017-03-20 07:24:16 -07:00
David Erwin ede6c44c93 Merge branch 'master' into flag-username-reset 2017-03-20 07:24:15 -07:00
David Erwin 8b0f01f2aa Merge pull request #394 from coralproject/bugfix-mod-queue-refresh
Preventing admin panel refresh on accept and reject
2017-03-20 07:24:14 -07:00
Riley Davis 065ccfbfc4 Merge branch 'master' into flag-username-reset 2017-03-20 07:24:13 -07:00
Riley Davis 43119e5e15 sort the stories by date 2017-03-17 14:30:11 -06:00
David Erwin 471a55c3eb Merge pull request #422 from coralproject/plugin-patch
Update Dockerfile
2017-03-17 12:50:17 -04:00
Wyatt Johnson c79d1d40da Update Dockerfile 2017-03-17 10:46:45 -06:00
David Erwin dbc3c265a3 Merge pull request #421 from coralproject/plugins
Server Graph Plugins
2017-03-17 12:35:51 -04:00
David Erwin b3d80b2888 Merge branch 'master' into plugins 2017-03-17 12:31:40 -04:00
Riley Davis cb3572acc5 Merge pull request #418 from coralproject/recaptcha-fix
Added new pageData action/reducer
2017-03-17 10:24:22 -06:00
Riley Davis c48ce8323d combine destructuring 2017-03-17 10:18:07 -06:00
Wyatt Johnson df4e07d39c Renamed pageData -> config 2017-03-17 08:37:58 -06:00
Wyatt Johnson 6426663103 Merge branch 'master' into plugins 2017-03-17 08:26:42 -06:00
Wyatt Johnson edeedfaa71 Merge branch 'master' into recaptcha-fix 2017-03-17 08:01:47 -06:00
Kim Gardner 42beac9a39 Merge pull request #420 from coralproject/view-more-bug
show different copy per # of replies
2017-03-17 09:05:24 -04:00
Riley Davis a7cdce3579 differentiate between top- and comment-level LoadMore 2017-03-16 15:35:38 -06:00
Riley Davis 528e2ff851 add spanish translations 2017-03-16 14:54:09 -06:00
Riley Davis c61b249a37 show different copy per # of replies 2017-03-16 14:37:29 -06:00
David Erwin 4b8fb48f63 Fix bug in example 2017-03-16 14:32:59 -04:00
Wyatt Johnson 4a416d9477 Fixed bug in docs 2017-03-16 12:31:25 -06:00
Wyatt Johnson b277f3be75 Merge branch 'master' into recaptcha-fix 2017-03-16 11:59:56 -06:00
Kim Gardner 4219520002 Merge pull request #419 from coralproject/dashboard-reload-bugfix
dashboard refresh fix
2017-03-16 13:55:17 -04:00
Riley Davis 39596399ec dashboard reload fix 2017-03-16 11:45:10 -06:00
Wyatt Johnson 681bdb1d92 bug fix 2017-03-16 11:11:24 -06:00
Wyatt Johnson 598fcc4cb0 Added new pageData action/reducer 2017-03-16 11:07:38 -06:00
Kim Gardner 3277923a8c Merge pull request #412 from coralproject/recaptcha-support
recaptcha + RL support
2017-03-16 12:16:50 -04:00
Riley Davis 736ac11782 Merge branch 'master' into recaptcha-support 2017-03-16 10:12:28 -06:00
Kim Gardner 080895c70b Merge pull request #417 from coralproject/dashboard-bug
move CountdownTimer to its own component
2017-03-16 12:07:49 -04:00
Riley Davis 1043a37eef do a null check. 2017-03-16 09:28:17 -06:00
Wyatt Johnson d0ec7bbad1 Fixed typo 2017-03-15 16:54:07 -06:00
Wyatt Johnson 19e8f5133f Added docs and added new context based plugins. 2017-03-15 16:52:04 -06:00
Riley Davis 378e855c4c remove logs 2017-03-15 14:20:27 -06:00
Riley Davis 9ddc3aa0b7 move CountdownTimer to its own component 2017-03-15 14:13:18 -06:00
Kim Gardner 8d87af2115 Merge pull request #415 from coralproject/config-shuffle
move pre-mod links
2017-03-15 14:12:24 -04:00
Kim Gardner babd1b3d8d Merge branch 'master' into config-shuffle 2017-03-15 13:30:43 -04:00
Kim Gardner 9e890101fa Merge pull request #416 from coralproject/login-error-fix
correctly handle error when not logged in
2017-03-15 13:28:45 -04:00
Riley Davis 9beff74862 correctly handle error when not logged in 2017-03-15 11:16:13 -06:00
riley 3071b3783e move pre-mod links 2017-03-15 10:15:47 -06:00
Kim Gardner 8a84587e54 Merge pull request #414 from coralproject/streams-to-stories
change Stream > Story sometimes
2017-03-15 11:28:11 -04:00
Wyatt Johnson dcb673f072 Changed before/after to pre/post 2017-03-15 09:21:24 -06:00
Wyatt Johnson fff9d84303 Initial implementation at plugins 2017-03-14 17:46:04 -06:00
Riley Davis 04298aca7d change Stream > Story sometimes 2017-03-14 16:30:02 -06:00
Riley Davis dbd1366c26 Merge branch 'master' into recaptcha-support 2017-03-14 15:56:20 -06:00
Kim Gardner d8ce015b98 Merge pull request #411 from coralproject/remove-like-widget
remove like widget for now
2017-03-14 17:45:26 -04:00
Riley Davis b612bbd96c Merge branch 'master' into remove-like-widget 2017-03-14 15:35:07 -06:00
Riley Davis 099cc36b89 Merge branch 'master' into recaptcha-support 2017-03-14 15:33:18 -06:00
Riley Davis 7bbf8abd3d try to just add recaptcha manually
use react-recaptcha

remove recaptcha file
2017-03-14 15:32:16 -06:00
David Erwin 66d04ab5ca Merge pull request #406 from coralproject/asset-comment-metrics
Asset comment metrics
2017-03-14 17:16:15 -04:00
David Erwin 6758a178eb Removing log 2017-03-14 17:10:37 -04:00
David Erwin 13dad0c895 Language update 2017-03-14 17:07:48 -04:00
Riley Davis b4d236df36 Merge branch 'master' into asset-comment-metrics 2017-03-14 15:00:27 -06:00
Kim Gardner 8876c10254 Merge pull request #407 from coralproject/hide-dashboard-cta
save dismissed timer note to localStorage
2017-03-14 13:29:45 -04:00
Kim Gardner c50d25c0e9 Merge branch 'master' into hide-dashboard-cta 2017-03-14 13:19:23 -04:00
Kim Gardner 1330ffd394 Merge pull request #410 from coralproject/firefox-mod-buttons
button text was wrapping in firefox
2017-03-14 13:17:45 -04:00
Kim Gardner d85473e20c Merge branch 'master' into firefox-mod-buttons 2017-03-14 13:01:24 -04:00
Kim Gardner 13e2766ae9 Merge pull request #413 from coralproject/reporting-messaging
indentation and flag -> report
2017-03-14 13:00:49 -04:00
Riley Davis fa66bab937 Merge branch 'master' into asset-comment-metrics 2017-03-14 10:46:21 -06:00
Riley Davis f0eaf01ccc remove fakeurl 2017-03-13 13:33:31 -06:00
riley 147345505f indentation and flag -> report 2017-03-13 13:23:00 -06:00
riley b1f426e70d Merge branch 'master' into recaptcha-support 2017-03-13 12:27:19 -06:00
riley 7bfa718830 remove like widget for now 2017-03-13 12:15:38 -06:00
riley 801555e66e button text was wrapping in firefox 2017-03-13 12:08:57 -06:00
Riley Davis bf31f07053 Merge pull request #404 from coralproject/keyboard-shortcut-scroll
Scrolling to keep selected comments in view.
2017-03-13 11:52:08 -06:00
Riley Davis 6d3bb6af03 Merge branch 'master' into keyboard-shortcut-scroll 2017-03-13 11:14:04 -06:00
Riley Davis f999be6fbf scrollIntoView will work in more browsers 2017-03-13 11:13:28 -06:00
David Erwin 15e962c985 Merge pull request #409 from coralproject/revert-version-pin
Removed the version pin
2017-03-13 12:01:18 -04:00
Wyatt Johnson 8e6c2e8075 Resolved #357 2017-03-13 09:43:47 -06:00
David Erwin eb00320a0c Merge pull request #405 from coralproject/asset-comment-total-count
Added support for total comment count
2017-03-13 11:32:35 -04:00
David Erwin 33062ca43e Merge branch 'master' into asset-comment-total-count 2017-03-13 11:14:36 -04:00
David Erwin cf753632bf Merge branch 'master' into asset-comment-metrics 2017-03-13 10:18:57 -04:00
David Erwin 5ceaa24217 Merge pull request #408 from coralproject/widen-notifications
make notifications wider
2017-03-13 10:18:28 -04:00
David Erwin 9f5648b4c3 Merge branch 'master' into widen-notifications 2017-03-13 10:14:51 -04:00
Riley Davis 087851f694 Merge pull request #403 from coralproject/bugfix-ban-dialog-language
Hiding rejected note on ban dialog for rejected comments.
2017-03-10 15:08:27 -07:00
Riley Davis e159435293 Merge branch 'master' into bugfix-ban-dialog-language 2017-03-10 14:50:49 -07:00
Riley Davis 9d9d336e06 make notifications wider 2017-03-10 13:54:33 -07:00
Riley Davis 9e7b710faa save dismissed timer note to localStorage 2017-03-10 13:48:14 -07:00
Riley Davis 4a10122f34 add some translations 2017-03-10 12:16:20 -07:00
Wyatt Johnson 93221de563 Added support for total comment count 2017-03-09 17:07:07 -07:00
David Jay 255807858d Scrolling to keep selected comments in view. 2017-03-09 18:06:44 -05:00
Riley Davis f6af847bfb add Activity widget 2017-03-09 16:05:09 -07:00
riley 25fe268872 Merge branch 'master' into asset-comment-metrics 2017-03-09 13:20:51 -07:00
Riley Davis 5893e0cbde Merge pull request #398 from coralproject/widget-auto-update
Auto-update Dashboard
2017-03-09 13:19:39 -07:00
Riley Davis 0a88d3da97 Merge branch 'master' into widget-auto-update 2017-03-09 13:15:56 -07:00
Riley Davis d46b06da1c Merge pull request #401 from coralproject/remove-notification-warning
incorrectly using dispatch now that we do pym directly
2017-03-09 13:01:37 -07:00
Riley Davis cb10de6c7a modify to light grey 2017-03-09 12:58:57 -07:00
Riley Davis 98a87f4595 Merge branch 'master'
Conflicts:
	client/coral-admin/src/containers/Dashboard/Widget.css
2017-03-09 12:22:20 -07:00
David Jay 1884ad6437 Merge branch 'master' into bugfix-ban-dialog-language 2017-03-09 13:51:51 -05:00
David Jay 3c296ea4e9 Merge branch 'master' into remove-notification-warning 2017-03-09 13:51:21 -05:00
Riley Davis 82f47e27ac Merge pull request #396 from coralproject/open-closed-stream-buttons
fix some firefox display things
2017-03-09 11:50:28 -07:00
David Jay 3232c04997 Merge branch 'master' into remove-notification-warning 2017-03-09 13:50:09 -05:00
Riley Davis e6dfb3f64a Merge branch 'master' into open-closed-stream-buttons 2017-03-09 11:44:47 -07:00
Riley Davis fb7895b02f Merge branch 'master' into widget-auto-update 2017-03-09 11:44:16 -07:00
Riley Davis a152b9f1d3 Merge pull request #392 from coralproject/mod-queue-load-more
Load More button in mod queue
2017-03-09 11:43:27 -07:00
David Jay 542ccc6190 Merge branch 'master' into bugfix-ban-dialog-language 2017-03-09 13:40:44 -05:00
David Jay 8f988c898d Merge branch 'mod-queue-load-more' of github.com:coralproject/talk into mod-queue-load-more 2017-03-09 13:36:59 -05:00
David Jay 66adf19b97 Merge branch 'master' into mod-queue-load-more 2017-03-09 13:36:37 -05:00
David Jay f24eb74745 Merge pull request #402 from coralproject/autoreject-on-ban
Autoreject on ban
2017-03-09 13:35:10 -05:00
David Jay a0953c0689 Adding proptypes to loadmore component. 2017-03-09 13:33:00 -05:00
Belén Curcio c3749c5e31 Merge branch 'master' into autoreject-on-ban 2017-03-09 13:27:30 -05:00
David Erwin ebdf4b5ec7 Merge branch 'master' into open-closed-stream-buttons 2017-03-09 13:15:45 -05:00
David Erwin 53043051f3 Merge branch 'master' into bugfix-ban-dialog-language 2017-03-09 13:13:10 -05:00
David Erwin a195879556 Merge branch 'master' into widget-auto-update 2017-03-09 13:11:17 -05:00
David Erwin b0ca4d0785 Merge pull request #400 from coralproject/flag-username-reset
Resetting report menu on close.
2017-03-09 13:10:31 -05:00
David Jay 39386a9fcf Merge branch 'master' into mod-queue-load-more 2017-03-09 13:07:41 -05:00
Riley Davis 0db92974c4 make only the title clickable 2017-03-09 11:05:18 -07:00
David Erwin f64063ba14 Merge branch 'master' into flag-username-reset 2017-03-09 12:57:09 -05:00
David Erwin 91dff157aa Merge pull request #394 from coralproject/bugfix-mod-queue-refresh
Preventing admin panel refresh on accept and reject
2017-03-09 12:55:24 -05:00
David Jay fd9f838948 Merge branch 'master' into open-closed-stream-buttons 2017-03-09 12:39:54 -05:00
Riley Davis db5df1699c Merge branch 'master' into autoreject-on-ban 2017-03-09 10:30:10 -07:00
Riley Davis c064bee4f6 Merge branch 'master' into flag-username-reset 2017-03-09 10:27:27 -07:00
Riley Davis 895d3c31fc Merge branch 'master' into bugfix-mod-queue-refresh 2017-03-09 10:24:25 -07:00
Riley Davis 070ed13495 Merge branch 'master' into mod-queue-load-more 2017-03-09 10:15:08 -07:00
Gabriela Rodríguez Berón 491694b9c1 Merge branch 'master' into story-138187767-mod-flag-names 2017-03-09 17:20:00 +01:00
Belén Curcio 6a72f4d066 Merge pull request #397 from coralproject/long-titles
Adding fix for handling long titles in the Moderation Queue
2017-03-09 11:17:11 -05:00
Belén Curcio 9bfc7d2037 Merge branch 'master' into long-titles 2017-03-09 11:05:08 -05:00
Belen Curcio 438877d58f Merge branch 'long-titles' of github.com:coralproject/talk into long-titles 2017-03-09 11:03:37 -05:00
Belen Curcio ce7a2d54d0 Font Size 2017-03-09 10:59:28 -05:00
Wyatt Johnson 63220cb6a7 Some fixes to make it work 2017-03-08 19:14:02 -07:00
Wyatt Johnson feaad2d6ca Added loader, graphql 2017-03-08 19:01:08 -07:00
David Jay 9b53930861 Merge branch 'master' into flag-username-reset 2017-03-08 17:49:59 -05:00
riley 2cdf2bb332 add moderate button 2017-03-08 15:26:45 -07:00
David Jay 7bf5285a1d Merge branch 'master' into bugfix-ban-dialog-language 2017-03-08 17:10:14 -05:00
David Jay a8d090d5a2 Merge branch 'master' into autoreject-on-ban 2017-03-08 17:07:26 -05:00
David Jay 377563c798 Hiding rejected note on ban dialog for rejected comments. 2017-03-08 17:02:47 -05:00
Riley Davis f0b1460aa8 Merge pull request #399 from coralproject/mod-squad
update dashboard links
2017-03-08 15:01:00 -07:00
Riley Davis 7b5721e0c6 Merged branch open-closed-stream-buttons into open-closed-stream-buttons 2017-03-08 15:00:09 -07:00
Riley Davis b34758e8cc unused vars 2017-03-08 14:59:55 -07:00
Riley Davis ee84b821fc Merge branch 'master' into mod-squad 2017-03-08 14:57:19 -07:00
Riley Davis c9e5b91b18 Merge branch 'master' into open-closed-stream-buttons 2017-03-08 14:53:24 -07:00
Riley Davis eaf035acd6 simplify dashboard table rendering 2017-03-08 14:53:07 -07:00
David Jay fac3d26c22 Closing ban dialog after ban is complete. 2017-03-08 16:39:53 -05:00
Riley Davis 5a50ae0d10 incorrectly using dispatch now that we do pym directly 2017-03-08 14:16:20 -07:00
David Erwin c467460580 Merge pull request #393 from coralproject/banned-optimize-bug
rejected comment optimize bug
2017-03-08 16:16:06 -05:00
David Jay d203a9afe0 Auto-rejecting comments on ban. 2017-03-08 16:06:34 -05:00
Riley Davis e4cdc73bf8 fix for react-mdn-selectfield in FF 2017-03-08 13:53:39 -07:00
David Jay 3262d136b7 Resetting report menu on close. 2017-03-08 15:44:19 -05:00
David Jay 3c28f68d0a Merge branch 'master' into mod-squad 2017-03-08 15:32:48 -05:00
David Jay a13e0205de Merge branch 'master' into open-closed-stream-buttons 2017-03-08 15:22:08 -05:00
David Jay be3212e144 Merge branch 'master' into banned-optimize-bug 2017-03-08 15:05:12 -05:00
David Jay 510ee2c28e Merge branch 'master' into bugfix-mod-queue-refresh 2017-03-08 15:04:45 -05:00
David Jay 3861d14a07 Merge branch 'master' into mod-queue-load-more 2017-03-08 15:04:24 -05:00
Gabriela Rodríguez Berón dae2e7d9d6 Merge branch 'master' into story-138187767-mod-flag-names 2017-03-08 20:09:50 +01:00
gaba 69916a6ef2 I could not figure out how to send the parameter to the graphql query through the client. 2017-03-08 19:39:28 +01:00
Riley Davis 4d69ecc425 Merge branch 'master' into widget-auto-update 2017-03-08 11:12:53 -07:00
Riley Davis ec634fa3c0 add translations 2017-03-08 11:12:44 -07:00
Riley Davis 8989f3bfc2 Merge pull request #389 from coralproject/keyboard-shortcuts
Keyboard shortcuts
2017-03-08 11:10:24 -07:00
David Jay 15193e97a8 Preventing keys from scrolling past the end of the comment queue. 2017-03-08 13:01:37 -05:00
Riley Davis 0863ca26ff update dashboard links 2017-03-08 09:56:36 -07:00
Riley Davis 097fc30ac9 Merge branch 'master' into bugfix-mod-queue-refresh 2017-03-08 09:35:05 -07:00
Riley Davis bc1fc15572 Merge branch 'master' into banned-optimize-bug 2017-03-08 09:34:55 -07:00
Riley Davis 8c67acf609 Merge branch 'master' into mod-queue-load-more 2017-03-08 09:34:49 -07:00
Riley Davis 68d64665a9 Merge branch 'master' into keyboard-shortcuts 2017-03-08 09:34:38 -07:00
Riley Davis 47f6f4cb6c styles and auto-update Dashboard 2017-03-07 16:51:22 -07:00
Samantha Hankins 9d2d878790 Merge branch 'master' into long-titles 2017-03-07 17:17:44 -05:00
Samantha Hankins 02767cf060 Merge pull request #395 from coralproject/user-facing
User facing Styles Updates
2017-03-07 17:13:19 -05:00
Belen Curcio cceeafadec Indent 2017-03-07 16:53:42 -05:00
Belen Curcio af4a69a677 Changing icon and link 2017-03-07 16:50:36 -05:00
Belen Curcio f563bcf119 Adding fix for long text 2017-03-07 16:35:01 -05:00
Belen Curcio d1119604e0 ícon styling for infobox 2017-03-07 15:59:05 -05:00
Riley Davis e578f901b2 fix some firefox display things 2017-03-07 13:38:01 -07:00
Belen Curcio eb06f3892d changes 2017-03-07 15:27:43 -05:00
Belen Curcio ee30f549a1 Lato all the things 2017-03-07 15:04:35 -05:00
Belen Curcio dc96b550a6 Button should inherit font 2017-03-07 14:56:28 -05:00
Riley Davis d66dcd8548 Merge branch 'master' into mod-queue-load-more 2017-03-07 12:33:04 -07:00
riley c1d6a4dc7e Merged master into banned-optimize-bug 2017-03-07 12:31:42 -07:00
Riley Davis 9d13eedede Merge pull request #387 from coralproject/notification-warning
move notifications to parent
2017-03-07 12:30:02 -07:00
Riley Davis c4f2d876ee Merge branch 'master' into notification-warning 2017-03-07 12:22:42 -07:00
David Jay 7e55ecab59 Merge pull request #388 from coralproject/reply-box-focus
Focusing on reply box when reply is clicked.
2017-03-07 14:19:37 -05:00
David Jay 95aa89faed Switching premod status update to updateQueries function. 2017-03-07 14:16:44 -05:00
Riley Davis 87ef7ffb67 Merge branch 'master' into reply-box-focus 2017-03-07 11:54:55 -07:00
Riley Davis 162091a6d0 Merge branch 'master' into story-138187767-mod-flag-names 2017-03-07 11:44:10 -07:00
David Jay d0d0766ca6 Merge branch 'master' into notification-warning 2017-03-07 13:33:24 -05:00
David Jay 1e34ffd075 Merge branch 'master' into banned-optimize-bug 2017-03-07 13:31:12 -05:00
David Jay 2b947bebf8 Merge pull request #383 from coralproject/premod-links
Premod links
2017-03-07 13:30:20 -05:00
Riley Davis 21003ca994 Merge branch 'master' into mod-queue-load-more 2017-03-07 11:29:59 -07:00
Riley Davis e15e4b004f Merge branch 'master' into keyboard-shortcuts 2017-03-07 11:26:58 -07:00
Riley Davis 37dad96fee Merge branch 'master'
Conflicts:
	client/coral-admin/src/containers/Configure/StreamSettings.js
2017-03-07 11:23:22 -07:00
David Jay 78a2a64671 Not displaying premod or rejected comments. 2017-03-07 13:13:21 -05:00
Gabriela Rodríguez Berón 0b280d0de2 Merge branch 'master' into story-138187767-mod-flag-names 2017-03-07 19:12:45 +01:00
David Jay 3d4521ab1d Merge pull request #384 from coralproject/global-info-bug
Guidelines info box has a word wrapping issue
2017-03-07 13:11:39 -05:00
David Jay ae5d35273b Merge branch 'master' into global-info-bug 2017-03-07 12:58:25 -05:00
David Jay 9bf1246d3a Merge pull request #386 from coralproject/configure-bugs
Configure Styles and UX Polish
2017-03-07 12:58:02 -05:00
Riley Davis 978bf05128 Merge branch 'master' into premod-links 2017-03-07 10:57:05 -07:00
David Jay 7379924e21 Merge branch 'master' into global-info-bug 2017-03-07 12:56:19 -05:00
Riley Davis d46795bbb7 Merge branch 'master' into mod-queue-load-more 2017-03-07 10:55:28 -07:00
David Jay bf1d46d60d Merge branch 'master' into configure-bugs 2017-03-07 12:52:57 -05:00
David Jay 2c8678f9e1 Merge pull request #391 from coralproject/favicon
Favicon Support
2017-03-07 12:52:28 -05:00
Riley Davis 5e4c251e83 style the pending comment correctly 2017-03-07 10:47:51 -07:00
David Jay 2a6236b618 Moving ModerationMenu to a stateless compenent. 2017-03-07 12:37:12 -05:00
gaba f8bc1037d2 Adds the reason. 2017-03-07 18:36:27 +01:00
David Jay 54aa3bc8fa Styling load more button. 2017-03-07 12:31:43 -05:00
Riley Davis 3a31ca8d38 blur the comment until it's ready 2017-03-07 10:06:03 -07:00
gaba 62423e620e Remove edge on user. 2017-03-07 18:04:50 +01:00
David Jay d5b46f219e Adding load more button. 2017-03-07 11:54:44 -05:00
gaba 54354c3cc6 Translation. 2017-03-07 16:27:12 +01:00
gaba 564b8d8eaa Merge branch 'master' into story-138187767-mod-flag-names 2017-03-07 13:04:28 +01:00
gaba d06a5d37f0 Moves to the loader and work on the user query to add pagination and action type. 2017-03-07 07:29:32 +01:00
Wyatt Johnson d82f3d0f0d Added initial recaptcha + RL support 2017-03-06 15:26:44 -07:00
Riley Davis 09556c9bef indention 2017-03-06 14:06:32 -07:00
Belen Curcio d0e67592c0 Adding favicon :) 2017-03-06 16:05:56 -05:00
Riley Davis d9d086c61e remove a banned comment after it's confirmed as rejected 2017-03-06 13:53:56 -07:00
Riley Davis 832f3c8abc Merge branch 'master' into configure-bugs 2017-03-06 13:52:54 -07:00
Riley Davis aca2af44e2 Merge branch 'master' into notification-warning 2017-03-06 13:51:25 -07:00
Riley Davis b7fe4c6802 Merge pull request #390 from coralproject/password-reset-view-fix
Fix password reset view render path
2017-03-06 13:21:37 -07:00
Wyatt Johnson 1c81d0a04f Update index.js 2017-03-06 13:00:22 -07:00
David Jay 450f1fdbac Focusing on reply box when reply is clicked. 2017-03-06 13:25:01 -05:00
Riley Davis 5cb7172bbe Merge branch 'master'
Conflicts:
	client/coral-embed-stream/src/Embed.js
2017-03-06 11:01:37 -07:00
Riley Davis 000833509f remove notification reducer. have parent show notifications 2017-03-06 10:59:12 -07:00
Belen Curcio e80d6e7b0a Configure UX Style Polish! 2017-03-06 12:12:26 -05:00
David Jay 77386fb21d Merge pull request #385 from coralproject/version-endpoint-fix
Moved to correct route
2017-03-06 12:04:19 -05:00
Wyatt Johnson 56350ffe1a Moved to correct route 2017-03-06 09:58:21 -07:00
Belen Curcio 6cd75b57dc border box 2017-03-03 16:18:58 -05:00
David Jay 3280ed9ae5 Merge branch 'master' into premod-links 2017-03-03 16:17:40 -05:00
Belen Curcio ec4fc566c1 props 2017-03-03 16:15:30 -05:00
David Jay be8332da66 Switching link action from flag to premod. 2017-03-03 16:12:53 -05:00
David Jay b9488dc36c Updating moderation. 2017-03-03 16:09:34 -05:00
David Jay b912b60428 Removing unecessary log statement. 2017-03-03 16:01:53 -05:00
Belen Curcio 1f0d102115 new components!! textarea stuff and configure bugs 2017-03-03 15:53:53 -05:00
David Jay 2e5d453ea7 Updating link search and adding settings in admin panel. 2017-03-03 15:44:03 -05:00
Belen Curcio a1a45a7969 Styles and disable and enable things 2017-03-03 15:10:15 -05:00
David Jay cb88f0a9b7 Flagging links when settings are set appropriately. 2017-03-03 14:32:57 -05:00
Kim Gardner 6c4282128e Merge pull request #382 from coralproject/my_history_comments_reverse
Reverse chronological order for My Comment History
2017-03-03 13:45:08 -05:00
Kim Gardner 1d1aa9c30e Merge branch 'master' into my_history_comments_reverse 2017-03-03 13:41:18 -05:00
Belen Curcio bb05fa5325 Reverse chronos 2017-03-03 13:37:10 -05:00
Kim Gardner f8d7a94b76 Merge pull request #379 from coralproject/140895223
Stories and Moderate on individual steam comment components
2017-03-03 13:35:11 -05:00
Belén Curcio 740aa88958 Merge branch 'master' into 140895223 2017-03-03 13:30:39 -05:00
Kim Gardner 6b4c303012 Merge pull request #381 from coralproject/star
Best Comments - Heart to Star and Coral Icon added
2017-03-03 13:29:39 -05:00
Kim Gardner 74d9b5814f Merge branch 'master' into star 2017-03-03 13:24:32 -05:00
Belen Curcio 79102de9d2 Star and Coral UI added :D 2017-03-03 13:21:42 -05:00
Belén Curcio a21bdff12c Merge branch 'master' into 140895223 2017-03-03 13:14:10 -05:00
Wyatt Johnson 3a581941e9 Merge pull request #380 from coralproject/version-endpoint
Added Version endpoint
2017-03-03 11:13:57 -07:00
Belén Curcio 853a0ef8b0 Merge branch 'master' into 140895223 2017-03-03 13:12:40 -05:00
Wyatt Johnson 3fc349dbe4 Fixed endpoint... 2017-03-03 11:08:59 -07:00
Wyatt Johnson 840bfad06f Simplfied endpoint 2017-03-03 11:04:46 -07:00
Wyatt Johnson 646d1aa585 Added version endpoint 2017-03-03 11:03:11 -07:00
Belen Curcio 5da1af8387 Styles ready! 2017-03-03 13:00:22 -05:00
David Jay 45208b096f Correcting bug in prev keyboard shortcut. 2017-03-03 12:52:20 -05:00
David Jay 3ecb93365a Correcting bug in keyboard shortcut next and previous. 2017-03-03 12:50:39 -05:00
Wyatt Johnson 5e8ee8ccb6 Merge pull request #378 from coralproject/bump-version-110
Update for major release to include Best Comments
2017-03-03 10:04:58 -07:00
Belen Curcio d218979ee6 Show Story, show and hide Moderate 2017-03-03 11:46:09 -05:00
Kim Gardner d65fb64b7e Update for major release to include Best Comments 2017-03-03 11:28:22 -05:00
Kim Gardner 8ebf6cb35f Merge pull request #377 from coralproject/bump-version-101
Bump version for 1.0.1 release
2017-03-03 10:08:26 -05:00
Kim Gardner 9079756ec2 Merge branch 'master' into bump-version-101 2017-03-03 10:04:39 -05:00
Kim Gardner 03a4f246f5 Merge pull request #376 from coralproject/keyboard-shortcuts
Keyboard shortcuts
2017-03-03 10:03:52 -05:00
Kim Gardner b5653eca2d Merge branch 'master' into keyboard-shortcuts 2017-03-03 09:59:45 -05:00
Kim Gardner 65c9bc550e Merge branch 'master' into bump-version-101 2017-03-03 09:59:15 -05:00
Kim Gardner f40211031b Merge pull request #350 from gobengo/138617379_best_comments
Best Comments
2017-03-03 09:56:03 -05:00
Belén Curcio f30275e375 Merge branch 'master' into 138617379_best_comments 2017-03-03 09:51:25 -05:00
Kim Gardner edd694cfa5 Bump version for 1.0.1 release 2017-03-03 06:55:55 -05:00
gaba 0fe456f574 Fix suspend button. 2017-03-02 16:43:52 -08:00
gaba 2fa832a3f4 adds suspendUser func and mutation. 2017-03-02 15:54:50 -08:00
David Jay b92ea72b78 Re-enabling premod links in settings. 2017-03-02 16:44:36 -05:00
Riley Davis e3f6327b86 messages show up 2017-03-02 14:43:26 -07:00
Kim Gardner f7d19dcd68 Merge pull request #371 from coralproject/bugfix-e2e-permalink
Fix e2e
2017-03-02 16:34:50 -05:00
Kim Gardner da6d5d58b3 Merge branch 'master' into bugfix-e2e-permalink 2017-03-02 16:26:54 -05:00
David Jay 57a7000e59 Animating transition in mod queue. 2017-03-02 16:08:24 -05:00
David Jay d554bf5211 Adding single view and restyling selected comment. 2017-03-02 16:03:18 -05:00
David Jay 1146f22613 Accepting and rejecting comments w/ keystrokes. 2017-03-02 15:41:51 -05:00
David Jay 78be17ec6d Adding keyboard selection of comments. 2017-03-02 15:19:33 -05:00
David Jay 7be3b05f22 Re-enabling keyboard shortcut menu. 2017-03-02 14:51:51 -05:00
Riley Davis 51b8f660b2 Merge pull request #373 from coralproject/node-gyp-fix
node-gyp fix
2017-03-02 12:47:52 -07:00
Wyatt Johnson 3b21026526 Merge branch 'master' into node-gyp-fix 2017-03-02 12:38:38 -07:00
Wyatt Johnson 6ae7a89642 Merge pull request #375 from coralproject/un-jank
only trigger a re-flow when the height actually changes
2017-03-02 12:38:25 -07:00
Riley Davis a6c7fe85b9 remove querySelector call 2017-03-02 12:34:10 -07:00
Riley Davis 2246728c80 only trigger a re-flow when the height actually changes 2017-03-02 12:27:31 -07:00
Wyatt Johnson 6c4db2d4ca Merge branch 'master' into node-gyp-fix 2017-03-02 12:10:39 -07:00
Wyatt Johnson d04b230b3f Merge pull request #372 from coralproject/session-timeout-extension
Adjusted session timeouts
2017-03-02 12:10:29 -07:00
Wyatt Johnson daf0d38c26 fixes #362 2017-03-02 12:07:42 -07:00
David Jay 06f9c12f5e Updating comment count. 2017-03-02 13:11:16 -05:00
David Jay 609af767e8 Merge branch 'master' of github.com:coralproject/talk into bugfix-e2e-permalink 2017-03-02 13:04:32 -05:00
David Jay acda388d4e Updating e2e tests. 2017-03-02 13:00:54 -05:00
Wyatt Johnson 21783edbfe Adjusted session details 2017-03-02 10:59:19 -07:00
gaba 1899dcbb7e Suspend User dialog. 2017-03-02 09:27:53 -08:00
gaba 3626d2678e Accept button working. Add suspend user dialog. 2017-03-01 23:43:25 -08:00
Wyatt Johnson ec09f1ca44 Merge branch 'master' into 138617379_best_comments 2017-03-01 16:47:03 -07:00
gaba acc9b2d9bf Approve and Reject buttons. Still misssing suspending 2017-03-01 14:21:49 -08:00
gaba f5ef33d24e Ban Button with graphql mutation. 2017-03-01 12:57:12 -08:00
Wyatt Johnson eb8b733ac7 Merge branch 'master' into 138617379_best_comments 2017-03-01 11:46:04 -07:00
Wyatt Johnson 69deba245a Merge branch 'master' into 138617379_best_comments 2017-03-01 10:32:57 -07:00
Wyatt Johnson a1e2077ef1 Merge branch 'master' into 138617379_best_comments 2017-03-01 09:43:44 -07:00
Benjamin Goering 5cb522c419 Merge branch 'master' into 138617379_best_comments 2017-03-01 20:49:04 +08:00
Benjamin Goering ab70ac38fd If fail to addremov best comment tag, show user an error 2017-03-01 16:07:25 +08:00
Benjamin Goering 90730e52be Make all comment action buttons use cursor:pointer and nowrap (sam said ok) 2017-03-01 15:02:49 +08:00
Benjamin Goering 5e0d01a649 lift findById out of add,removeCommentTag mutators and into root_mutation.js defs 2017-03-01 14:15:31 +08:00
gaba 6dfd3fc4ef Fix the people tag. 2017-02-28 22:10:14 -08:00
Benjamin Goering b0036ef778 Remove findById in error conditions of CommentsService@addTag,removeTag 2017-03-01 14:09:03 +08:00
Benjamin Goering 4b2cc72e1a Remove unnecessary defensive code in CommentsService 2017-03-01 14:01:12 +08:00
Benjamin Goering 405dac292c Only transform-async-to-generator for client code. Allows async to work with mocha tests (if proper node version) 2017-03-01 13:43:39 +08:00
gaba da3c812dc1 Adds graphql for users flagged in the server and client. 2017-02-28 21:33:01 -08:00
Benjamin Goering 388c3182ea Change add,removeTag in CommentsService to use simpler query then elemMatch 2017-03-01 13:08:36 +08:00
Benjamin Goering 378622f7e8 commentTag mutation tests await SettingsService 2017-03-01 12:46:35 +08:00
Benjamin Goering d6167e10ba Revert ALLOWED_TAGS declaration to be arr of objects, not strings 2017-02-28 23:57:35 +08:00
Benjamin Goering 32152fee7e add newline that eslint wants 2017-02-28 23:56:53 +08:00
Benjamin Goering 3a2f893c24 eslintrc.json doesn't use babel-eslint parser, but does set ecmaVersion to 2017 2017-02-28 23:46:02 +08:00
Benjamin Goering 97ebb66f8c Remove @TODOs I don't care about 2017-02-28 20:09:24 +08:00
Benjamin Goering d9df38fc3a fix e2e tests with .comment selector that avoids hidden dialog 2017-02-28 20:00:41 +08:00
Benjamin Goering f8479a8661 fix e2e tests with .comment selector that avoids hidden dialog 2017-02-28 20:00:41 +08:00
Benjamin Goering fe8cf2c612 Style multiple tag labels and material-icon therein (edited dist/../default.css before :/) 2017-02-28 19:59:35 +08:00
Benjamin Goering 96d1447602 remove debugging code from embedStreamPage 2017-02-28 19:59:35 +08:00
Benjamin Goering af23717988 Indicate best comments. Add some aria labels 2017-02-28 19:59:34 +08:00
Benjamin Goering c2d199a21c Remove BestButton copy for now 2017-02-28 19:59:34 +08:00
Benjamin Goering c6df602b94 Remove cursor: pointer from BestButton for now. And remove unneeded container 2017-02-28 19:59:34 +08:00
Benjamin Goering 51452a6b33 Comment component passes addTag,removeTag to its replies' Comments 2017-02-28 19:59:34 +08:00
Benjamin Goering 7e3c5820dc BestButton is disabled while its saving 2017-02-28 19:59:33 +08:00
Benjamin Goering bce84f24fd Embed can remove best tag 2017-02-28 19:59:33 +08:00
Benjamin Goering 98f3033326 Add permission checking to addCommentTag,removeCommentTag 2017-02-28 19:59:33 +08:00
Benjamin Goering 30ee80b12c basic working removeCommentTag mutation 2017-02-28 19:59:33 +08:00
Benjamin Goering 4ff6bce8ec Add ComentsService#removeTag 2017-02-28 19:59:33 +08:00
Benjamin Goering 1bb20923a2 Fix Commenter/BestCommentTest to postComment before checking things. 2017-02-28 19:59:32 +08:00
Benjamin Goering 6e338290a1 addCommentTag mutation works 2017-02-28 19:59:32 +08:00
Benjamin Goering fc9c67b05c BestButton sends mutations to backend, but they aren't implemented yet 2017-02-28 19:59:32 +08:00
Benjamin Goering 907c4526da BestButton only shows if currentUser can use it (and e2e) 2017-02-28 19:57:50 +08:00
Benjamin Goering a26a60cc67 minial best button uionly and e2e 2017-02-28 19:57:50 +08:00
gaba ca4b03872b Merge branch 'master' into story-138187767-mod-flag-names 2017-02-27 09:39:52 -08:00
gaba 714b496c9d Merge branch 'master' into story-138187767-mod-flag-names 2017-02-27 07:46:23 -08:00
gaba 7557592dc4 Get more data into the store. 2017-02-24 16:06:18 -08:00
gaba ad1e640b87 Adds emptystate. 2017-02-23 16:44:30 -08:00
gaba b01db3297d Adds menu for Community. Rename community to people. Adds account flagged tabs. 2017-02-23 16:37:16 -08:00
365 changed files with 11544 additions and 2817 deletions
+15
View File
@@ -1,3 +1,18 @@
# excluded because we'll likely need to rebuild this.
node_modules
# scripts are used during development and testing, not
# production.
scripts
# documentation should not be visable in production.
docs
# static assets are rebuild in the docker container.
dist
# tests are not run in the docker container.
test
# we won't use the .git folder in production.
.git
+1 -4
View File
@@ -2,14 +2,11 @@ root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[{package.json,.*rc,*.yml}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
+4
View File
@@ -1,3 +1,7 @@
dist
client/lib
**/*.html
plugins/*
!plugins/coral-plugin-facebook-auth
!plugins/coral-plugin-respect
node_modules
+3
View File
@@ -4,6 +4,9 @@
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2017
},
"rules": {
"indent": ["error",
2
+16 -7
View File
@@ -1,14 +1,23 @@
node_modules
npm-debug.log*
dist
!dist/coral-admin
dist/coral-admin/bundle.js
test/e2e/reports
.DS_Store
*.iml
*.swp
npm-debug.log*
dump.rdb
.env
*.cfg
.idea/
*.swp
test/e2e/reports
coverage/
plugins.json
plugins/*
!plugins/coral-plugin-facebook-auth
!plugins/coral-plugin-respect
!plugins/coral-plugin-offtopic
**/node_modules/*
+1 -1
View File
@@ -1,5 +1,5 @@
{
"verbose": true,
"ignore": ["test/*", "client/*", "dist/*"],
"ignore": ["test/*", "client/*", "dist/*", "plugins/*/client"],
"ext": "js,json,graphql"
}
+3 -2
View File
@@ -10,10 +10,11 @@ By contributing to this project you agree to the [Code of Conduct](https://coral
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.
If you'd like to see our longer term roadmap, [get in touch.](https://coralproject.net/contact.html)
## 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 looking for _documentarians_ to:
@@ -55,7 +56,7 @@ Please [contact us](https://github.com/coralproject/talk/wiki/Contact-Us) early
To get an idea of where the Coral Team is going, see:
* our [product/design Trello board](https://trello.com/b/ILND751a/talk),
* our [Slack channel](https://coralprojectslackin.herokuapp.com/), where you can talk directly with the Team and other community members,
* our [current stories](https://www.pivotaltracker.com/n/projects/1863625), and
* our [issues](https://github.com/coralproject/talk/issues).
+12 -10
View File
@@ -1,23 +1,25 @@
FROM node:7.6
# Add node-gyp for bcrypt build support
RUN yarn global add node-gyp
FROM node:7.8
# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Setup the environment
ENV NODE_ENV production
ENV PATH /usr/src/app/bin:$PATH
ENV TALK_PORT 5000
EXPOSE 5000
# Install app dependencies
COPY package.json yarn.lock /usr/src/app/
RUN yarn install --production
# Bundle app source
COPY . /usr/src/app
CMD [ "yarn", "start" ]
# Install app dependencies and build static assets.
RUN yarn install --frozen-lockfile && \
cli plugins reconcile && \
yarn build && \
yarn install --production && \
yarn cache clean
# Ensure the runtime of the container is in production mode.
ENV NODE_ENV production
CMD ["yarn", "start"]
+14
View File
@@ -0,0 +1,14 @@
FROM coralproject/talk:latest
# Bundle app source
ONBUILD COPY . /usr/src/app
# At this stage, we need to install the development dependancies again because
# we need to have webpack available. We then build the new dependancies and
# clear out the development dependancies again. After this we of course need to
# clear out the yarn cache, this saves quite a lot of size.
ONBUILD RUN NODE_ENV=development yarn install --frozen-lockfile && \
NODE_ENV=production cli plugins reconcile && \
NODE_ENV=production yarn build && \
NODE_ENV=production yarn install --production --force && \
yarn cache clean
+218 -37
View File
@@ -1,37 +1,204 @@
## Contents
- [Installation](#installation) - install the application on a machine
- [Via Docker](#installation-from-docker)
- [Via Source](#installation-from-source)
- [Setup](#setup) - setup the application for first use
- [Usage](#usage) - connect the application to a website
# Installation
## Requirements
### System
- Any flavour of Linux, OSX or Windows
- 1GB memory (minimum)
- 5GB storage (minimum)
- [MongoDB](https://www.mongodb.com/) v3.4 or later
- [Redis](https://redis.io/) v3.2 or later
- SSL Certificate
- This application assumes that you will be serving this application in a
production environment, and therefore requires that you serve it behind a
webserver with a valid SSL certificate. This is chosen in order to secure
user's sessions.
## Installation From Docker
We currently support packaging the Talk application via Docker, which automates
the dependency install and asset build process. This is the recommended way to
deploy the application when used in production.
Available as [coralproject/talk](https://hub.docker.com/r/coralproject/talk/) on Docker Hub.
Images are tagged using the following notation:
- `x` (where `x` is the major version number): any minor or patch updates will be included in this. If you're ok getting
new features occasionally and all the bug fixes, this is the tag for you.
- `x.y` (where `y` is the minor version number): any patch updates will be
included with this tag. If you like getting fixes and having features change
only when you want, this is the tag for you. **(recommended)**
- `x.y.z` (where `z` is the patch version): this tag never gets updated, and
essentially freezes your version, this should only be used when you are either
extending Talk or are sure of a specific version you want to freeze.
We provide tags with `*-onbuild` that can be used for easy plugin integration and
acts as a customization endpoint. Instructions are provided in the `PLUGINS.md`
document as to how to use it.
### Requirements
There are some runtime requirements for running Talk for Docker:
- [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. Incorrect versions
of these may lead to unexpected errors!_
### Installing
An example docker-compose.yml:
```yaml
version: '2'
services:
talk:
image: coralproject/talk:1.5
restart: always
ports:
- "5000:5000"
depends_on:
- mongo
- redis
environment:
- TALK_MONGO_URL=mongodb://mongo/talk
- TALK_REDIS_URL=redis://redis
mongo:
image: mongo:3.2
restart: always
volumes:
- mongo:/data/db
redis:
image: redis:3.2
restart: always
volumes:
- redis:/data
volumes:
mongo:
external: false
redis:
external: false
```
At this stage, you should refer to the `README.md` for configuration variables
that are specific to your installation. Some pre-defined fields have been filled
in the above example which are consistent with Docker Compose naming conventions
for [Docker Links](https://docs.docker.com/compose/networking/#links).
### Scaling
If you are interested in splitting apart services, you can simply adjust the
command being executed in the container to optimize for your use case. An
example would be if you wanted to run the API server and the job processor
on different machines. You can achieve this easily with docker compose:
```yaml
version: '2'
services:
talk-api:
image: coralproject/talk:1.5
command: cli serve
restart: always
ports:
- "5000:5000"
depends_on:
- mongo
- redis
environment:
- TALK_MONGO_URL=mongodb://mongo/talk
- TALK_REDIS_URL=redis://redis
talk-jobs:
image: coralproject/talk:1.5
command: cli jobs process
restart: always
ports:
- "5001:5000"
depends_on:
- mongo
- redis
environment:
- TALK_MONGO_URL=mongodb://mongo/talk
- TALK_REDIS_URL=redis://redis
mongo:
image: mongo:3.2
restart: always
volumes:
- mongo:/data/db
redis:
image: redis:3.2
restart: always
volumes:
- redis:/data
volumes:
mongo:
external: false
redis:
external: false
```
Note that the only difference is in the `command` key. From this, you are able
to discretely control which modules are running in order to have the maximum
flexibility when managing your application.
### Running
If you're using docker compose:
```bash
# Start the services using compose
docker-compose up -d
```
If you're using plain docker:
```bash
docker run -d -P coralproject/talk:latest
```
## Installation From Source
This provides information on how to setup the application from source. Note that
this is not recommended for production deploys, but will work for development
and testing purposes.
### 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
- [Node](https://nodejs.org/) ~7.8
- [Yarn](https://yarnpkg.com/) ^0.22.0
_Please be sure to check the versions of these requirements. Insufficient versions of these may lead to unexpected errors!_
_Please be sure to check the versions of these requirements. Incorrect versions
of these may lead to unexpected errors!_
### Installing
#### Download
It is highly recommended that you download a released version as the code
available in `master` may not be stable. You can download the latest release
from the [releases page](https://github.com/coralproject/talk/releases).
You can also clone the git repository via:
```bash
# Download the tarball containing the repository
curl -L https://github.com/coralproject/talk/tarball/master -o coralproject-talk.tar.gz
git clone https://github.com/coralproject/talk.git
```
# Untar that file and change to that directory
tar xpf coralproject-talk.tar.gz
mv coralproject-talk-* coralproject-talk
cd coralproject-talk
#### Building
We now have to install the dependencies and build the static assets.
```bash
# Install package dependancies
yarn
@@ -39,6 +206,17 @@ yarn
yarn build
```
After you create/modify the `plugins.json` (refer to `PLUGINS.md` for plugin
docs) file, you can re-run the following to install their dependencies:
```bash
# Reconcile plugins
./bin/cli plugins reconcile
# Build static files
yarn build
```
### Running
Refer to the `README.md` file for required configuration variables to add to the
@@ -50,45 +228,48 @@ You can start the server after configuring the server using the command:
yarn start
```
This will setup the server to serve everything on a single node.js process and
is designed to be used in production.
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
- `yarn dev-start` watch for changes to server files and reload the server while
also sourcing a `.env` file in your local directory for configuration
## Installation From Docker Hub
# Setup
### Requirements
Once you've installed Talk (either via Docker or source), you still need to
setup the application. If you are unfamiliar with any terminoligy used in the
setup process, refer to the `TERMINOLOGY.md` document.
There are some runtime requirements for running Talk for Docker:
## Via Web
- [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
If you want to perform your setup via the web, you can navigate to your
installation of Talk at the path `/admin/install`. There you will be asked a
series of questions for your installation.
_Please be sure to check the versions of these requirements. Insufficient versions of these may lead to unexpected errors!_
## Via CLI
### Installing
If you want to perform your setup through the terminal, you can simply run:
```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
cli setup
```
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.
And follow the instructions to perform initial setup and create your first user
account.
### Running
```bash
# Start the services using compose
docker-compose up -d
```
# Usage
After setup is complete, you can then refer to the `/admin/configure` path to
get the embed code that you can copy/paste onto your blog or website in order to
start using Talk.
_In order for the embed to work correctly, you will need to whitelist the domain
that is allowed to embed your site on the `/admin/configure` page, failure to do
so will result in the comment stream not loading._
+436
View File
@@ -0,0 +1,436 @@
# Talk Plugins
Plugins for Talk can take various forms, currently we are only supporting server
side plugins.
## Plugin Registration
The parsing order for the plugin registration is as follows:
- `TALK_PLUGINS_JSON` environment variable
- `plugins.json` file
- `plugins.default.json` file
If you need to "disable all plugins", you can simply provide `{}` as the
contents of `process.env.TALK_PLUGINS_JSON` or the `plugins.json`.
The format for this is thus:
```json
{
"server": [
"people"
]
}
```
Where we have a `server` key with an array of plugins that match the folder
name in the `plugins/` folder. For example, the above config would
require a plugin from `plugins/people`, which must provide a `index.js` file
that returns an object that matches the Plugin Specification.
If the package is external (available on NPM) you can specify the string for
the version by using an object instead, for example:
```json
{
"server": [
{"people": "^1.2.0"}
]
}
```
External plugins can be resolved by running:
```bash
./bin/cli plugins reconcile
```
This achieves two things:
1. It will traverse into local plugin folders and install their dependencies.
_Note that if the plugin is already installed and available in the node_modules folder, it will not be
fetched again unless there is a version mismatch._ This will result in the
project `package.json` and `yarn.lock` files to be modified, this is normal as
this ensures that repeated deployments (with the same config) will have the
same config, these changes should not be committed to source control.
2. It will seek out dependencies that are listed in the object notation and try
to install them from npm.
## Plugin Dependencies
You may also include additional external dependencies in your local packages by
specifying a `package.json` at your plugin root which will result in a
`node_modules` folder being generated at the plugin root with your specific
dependencies.
## Deployment Solutions
Plugins can be deployed with a production instance of Talk.
### Source
Source deployments can just modify the `plugins.json` file and include any
local plugins into the `plugins/` directory. After including the config, you
need to reconcile the plugins and build the static assets:
```bash
# get plugin dependancies and remote plugins
./bin/cli plugins reconcile
# build staic assets (including enabled client side plugins)
yarn build
```
Then the application can be started as is.
### Docker
If you deploy using Docker, you can extend from the `*-onbuild` image, an
example `Dockerfile` for your project could be:
```Dockerfile
FROM coralproject/talk:latest-onbuild
```
Where the directory for your instance would contain a `plugins.json` file
describing the plugin requirements and a `plugins` directory containing any
other local plugins that should be included.
Onbuild triggers will execute when the image is building with your custom
configuration and will ensure that the image is ready to use by building all
assets inside the image as well.
## Server Plugins
### API
You can access any API available inside the talk directory in a plugin by simply
importing the file relative to the talk project root. An example would be if you
wanted to import the `MetadataService`, you would simply write:
```javascript
const MetadataService = require('services/metadata');
```
### Specification
Each plugin should export a single object with all hooks available on it.
_**Note: You will have access to the whole core and other plugin's typeDefs,
context, loaders, mutators, resolvers, hooks. This is intentional, as it
encourages composing plugins to merge functionality, like a Slack plugin which
provides a Slack notify context function as well as having the loader for
comments.**_
The following are the hooks available:
#### Field: `typeDefs`
```graphql
enum COLOUR {
RED
BLUE
}
type Person {
name: String!
colour: COLOUR!
}
type RootMutation {
createPerson(name: String!): Person
}
type RootQuery {
people: [Person!]
}
```
Thanks to [gql-merge](https://www.npmjs.com/package/gql-merge) the contents of
`typeDefs` should be a string that will be _merged_ with the existing type
definitions. `enum`'s will be appended to, types will be appended, and new types
will be added.
#### Field: `context`
```js
{
Slack: (context) => ({
notify: (message) => {
// return a promise after we're done sending notifications.
}
})
}
```
Any property provided here will be added to the context parameter available
inside all resolvers, loaders, mutators, and of course, other context based
plugins.
The top level item must accept a context for the request which it should use to
configure the context plugin before it would be mounted at `context.plugins`.
This plugin above would mount at: `context.plugins.Slack`, or, if you're using
[object destructuring](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), `{plugins: {Slack}}`.
#### Field: `loaders`
```js
(context) => ({
People: {
load: () => db.people.find({user: context.user})
}
})
```
Loaders should be provided as a function which returns a map which is used in
the resolvers function. These must return a promise or a value.
#### Field: `mutators`
```js
(context) => ({
People: {
create: (name) => {
return db.people.insert({user: context.user, name});
}
}
})
```
Mutators should be provided as a function which returns a map which is used in
the resolvers function. These must return a promise or a value.
#### Field: `resolvers`
```js
{
Person: {
name(obj, args, context) {
return obj.name;
},
colour(obj, args, context) {
// Bill likes the colour red, everyone else likes blue.
return obj.name === 'bill' ? 'RED' : 'BLUE';
}
},
RootQuery: {
people(obj, args, {loaders: {People}}) {
return People.load();
}
},
RootMutation: {
createPerson(obj, {name}, {mutators: {People}}) {
return People.create(name);
}
}
}
```
Should return a resolver map as described in the
[Apollo Docs](http://dev.apollodata.com/tools/graphql-tools/resolvers.html#Resolver-map).
This will merge with the existing resolvers in core and from previous plugins.
#### Field: `hooks`
```js
{
RootMutation: {
createPerson: {
post: async (obj, args, {plugins: {Slack}}, info, person) {
if (!person) {
return person;
}
await Slack.notify(`A new person just was created with name ${person.name}`);
return person;
}
}
}
}
```
Hooks here are pretty special, for each resolver field, you can specify a
pre/post hook that will execute pre and post field resolution.
If your post function accepts four parameters, then it can modify the field
result. It is *required* that the function resolves a promise (or returns) with
the modified value or simply the original if you didn't modify it.
#### Field: `router`
```js
(router) => {
router.get('/api/v1/people', (req, res) => {
res.json({people: [{name: 'Bob'}]});
});
}
```
The Router hook allows you to create a function that accepts the base express
router where you can mount any amount of middleware/routes to do any form of
action needed by external applications. We also provide the authorization
middleware via:
```js
const authorization = require('middleware/authorization');
module.exports = {
router(router) {
router.get('/api/v1/people', authorization.needed('ADMIN'), (req, res) => {
res.json({people: [{name: 'SECRET PEOPLE'}]});
});
}
}
```
#### Field: `passport`
```js
const FacebookStrategy = require('passport-facebook').Strategy;
const UsersService = require('services/users');
const {ValidateUserLogin, HandleAuthPopupCallback} = require('services/passport');
module.exports = {
passport(passport) {
passport.use(new FacebookStrategy({
clientID: process.env.TALK_FACEBOOK_APP_ID,
clientSecret: process.env.TALK_FACEBOOK_APP_SECRET,
callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`,
passReqToCallback: true,
profileFields: ['id', 'displayName', 'picture.type(large)']
}, async (req, accessToken, refreshToken, profile, done) => {
let user;
try {
user = await UsersService.findOrCreateExternalUser(profile);
} catch (err) {
return done(err);
}
return ValidateUserLogin(profile, user, done);
}));
},
router(router) {
// Note that we have to import the passport instance here, it is
// instantiated after all the strategies have been mounted.
const {passport} = require('services/passport');
/**
* Facebook auth endpoint, this will redirect the user immediatly to facebook
* for authorization.
*/
router.get('/facebook', passport.authenticate('facebook', {display: 'popup', authType: 'rerequest', scope: ['public_profile']}));
/**
* Facebook callback endpoint, this will send the user a html page designed to
* send back the user credentials upon sucesfull login.
*/
router.get('/facebook/callback', (req, res, next) => {
// Perform the facebook login flow and pass the data back through the opener.
passport.authenticate('facebook', HandleAuthPopupCallback(req, res, next))(req, res, next);
});
}
};
```
This is a full example including the routes hook to add the required components
to the application router to support a different auth strategy.
### Full Example
Contents of `plugins.json`:
```json
{
"server": [
"people"
]
}
```
Located in `plugins/people/index.js`:
```js
module.exports = {
typeDefs: `
enum COLOUR {
RED
BLUE
}
type Person {
name: String!
colour: COLOUR!
}
type RootMutation {
createPerson(name: String!): Person
}
type RootQuery {
people: [Person!]
}
`,
context: {
Slack: () => ({
notify: (message) => {
// return a promise after we're done sending notifications.
}
})
},
loaders: ({user}) => ({
People: {
load: () => db.people.find({user})
}
}),
mutators: ({user}) => ({
People: {
create: (name) => {
return db.people.insert({user, name});
}
}
}),
resolvers: {
Person: {
name(obj, args, context) {
return obj.name;
},
colour(obj, args, context) {
// Bill likes the colour red, everyone else likes blue.
return obj.name === 'bill' ? 'RED' : 'BLUE';
}
},
RootQuery: {
people(obj, args, {loaders: {People}}) {
return People.load();
}
},
RootMutation: {
createPerson(obj, {name}, {mutators: {People}}) {
return People.create(name);
}
}
},
hooks: {
RootMutation: {
createPerson: {
post: async (obj, args, {plugins: {Slack}}, info, person) => {
if (!person) {
return person;
}
await Slack.notify(`A new person just was created with name ${person.name}`);
return person;
}
}
}
}
};
```
+4 -7
View File
@@ -1,6 +1,6 @@
# Talk [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk)
A commenting platform from [The Coral Project](https://coralproject.net). 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)
Talk is currently in Beta! [Read more about Talk here.](https://coralproject.net/products/talk.html)
Third party licenses are available via the `/client/3rdpartylicenses.txt`
endpoint when the server is running with built assets.
@@ -28,12 +28,6 @@ The Talk application looks for the following configuration values either as envi
secure cookies.
- `TALK_ROOT_URL` (*required*) - root url of the installed application externally
available in the format: `<scheme>://<host>` without the path.
- `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 for email*) - username of the SMTP provider you are using.
@@ -41,6 +35,9 @@ Facebook Login enabled app.
- `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.
- `TALK_RECAPTCHA_SECRET` (*required for reCAPTCHA support*) - server secret used for enabling reCAPTCHA powered logins. If not provided it will instead default to providing only a time based lockout.
- `TALK_RECAPTCHA_PUBLIC` (*required for reCAPTCHA support*) - client secret used for enabling reCAPTCHA powered logins. If not provided it will instead default to providing only a time based lockout.
- `TALK_PLUGINS_JSON` (_optional_) - used to specify the plugin config via the environment
Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for
alternative methods of loading configuration during development.
+17 -5
View File
@@ -3,7 +3,8 @@ const bodyParser = require('body-parser');
const morgan = require('morgan');
const path = require('path');
const helmet = require('helmet');
const passport = require('./services/passport');
const {passport} = require('./services/passport');
const plugins = require('./services/plugins');
const session = require('express-session');
const enabled = require('debug').enabled;
const RedisStore = require('connect-redis')(session);
@@ -34,6 +35,7 @@ app.use(helmet({
}));
app.use(bodyParser.json());
app.use('/client', express.static(path.join(__dirname, 'dist')));
app.use('/public', express.static(path.join(__dirname, 'public')));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
@@ -45,16 +47,15 @@ const session_opts = {
secret: process.env.TALK_SESSION_SECRET,
httpOnly: true,
rolling: true,
saveUninitialized: false,
resave: false,
saveUninitialized: true,
resave: true,
unset: 'destroy',
name: 'talk.sid',
cookie: {
secure: false,
maxAge: 36000000, // 1 hour for expiry.
maxAge: 8.64e+7, // 24 hours for session token expiry
},
store: new RedisStore({
ttl: 1800,
client: redis.createClient(),
})
};
@@ -75,6 +76,17 @@ app.use(session(session_opts));
// PASSPORT MIDDLEWARE
//==============================================================================
const passportDebug = require('debug')('talk:passport');
// Install the passport plugins.
plugins.get('server', 'passport').forEach((plugin) => {
passportDebug(`added plugin '${plugin.plugin.name}'`);
// Pass the passport.js instance to the plugin to allow it to inject it's
// functionality.
plugin.passport(passport);
});
// Setup the PassportJS Middleware.
app.use(passport.initialize());
app.use(passport.session());
+1
View File
@@ -13,6 +13,7 @@ program
.command('setup', 'setup the application')
.command('jobs', 'work with the job queues')
.command('users', 'work with the application auth')
.command('plugins', 'provides utilities for interacting with the plugin system')
.parse(process.argv);
/**
+298
View File
@@ -0,0 +1,298 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
// Interface heavily inspired by the yarn package manager:
// https://yarnpkg.com/
const program = require('./commander');
// Make things colorful!
require('colors');
const emoji = require('node-emoji');
const dir = process.cwd();
const fs = require('fs');
const path = require('path');
const spawn = require('cross-spawn');
const semver = require('semver');
const resolve = require('resolve');
const {plugins, itteratePlugins, isInternal} = require('../plugins');
function existsInNodeModules(name) {
try {
resolve.sync(name, {basedir: dir});
return true;
} catch (e) {
return false;
}
}
function versionMatch(name, version) {
try {
let matched = false;
resolve.sync(name, {
basedir: dir,
packageFilter: (pkg) => {
if (pkg && pkg.version && semver.satisfies(pkg.version, version)) {
matched = true;
}
return pkg;
}
});
return matched;
} catch (e) {
return false;
}
}
const EXTERNAL = /^\w[a-z\-0-9\.]+$/; // Match "react", "path", "fs", "lodash.random", etc.
function reconcilePackages({quiet = false, upgradeRemote = false}) {
const fetchable = [];
const local = [];
const upgradable = [];
if (!quiet) {
console.log();
console.log(' +local (l) packages in your project');
console.log(' +external (e) packages are external');
console.log(' +outofdate (oe) packages are external but are out of date');
console.log(' +missing (m) packages are not found');
console.log();
}
for (let i in plugins) {
let section = itteratePlugins(plugins[i]);
for (let j in section) {
let {name, version} = section[j];
let namespaced = name.charAt(0) === '@';
let dep = name.split('/')
.slice(0, namespaced ? 2 : 1)
.join('/');
// Ignore relative modules, which aren't installed by NPM
if (!dep.match(EXTERNAL) && !namespaced) {
return;
}
if (isInternal(dep)) {
if (!quiet) {
console.log(` l ${name}`);
}
local.push({name, version});
continue;
}
if (!existsInNodeModules(dep)) {
if (!quiet) {
console.log(` m ${name}`);
}
fetchable.push({name, version});
} else if (!versionMatch(dep, version)) {
// A plugin was found, yet the current version does not match the
// current version installed. We should warn if upgradeRemote is
// not enabled that it is currently not supported.
if (!upgradeRemote) {
if (!quiet) {
console.warn(` oe ${name} (package upgrade may be required)`.bgRed);
}
continue;
}
console.log(` oe ${name} (package upgrade may be required)`);
upgradable.push({name, version});
} else {
if (!quiet) {
console.log(` e ${name}`);
}
if (upgradeRemote) {
upgradable.push({name, version});
}
}
}
}
if (!quiet) {
console.log();
}
return {local, fetchable, upgradable};
}
async function reconcileRemotePlugins({skipLocal, dryRun, upgradeRemote}) {
console.log(`\n[${skipLocal ? '1/2' : '2/3'}] ${emoji.get('mag')} Reconciling plugins...`.yellow);
const {fetchable, upgradable} = reconcilePackages({upgradeRemote});
console.log(`[${skipLocal ? '2/2' : '3/3'}] ${emoji.get('truck')} Fetching plugins...\n`.yellow);
if (fetchable.length > 0) {
console.log(`$ yarn add --ignore-scripts ${fetchable.map(({name, version}) => `${name}@${version}`.cyan)}`);
if (!dryRun) {
let args = [
'add',
'--ignore-scripts',
...fetchable.map(({name, version}) => `${name}@${version}`)
];
let output = spawn.sync('yarn', args, {
stdio: ['ignore', 'pipe', 'inherit']
});
if (output.status) {
throw new Error('Could not install external plugins, errors occured during install');
}
console.log(output.stdout.toString());
}
}
if (upgradable.length > 0) {
console.log(`$ yarn upgrade ${upgradable.map(({name, version}) => `${name}@${version}`.cyan)}`);
if (!dryRun) {
let args = [
'upgrade',
...upgradable.map(({name, version}) => `${name}@${version}`)
];
let output = spawn.sync('yarn', args, {
stdio: ['ignore', 'pipe', 'inherit']
});
if (output.status) {
throw new Error('Could not install external plugins, errors occured during install');
}
console.log(output.stdout.toString());
}
}
return {upgradable, fetchable};
}
async function reconcileLocalPlugins({skipRemote, dryRun}) {
console.log(`\n[${skipRemote ? '1/1' : '1/3'}] ${emoji.get('pick')} Installing local plugin dependencies...\n`.yellow);
const {local} = reconcilePackages({quiet: true});
for (let i in local) {
let {name} = local[i];
if (!fs.existsSync(path.join(dir, 'plugins', name, 'package.json'))) {
continue;
}
let wd = path.join(dir, 'plugins', name);
console.log(`$ cd ${wd.cyan} && yarn`);
if (!dryRun) {
let args = [];
let output = spawn.sync('yarn', args, {
stdio: ['ignore', 'pipe', 'inherit'],
cwd: wd
});
if (output.status) {
throw new Error('Could not install local plugin dependencies, errors occured during install');
}
console.log(output.stdout.toString());
}
}
}
// This traverses the local plugins and installs any dependencies listed there,
// this only is really needed for plugins that are installed via docker because
// core plugins will have their dependencies already included in core.
async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote}) {
let startTime = new Date();
// We don't need to do anything if we skip everything....
if (skipLocal && skipRemote) {
return;
}
// Traverse local plugins and install dependencies if enabled.
if (!skipLocal) {
await reconcileLocalPlugins({skipRemote, dryRun});
}
// Locate any external plugins and install them.
if (!skipRemote) {
let results = [];
try {
results = await reconcileRemotePlugins({skipLocal, skipRemote, dryRun, upgradeRemote});
} catch (e) {
throw e;
}
let status;
if (dryRun) {
status = '[dry-run] success'.green;
} else {
status = 'success'.green;
}
let message;
if (results.upgradable.length === 0 && results.fetchable.length === 0) {
message = 'Already up-to-date.';
} else if (results.upgradable.length === 0) {
message = `Fetched ${results.fetchable.length} new plugins.`;
} else if (results.fetchable.length === 0) {
message = `Upgraded ${results.upgradable.length} new plugins.`;
} else {
message = `Fetched ${results.fetchable.length} new plugins, upgraded ${results.upgradable.length} plugins.`;
}
console.log(`\n${status} ${message}`);
}
let endTime = new Date();
let totalTime = ((endTime.getTime() - startTime.getTime()) / 1000).toFixed(2);
console.log(`✨ Done in ${totalTime}s.`);
}
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
program
.command('list')
.description('')
.action(reconcilePackages);
program
.command('reconcile')
.description('reconciles local plugin dependencies and downloads external plugins')
.option('-u, --upgrade-remote', 'upgrades remote dependencies')
.option('-d, --dry-run', 'does not actually change anything on the filesystem acts only as a simulation')
.option('--skip-local', 'skips the local dependancy reconciliation')
.option('--skip-remote', 'skips the remote plugin reconciliation')
.action(reconcilePluginDeps);
program.parse(process.argv);
// If there is no command listed, output help.
if (!process.argv.slice(2).length) {
program.outputHelp();
}
+4 -3
View File
@@ -1,6 +1,6 @@
machine:
node:
version: 7.6
version: 7.9
services:
- docker
- redis
@@ -20,6 +20,7 @@ dependencies:
# - sudo service mongod restart
# Install node dependencies.
- yarn --version
- yarn
cache_directories:
- ~/.cache/yarn
@@ -47,9 +48,9 @@ deployment:
release:
tag: /v[0-9]+(\.[0-9]+)*/
commands:
- bash ./scripts/deploy.sh
- bash ./scripts/docker.sh deploy
latest:
branch: master
commands:
- bash ./scripts/deploy.sh
- bash ./scripts/docker.sh deploy
+1 -2
View File
@@ -1,15 +1,14 @@
{
"sourceMaps": true,
"presets": [
"es2015"
],
"plugins": [
"add-module-exports",
"transform-async-to-generator",
"transform-class-properties",
"transform-decorators-legacy",
"transform-object-assign",
"transform-object-rest-spread",
"transform-async-to-generator",
"transform-react-jsx"
]
}
-23
View File
@@ -1,23 +0,0 @@
# Coral Admin
This app handles moderation for Talk (and maybe more later on)
## Installation
$ yarn install
$ cp config.sample.json config.json
Then change `config.json` to adjust it to your project
## Building for production
$ yarn build
The public folder has everything you need for deployment. You can just copy that folder to your favorite static web server
## Development
$ yarn start
A development server will be running at http://localhost:4132
+17 -2
View File
@@ -1,14 +1,17 @@
import React from 'react';
import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-router';
import Streams from 'containers/Streams/Streams';
import Stories from 'containers/Stories/Stories';
import Configure from 'containers/Configure/Configure';
import LayoutContainer from 'containers/LayoutContainer';
import InstallContainer from 'containers/Install/InstallContainer';
import CommunityLayout from 'containers/Community/CommunityLayout';
import CommunityContainer from 'containers/Community/CommunityContainer';
import ModerationLayout from 'containers/ModerationQueue/ModerationLayout';
import ModerationContainer from 'containers/ModerationQueue/ModerationContainer';
import Dashboard from 'containers/Dashboard/Dashboard';
const routes = (
@@ -18,9 +21,21 @@ const routes = (
<IndexRoute component={Dashboard} />
<Route path='community' component={CommunityContainer} />
<Route path='configure' component={Configure} />
<Route path='streams' component={Streams} />
<Route path='stories' component={Stories} />
<Route path='dashboard' component={Dashboard} />
{/* Community Routes */}
<Route path='community' component={CommunityLayout}>
<Route path='flagged' components={CommunityContainer}>
<Route path=':id' components={CommunityContainer} />
</Route>
<Route path='people' components={CommunityContainer}>
<Route path=':id' components={CommunityContainer} />
</Route>
<IndexRedirect to='flagged' />
</Route>
{/* Moderation Routes */}
<Route path='moderate' component={ModerationLayout}>
+26 -9
View File
@@ -2,15 +2,28 @@ import * as actions from '../constants/auth';
import coralApi from 'coral-framework/helpers/response';
// Log In.
export const handleLogin = (email, password) => dispatch => {
export const handleLogin = (email, password, recaptchaResponse) => dispatch => {
dispatch({type: actions.LOGIN_REQUEST});
return coralApi('/auth/local', {method: 'POST', body: {email, password}})
.then(result => {
const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length;
dispatch(checkLoginSuccess(result.user, isAdmin));
const params = {method: 'POST', body: {email, password}};
if (recaptchaResponse) {
params.headers = {'X-Recaptcha-Response': recaptchaResponse};
}
return coralApi('/auth/local', params)
.then(({user}) => {
if (!user) {
return dispatch(checkLoginFailure('not logged in'));
}
const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length;
dispatch(checkLoginSuccess(user, isAdmin));
})
.catch(error => {
dispatch({type: actions.LOGIN_FAILURE, message: error.translation_key});
if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') {
dispatch({type: actions.LOGIN_MAXIMUM_EXCEEDED, message: error.translation_key});
} else {
dispatch({type: actions.LOGIN_FAILURE, message: error.translation_key});
}
});
};
@@ -34,9 +47,13 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
export const checkLogin = () => dispatch => {
dispatch(checkLoginRequest());
return coralApi('/auth')
.then(result => {
const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length;
dispatch(checkLoginSuccess(result.user, isAdmin));
.then(({user}) => {
if (!user) {
return dispatch(checkLoginFailure('not logged in'));
}
const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length;
dispatch(checkLoginSuccess(user, isAdmin));
})
.catch(error => {
console.error(error);
+21 -8
View File
@@ -7,28 +7,33 @@ import {
SORT_UPDATE,
COMMENTERS_NEW_PAGE,
SET_ROLE,
SET_COMMENTER_STATUS
SET_COMMENTER_STATUS,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG,
SHOW_SUSPENDUSER_DIALOG,
HIDE_SUSPENDUSER_DIALOG
} from '../constants/community';
import coralApi from '../../../coral-framework/helpers/response';
export const fetchCommenters = (query = {}) => dispatch => {
dispatch(requestFetchCommenters());
export const fetchAccounts = (query = {}) => dispatch => {
dispatch(requestFetchAccounts());
coralApi(`/users?${qs.stringify(query)}`)
.then(({result, page, count, limit, totalPages}) =>
.then(({result, page, count, limit, totalPages}) =>{
dispatch({
type: FETCH_COMMENTERS_SUCCESS,
commenters: result,
accounts: result,
page,
count,
limit,
totalPages
})
)
});
})
.catch(error => dispatch({type: FETCH_COMMENTERS_FAILURE, error}));
};
const requestFetchCommenters = () => ({
const requestFetchAccounts = () => ({
type: FETCH_COMMENTERS_REQUEST
});
@@ -55,3 +60,11 @@ export const setCommenterStatus = (id, status) => (dispatch) => {
return dispatch({type: SET_COMMENTER_STATUS, id, status});
});
};
// Ban User Dialog
export const showBanUserDialog = (user) => ({type: SHOW_BANUSER_DIALOG, user});
export const hideBanUserDialog = () => ({type: HIDE_BANUSER_DIALOG});
// Suspend User Dialog
export const showSuspendUserDialog = (user) => ({type: SHOW_SUSPENDUSER_DIALOG, user});
export const hideSuspendUserDialog = () => ({type: HIDE_SUSPENDUSER_DIALOG});
+7
View File
@@ -0,0 +1,7 @@
export const CONFIG_UPDATED = 'CONFIG_UPDATED';
export const fetchConfig = () => dispatch => {
let json = document.getElementById('data');
let data = JSON.parse(json.textContent);
dispatch({type: CONFIG_UPDATED, data});
};
+1 -1
View File
@@ -4,5 +4,5 @@ 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 showBanUserDialog = (user, commentId, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, showRejectedNote});
export const hideBanUserDialog = (showDialog) => ({type: actions.HIDE_BANUSER_DIALOG, showDialog});
@@ -4,6 +4,7 @@ import styles from './NotFound.css';
import {Button, TextField, Alert, Success} from 'coral-ui';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
import Recaptcha from 'react-recaptcha';
const lang = new I18n(translations);
class AdminLogin extends React.Component {
@@ -18,13 +19,22 @@ class AdminLogin extends React.Component {
this.props.handleLogin(this.state.email, this.state.password);
}
onRecaptchaLoad = () => {
// do something?
}
onRecaptchaVerify = (recaptchaResponse) => {
this.props.handleLogin(this.state.email, this.state.password, recaptchaResponse);
}
handleRequestPassword = e => {
e.preventDefault();
this.props.requestPasswordReset(this.state.email);
}
render () {
const {errorMessage} = this.props;
const {errorMessage, loginMaxExceeded, recaptchaPublic} = this.props;
const signInForm = (
<form onSubmit={this.handleSignIn}>
{errorMessage && <Alert>{lang.t(`errors.${errorMessage}`)}</Alert>}
@@ -49,6 +59,15 @@ class AdminLogin extends React.Component {
this.setState({requestPassword: true});
}}>Request a new one.</a>
</p>
{
loginMaxExceeded &&
<Recaptcha
sitekey={recaptchaPublic}
render='explicit'
theme='dark'
onloadCallback={this.onRecaptchaLoad}
verifyCallback={this.onRecaptchaVerify} />
}
</form>
);
const requestPasswordForm = (
@@ -84,9 +103,11 @@ class AdminLogin extends React.Component {
}
AdminLogin.propTypes = {
loginMaxExceeded: PropTypes.bool.isRequired,
handleLogin: PropTypes.func.isRequired,
passwordRequestSuccess: PropTypes.string,
loginError: PropTypes.string
loginError: PropTypes.string,
recaptchaPublic: PropTypes.string
};
export default AdminLogin;
@@ -5,7 +5,7 @@
i {
vertical-align: middle;
margin-right: 10px;
margin-right: 5px;
font-size: 14px;
}
}
@@ -8,7 +8,14 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
const onBanClick = (userId, commentId, handleBanUser, rejectComment, handleClose) => (e) => {
e.preventDefault();
handleBanUser({userId})
.then(handleClose)
.then(() => rejectComment({commentId}));
};
const BanUserDialog = ({open, handleClose, handleBanUser, rejectComment, user, commentId, showRejectedNote}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
@@ -23,13 +30,13 @@ const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
</div>
<div className={styles.separator}>
<h3>{lang.t('bandialog.are_you_sure', user.name)}</h3>
<i>{lang.t('bandialog.note')}</i>
<i>{showRejectedNote && lang.t('bandialog.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{lang.t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={() => handleBanUser({userId: user.id})} raised>
<Button cStyle="black" className={styles.ban} onClick={onBanClick(user.id, commentId, handleBanUser, rejectComment, handleClose)} raised>
{lang.t('bandialog.yes_ban_user')}
</Button>
</div>
@@ -40,6 +47,8 @@ const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
BanUserDialog.propTypes = {
handleBanUser: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
commentId: PropTypes.string,
user: PropTypes.object.isRequired,
};
@@ -1,71 +0,0 @@
import React from 'react';
import timeago from 'timeago.js';
import Linkify from 'react-linkify';
import styles from './ModerationList.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
import Highlighter from 'react-highlight-words';
import {Icon} from 'coral-ui';
import ActionButton from './ActionButton';
const linkify = new Linkify();
// Render a single comment for the list
const Comment = props => {
const {comment, author} = props;
let authorStatus = author.status;
const links = linkify.getMatches(comment.body);
return (
<li tabIndex={props.index} className={`mdl-card mdl-shadow--2dp ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span>{author.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 className={styles.sideActions}>
{links ?
<span className={styles.hasLinks}><Icon name='error_outline'/> Contains Link</span> : null}
<div className={`actions ${styles.actions}`}>
{props.modActions.map(
(action, i) =>
<ActionButton
option={action}
key={i}
type='COMMENTS'
comment={comment}
user={author}
menuOptionsMap={props.menuOptionsMap}
onClickAction={props.onClickAction}
onClickShowBanDialog={props.onClickShowBanDialog}/>
)}
</div>
{authorStatus === 'banned' ?
<span className={styles.banned}><Icon name='error_outline'/> {lang.t('comment.banned_user')}</span> : null}
</div>
</div>
<div className={styles.itemBody}>
<span className={styles.body}>
<Linkify component='span' properties={{style: linkStyles}}>
<Highlighter
searchWords={props.suspectWords}
textToHighlight={comment.body} />
</Linkify>
</span>
</div>
</li>
);
};
export default Comment;
const linkStyles = {
backgroundColor: 'rgb(255, 219, 135)',
padding: '1px 2px'
};
const lang = new I18n(translations);
@@ -0,0 +1,83 @@
import React, {PropTypes} from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
import styles from 'coral-admin/src/containers/Dashboard/Dashboard.css';
import {Icon} from 'coral-ui';
const lang = new I18n(translations);
const refreshIntervalSeconds = 60 * 5;
class CountdownTimer extends React.Component {
static propTypes = {
handleTimeout: PropTypes.func.isRequired
}
constructor (props) {
super(props);
try {
if (window.localStorage.getItem('coral:dashboardNote') === null) {
window.localStorage.setItem('coral:dashboardNote', 'show');
}
} catch (e) {
// above will fail in Private Mode in some browsers.
}
this.state = {
secondsUntilRefresh: refreshIntervalSeconds,
dashboardNote: window.localStorage.getItem('coral:dashboardNote') || 'show'
};
}
componentWillMount () {
this.interval = setInterval(() => { // the countdown timer
let nextCount = this.state.secondsUntilRefresh - 1;
if (nextCount < 0) {
nextCount = refreshIntervalSeconds;
return this.props.handleTimeout();
}
this.setState({secondsUntilRefresh: nextCount});
}, 1000);
}
componentWillUnmount () {
window.clearInterval(this.interval);
}
formatTime = () => {
const minutes = Math.floor(this.state.secondsUntilRefresh / 60);
let seconds = (this.state.secondsUntilRefresh % 60).toString();
if (seconds.length < 2) {
seconds = `0${seconds}`;
}
return `${minutes}:${seconds}`;
}
dismissNote = () => {
try {
window.localStorage.setItem('coral:dashboardNote', 'hide');
} catch (e) {
// when setItem fails in Safari Private mode
this.setState({dashboardNote: 'hide'});
}
}
render () {
const hideReloadNote = window.localStorage.getItem('coral:dashboardNote') === 'hide' ||
this.state.dashboardNote === 'hide'; // for Safari Incognito
return (
<p
style={{display: hideReloadNote ? 'none' : 'block'}}
className={styles.autoUpdate}
onClick={this.dismissNote}>
<b>×</b>
<Icon name='timer' /> <strong>{lang.t('dashboard.next-update', this.formatTime())}</strong> {lang.t('dashboard.auto-update')}
</p>
);
}
}
export default CountdownTimer;
@@ -0,0 +1,622 @@
$minHeight: 200px;
$fullscreenZIndex: 10;
.wrapper {
/* Fix blockquote styling of MDL: https://github.com/google/material-design-lite/issues/2037 */
blockquote {
> p:first-child {
padding-top: 16px;
}
> p:last-child {
margin-bottom: 0;
}
&::after {
margin-left: -0.5em;
}
}
}
.iconBold {
composes: material-icons from global;
&::before {
content: 'format_bold';
}
}
.iconItalic {
composes: material-icons from global;
&::before {
content: 'format_italic';
}
}
.iconTitle {
composes: material-icons from global;
&::before {
content: 'title';
}
}
.iconQuote {
composes: material-icons from global;
&::before {
content: 'format_quote';
}
}
.iconUnorderedList {
composes: material-icons from global;
&::before {
content: 'format_list_bulleted';
}
}
.iconOrderedList {
composes: material-icons from global;
&::before {
content: 'format_list_numbered';
}
}
.iconLink {
composes: material-icons from global;
&::before {
content: 'link';
}
}
.iconImage {
composes: material-icons from global;
&::before {
content: 'insert_photo';
}
}
.iconPreview {
composes: material-icons from global;
&::before {
content: 'remove_red_eye';
}
}
.iconSideBySide {
composes: material-icons from global;
&::before {
content: 'chrome_reader_mode';
}
}
.iconFullscreen {
composes: material-icons from global;
&::before {
content: 'fullscreen';
}
}
.iconGuide {
composes: material-icons from global;
&::before {
content: 'help_outline';
}
}
/*
* These are modified styles taken from https://github.com/NextStepWebs/simplemde-markdown-editor and
* put through http://sebastianpontow.de/css2compass/.
*/
/*
* simplemde v1.11.2
* Copyright Next Step Webs, Inc.
* @link https://github.com/NextStepWebs/simplemde-markdown-editor
* @license MIT
*/
:global {
.CodeMirror {
font-family: monospace;
color: black;
position: relative;
overflow: hidden;
background: white;
height: auto;
min-height: $minHeight;
border: 1px solid #ddd;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
padding: 10px;
font: inherit;
z-index: 1;
pre {
padding: 0 4px;
border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
font-variant-ligatures: none;
}
span {
/*TODO: vertical-align: text-bottom;*/
}
.CodeMirror-code {
.cm-tag {
color: #63a35c;
}
.cm-attribute {
color: #795da3;
}
.cm-string {
color: #183691;
}
.cm-header-1 {
font-size: 200%;
line-height: 200%;
}
.cm-header-2 {
font-size: 160%;
line-height: 160%;
}
.cm-header-3 {
font-size: 125%;
line-height: 125%;
}
.cm-header-4 {
font-size: 110%;
line-height: 110%;
}
.cm-comment {
background: rgba(0, 0, 0, .05);
border-radius: 2px;
}
.cm-link {
color: #7f8c8d;
}
.cm-url {
color: #aab2b3;
}
.cm-strikethrough {
text-decoration: line-through;
}
.cm-tab {
display: inline-block;
text-decoration: inherit;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
position: absolute;
}
.cm-header {
font-weight: bold;
}
.cm-strong {
font-weight: bold;
}
.cm-em {
font-style: italic;
}
.cm-link {
text-decoration: underline;
}
.cm-strikethrough {
text-decoration: line-through;
}
.cm-invalidchar {
color: #f00;
}
}
.CodeMirror-selected {
background: #d9d9d9;
}
.CodeMirror-placeholder {
opacity: .5;
}
div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word) {
background: rgba(255, 0, 0, .15);
}
}
.CodeMirror-lines {
padding: 4px 0;
cursor: text;
min-height: 1px;
}
.CodeMirror-scrollbar-filler {
background-color: white;
position: absolute;
z-index: 6;
display: none;
right: 0;
bottom: 0;
}
.CodeMirror-gutter-filler {
background-color: white;
position: absolute;
z-index: 6;
display: none;
left: 0;
bottom: 0;
}
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
position: absolute;
left: 0;
top: 0;
min-height: 100%;
z-index: 3;
box-sizing: content-box;
}
.CodeMirror-guttermarker {
color: black;
}
.CodeMirror-guttermarker-subtle {
color: #999;
}
.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
position: absolute;
}
@keyframes blink {
0% {
}
50% {
background-color: transparent;
}
100% {
}
}
.CodeMirror-scroll {
overflow: scroll !important;
margin-bottom: -30px;
margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none;
position: relative;
min-height: $minHeight;
box-sizing: content-box;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
box-sizing: content-box;
}
.CodeMirror-vscrollbar {
position: absolute;
z-index: 6;
display: none;
right: 0;
top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
position: absolute;
z-index: 6;
display: none;
bottom: 0;
left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -30px;
*zoom: 1;
*display: inline;
box-sizing: content-box;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
user-select: none;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0;
bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-code {
outline: none;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
pre {
position: static;
}
}
.CodeMirror-focused {
.CodeMirror-selected {
background: #d7d4f0;
}
div.CodeMirror-cursors {
visibility: visible;
}
}
.CodeMirror-selected {
background: #d9d9d9;
}
.CodeMirror-line::selection {
background: #d7d4f0;
}
.CodeMirror-line {
> span::selection {
background: #d7d4f0;
}
> span {
> span::selection {
background: #d7d4f0;
}
}
}
@media print {
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
.CodeMirror-fullscreen {
background: #fff;
position: fixed !important;
top: 50px;
left: 0;
right: 0;
bottom: 0;
height: auto;
z-index: $fullscreenZIndex;
}
.CodeMirror-sided {
width: 50% !important;
}
.editor-toolbar {
position: relative;
opacity: .6;
user-select: none;
padding: 0 10px;
border-top: 1px solid #bbb;
border-left: 1px solid #bbb;
border-right: 1px solid #bbb;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
&:after {
display: block;
content: ' ';
height: 1px;
margin-top: 8px;
}
&:before {
display: block;
content: ' ';
height: 1px;
margin-bottom: 8px;
}
&:hover {
opacity: .8;
}
&.fullscreen {
width: 100%;
height: 50px;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
padding-top: 10px;
padding-bottom: 10px;
box-sizing: border-box;
background: #fff;
border: 0;
position: fixed;
top: 0;
left: 0;
opacity: 1;
z-index: $fullscreenZIndex;
}
&.fullscreen::before {
width: 20px;
height: 50px;
background: linear-gradient(to right, rgba(255, 255, 255, 1) 0, rgba(255, 255, 255, 0) 100%);
position: fixed;
top: 0;
left: 0;
margin: 0;
padding: 0;
}
&.fullscreen::after {
width: 20px;
height: 50px;
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 1) 100%);
position: fixed;
top: 0;
right: 0;
margin: 0;
padding: 0;
}
a {
display: inline-block;
text-align: center;
text-decoration: none!important;
color: #2c3e50!important;
width: 30px;
height: 30px;
margin: 0;
border: 1px solid transparent;
border-radius: 3px;
cursor: pointer;
outline: 0;
margin-right: 2px;
&.active {
background: #fcfcfc;
border-color: #95a5a6;
}
&:hover {
background: #fcfcfc;
border-color: #95a5a6;
}
&:active {
background: #eee;
}
&:before {
line-height: 30px;
}
}
i.separator {
display: inline-block;
width: 0;
border-left: 1px solid #d9d9d9;
border-right: 1px solid #fff;
color: transparent;
text-indent: -10px;
margin: 0 6px;
}
&.disabled-for-preview a:not(.no-disable) {
pointer-events: none;
background: #fff;
border-color: transparent;
text-shadow: inherit;
}
}
@media only screen and(max-width: 700px) {
.editor-toolbar a.no-mobile {
display: none;
}
}
.editor-statusbar {
padding: 8px 10px;
font-size: 12px;
color: #959694;
text-align: right;
span {
display: inline-block;
min-width: 4em;
margin-left: 1em;
}
.lines:before {
content: 'lines: ';
}
.words:before {
content: 'words: ';
}
.characters:before {
content: 'characters: ';
}
}
.editor-preview {
padding: 10px;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: #fafafa;
z-index: 7;
overflow: auto;
display: none;
box-sizing: border-box;
> p {
margin-top: 0;
}
pre {
background: #eee;
margin-bottom: 10px;
}
table {
td {
border: 1px solid #ddd;
padding: 5px;
}
th {
border: 1px solid #ddd;
padding: 5px;
}
}
}
.editor-preview-side {
padding: 10px;
position: fixed;
bottom: 0;
width: 50%;
top: 50px;
right: 0;
background: #fafafa;
z-index: $fullscreenZIndex;
overflow: auto;
display: none;
box-sizing: border-box;
border: 1px solid #ddd;
> p {
margin-top: 0;
}
pre {
background: #eee;
margin-bottom: 10px;
}
table {
td {
border: 1px solid #ddd;
padding: 5px;
}
th {
border: 1px solid #ddd;
padding: 5px;
}
}
}
.editor-preview-active-side {
display: block;
}
.editor-preview-active {
display: block;
}
.CodeMirror-overwrite .CodeMirror-cursor {
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.cm-tab-wrap-hack:after {
content: '';
}
span.CodeMirror-selectedtext {
background: none;
}
.editor-wrapper input.title {
&:focus {
opacity: .8;
}
&:hover {
opacity: .8;
}
}
}
@@ -0,0 +1,147 @@
import React, {Component, PropTypes} from 'react';
import SimpleMDE from 'simplemde';
import cn from 'classnames';
import noop from 'lodash/noop';
import styles from './MarkdownEditor.css';
const config = {
status: false,
// Do not download fontAwesome icons as we replace them with
// material icons.
autoDownloadFontAwesome: false,
// Disable built-in spell checker as it is very rudimentary.
spellChecker: false,
toolbar: [
{
name: 'bold',
action: SimpleMDE.toggleBold,
className: styles.iconBold,
title: 'Bold',
},
{
name: 'italic',
action: SimpleMDE.toggleItalic,
className: styles.iconItalic,
title: 'Italic',
},
{
name: 'title',
action: SimpleMDE.toggleHeadingSmaller,
className: styles.iconTitle,
title: 'Title, Subtitle, Heading',
},
'|',
{
name: 'quote',
action: SimpleMDE.toggleBlockquote,
className: styles.iconQuote,
title: 'Quote',
},
{
name: 'unordered-list',
action: SimpleMDE.toggleUnorderedList,
className: styles.iconUnorderedList,
title: 'Generic List',
},
{
name: 'ordered-list',
action: SimpleMDE.toggleOrderedList,
className: styles.iconOrderedList,
title: 'Numbered List',
},
'|',
{
name: 'link',
action: SimpleMDE.drawLink,
className: styles.iconLink,
title: 'Create Link',
},
{
name: 'image',
action: SimpleMDE.drawImage,
className: styles.iconImage,
title: 'Insert Image',
},
'|',
{
name: 'preview',
action: SimpleMDE.togglePreview,
className: cn(styles.iconPreview, 'no-disable'),
title: 'Toggle Preview',
},
{
name: 'side-by-side',
action: SimpleMDE.toggleSideBySide,
className: cn(styles.iconSideBySide, 'no-disable'),
title: 'Toggle Side by Side',
},
{
name: 'fullscreen',
action: SimpleMDE.toggleFullScreen,
className: cn(styles.iconFullscreen, 'no-disable'),
title: 'Toggle Fullscreen',
},
'|',
{
name: 'guide',
action: 'https://simplemde.com/markdown-guide',
className: styles.iconGuide,
title: 'Markdown Guide',
},
],
};
export default class MarkdownEditor extends Component {
textarea = null
editor = null
onRef = (ref) => this.textarea = ref
componentDidMount() {
this.editor = new SimpleMDE({
...config,
element: this.textarea,
});
this.editor.codemirror.on('change', this.onChange);
}
componentWillReceiveProps(nextProps) {
if (this.props.value !== nextProps.value && nextProps.value !== this.editor.value()) {
this.editor.value(nextProps.value);
}
}
componentDidUpdate() {
// Workaround empty render issue.
// https://github.com/NextStepWebs/simplemde-markdown-editor/issues/313
this.editor.codemirror.refresh();
}
componentWillUnmount() {
this.editor.toTextArea();
}
onChange = () => {
if (this.props.onChange) {
this.props.onChange(this.editor.value());
}
}
render() {
return (
<div className={styles.wrapper}>
<textarea ref={this.onRef} {...this.props} onChange={noop} />
</div>
);
}
}
MarkdownEditor.propTypes = {
onChange: PropTypes.func,
value: PropTypes.string,
};
@@ -1,4 +1,3 @@
import React from 'react';
import {Button, Icon} from 'react-mdl';
import styles from './Modal.css';
@@ -178,8 +178,13 @@
}
}
.selected {
border-radius: 10px;
}
.actionButton {
transform: scale(.8);
margin: 0;
width: 140px;
}
@@ -1,106 +0,0 @@
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
@@ -1,50 +0,0 @@
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);
@@ -23,9 +23,9 @@ const CoralDrawer = ({handleLogout, restricted = false}) => (
{lang.t('configure.moderate')}
</Link>
<Link className={styles.navLink}
to="/admin/streams"
to="/admin/stories"
activeClassName={styles.active}>
{lang.t('configure.streams')}
{lang.t('configure.stories')}
</Link>
<Link className={styles.navLink}
to="/admin/community"
@@ -74,8 +74,12 @@
background-color: transparent;
transition: background-color 200ms;
&:hover {
background-color: #232323;
}
&.active {
background-color: #232323;
background-color: #232323;
}
}
+11 -4
View File
@@ -14,28 +14,35 @@ const CoralHeader = ({handleLogout, restricted = false}) => (
<div>
<Navigation className={styles.nav}>
<IndexLink
id='dashboardNav'
className={styles.navLink}
to="/admin/dashboard"
activeClassName={styles.active}>
{lang.t('configure.dashboard')}
</IndexLink>
<Link
id='moderateNav'
className={styles.navLink}
to="/admin/moderate"
activeClassName={styles.active}>
{lang.t('configure.moderate')}
</Link>
<Link className={styles.navLink}
to="/admin/streams"
<Link
id='streamsNav'
className={styles.navLink}
to="/admin/stories"
activeClassName={styles.active}>
{lang.t('configure.streams')}
{lang.t('configure.stories')}
</Link>
<Link className={styles.navLink}
<Link
id='communityNav'
className={styles.navLink}
to="/admin/community"
activeClassName={styles.active}>
{lang.t('configure.community')}
</Link>
<Link
id='configureNav'
className={styles.navLink}
to="/admin/configure"
activeClassName={styles.active}>
+1
View File
@@ -11,6 +11,7 @@ export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
export const LOGIN_REQUEST = 'LOGIN_REQUEST';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_FAILURE = 'LOGIN_FAILURE';
export const LOGIN_MAXIMUM_EXCEEDED = 'LOGIN_MAXIMUM_EXCEEDED';
export const FETCH_FORGOT_PASSWORD_REQUEST = 'FETCH_FORGOT_PASSWORD_REQUEST';
export const FETCH_FORGOT_PASSWORD_SUCCESS = 'FETCH_FORGOT_PASSWORD_SUCCESS';
+2 -1
View File
@@ -1,6 +1,7 @@
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
export const USERS_MODERATION_QUEUE_FETCH_SUCCESS = 'USERS_MODERATION_QUEUE_FETCH_SUCCESS';
export const SHOW_SUSPENDUSER_DIALOG = 'SHOW_SUSPENDUSER_DIALOG';
export const HIDE_SUSPENDUSER_DIALOG = 'HIDE_SUSPENDUSER_DIALOG';
export const COMMENTS_MODERATION_QUEUE_FETCH_REQUEST = 'COMMENTS_MODERATION_QUEUE_FETCH_REQUEST';
export const COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS = 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS';
export const COMMENT_CREATE_SUCCESS = 'COMMENT_CREATE_SUCCESS';
@@ -5,3 +5,13 @@ export const SORT_UPDATE = 'SORT_UPDATE';
export const COMMENTERS_NEW_PAGE = 'COMMENTERS_NEW_PAGE';
export const SET_ROLE = 'SET_ROLE';
export const SET_COMMENTER_STATUS = 'SET_COMMENTER_STATUS';
export const FETCH_FLAGGED_COMMENTERS_REQUEST = 'FETCH_FLAGGED_COMMENTERS_REQUEST';
export const FETCH_FLAGGED_COMMENTERS_SUCCESS = 'FETCH_FLAGGED_COMMENTERS_SUCCESS';
export const FETCH_FLAGGED_COMMENTERS_FAILURE = 'FETCH_FLAGGED_COMMENTERS_FAILURE';
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
export const SHOW_SUSPENDUSER_DIALOG = 'SHOW_SUSPENDUSER_DIALOG';
export const HIDE_SUSPENDUSER_DIALOG = 'HIDE_SUSPENDUSER_DIALOG';
@@ -3,4 +3,3 @@ 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,3 +1,5 @@
@custom-media --big-viewport (min-width: 780px);
.container {
padding: 10px;
display: flex;
@@ -15,6 +17,12 @@
box-sizing: border-box;
}
.mainFlaggedContent {
width: 100%;
padding: 34px 14px;
box-sizing: border-box;
}
.roleButton {
display: block;
}
@@ -94,3 +102,232 @@
cursor: pointer;
}
}
.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: 18px;
width: 100%;
max-width: 660px;
min-width: 400px;
margin: 0 auto;
position: relative;
transition: all 200ms;
padding: 10px 0 0;
min-height: 220px;
.container {
display: block;
padding: 0 14px;
min-height: 180px;
}
&: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 {
font-size: 16px;
font-weight: bold;
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;
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;
}
.banButton {
width: 114px;
letter-spacing: 1px;
i {
vertical-align: middle;
margin-right: 10px;
font-size: 14px;
}
}
.actionButton {
transform: scale(.8);
margin: 0;
}
.flaggedByCount {
display: block;
text-align: left;
}
.flaggedBy {
display: inline;
padding: 3px;
font-size: 16px;
}
.flaggedByLabel {
font-weight: bold;
font-size: 14px;
}
.flaggedReasons {
padding-top: 15px;
margin-left: 24px;
}
.flaggedByReason {
font-size: 1tpx;
}
@@ -1,27 +1,47 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {compose} from 'react-apollo';
import {modUserFlaggedQuery} from 'coral-admin/src/graphql/queries';
import {banUser, setUserStatus, suspendUser} from 'coral-admin/src/graphql/mutations';
import {
fetchCommenters,
fetchAccounts,
updateSorting,
newPage,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog
} from '../../actions/community';
import Community from './Community';
import CommunityMenu from './components/CommunityMenu';
import BanUserDialog from './components/BanUserDialog';
import SuspendUserDialog from './components/SuspendUserDialog';
import People from './People';
import FlaggedAccounts from './FlaggedAccounts';
class CommunityContainer extends Component {
constructor(props) {
super(props);
this.state = {
searchValue: ''
searchValue: '',
timer: null
};
this.onKeyDownHandler = this.onKeyDownHandler.bind(this);
this.onChangeHandler = this.onChangeHandler.bind(this);
this.onSearchChange = this.onSearchChange.bind(this);
this.onHeaderClickHandler = this.onHeaderClickHandler.bind(this);
this.onNewPageHandler = this.onNewPageHandler.bind(this);
}
componentWillMount() {
this.props.fetchAccounts({});
}
onKeyDownHandler(e) {
if (e.key === 'Enter') {
e.preventDefault();
@@ -29,25 +49,29 @@ class CommunityContainer extends Component {
}
}
onChangeHandler(e) {
this.setState({
searchValue: e.target.value
onSearchChange(e) {
const value = e.target.value;
this.setState((prevState) => {
prevState.searchValue = value;
clearTimeout(prevState.timer);
const fetchAccounts = this.props.fetchAccounts;
prevState.timer = setTimeout(() => {
fetchAccounts({value});
}, 350);
return prevState;
});
}
search(query = {}) {
const {community} = this.props;
this.props.dispatch(fetchCommenters({
this.props.fetchAccounts({
value: this.state.searchValue,
field: community.field,
asc: community.asc,
field: community.fieldPeople,
asc: community.ascPeople,
...query
}));
}
});
componentDidMount() {
this.search();
}
onHeaderClickHandler(sort) {
@@ -60,19 +84,66 @@ class CommunityContainer extends Component {
this.search({page});
}
getTabContent(searchValue, props) {
const {community, data} = props;
const activeTab = props.route.path === ':id' ? 'flagged' : props.route.path;
if (activeTab === 'people') {
return (
<People
isFetching={community.isFetchingPeople}
commenters={community.accounts}
searchValue={searchValue}
onSearchChange={this.onSearchChange}
error={community.errorPeople}
totalPages={community.totalPagesPeople}
page={community.pagePeople}
onKeyDown={this.onKeyDownHandler}
onHeaderClickHandler={this.onHeaderClickHandler}
onNewPageHandler={this.onNewPageHandler}
/>
);
}
return (
<div>
<FlaggedAccounts
commenters={data.users}
isFetching={data.loading}
error={data.error}
showBanUserDialog={props.showBanUserDialog}
approveUser={props.approveUser}
suspendUser={props.suspendUser}
showSuspendUserDialog={props.showSuspendUserDialog}
/>
<BanUserDialog
open={community.banDialog}
user={community.user}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
/>
<SuspendUserDialog
open={community.suspendDialog}
handleClose={props.hideSuspendUserDialog}
user={community.user}
suspendUser={props.suspendUser}
/>
</div>
);
}
render() {
const {searchValue} = this.state;
const {community} = this.props;
const tab = this.getTabContent(searchValue, this.props);
return (
<Community
searchValue={searchValue}
commenters={community.commenters}
isFetching={community.isFetching}
error={community.error}
totalPages={community.totalPages}
page={community.page}
{...this}
/>
<div>
<CommunityMenu />
<div>
{ tab }
</div>
</div>
);
}
}
@@ -81,4 +152,18 @@ const mapStateToProps = state => ({
community: state.community.toJS()
});
export default connect(mapStateToProps)(CommunityContainer);
const mapDispatchToProps = dispatch => ({
fetchAccounts: query => dispatch(fetchAccounts(query)),
showBanUserDialog: (user) => dispatch(showBanUserDialog(user)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
showSuspendUserDialog: (user) => dispatch(showSuspendUserDialog(user)),
hideSuspendUserDialog: () => dispatch(hideSuspendUserDialog())
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
modUserFlaggedQuery,
banUser,
setUserStatus,
suspendUser
)(CommunityContainer);
@@ -0,0 +1,9 @@
import React from 'react';
const CommunityLayout = props => (
<div>
{props.children}
</div>
);
export default CommunityLayout;
@@ -0,0 +1,44 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
import styles from './Community.css';
import Loading from './Loading';
import EmptyCard from 'coral-admin/src/components/EmptyCard';
import User from './components/User';
const FlaggedAccounts = ({...props}) => {
const {commenters, isFetching} = props;
const hasResults = !isFetching && commenters && !!commenters.length;
// if (commenter.status === 'PENDING' && commenter.actions.length > 0) {
return (
<div className={styles.container}>
<div className={styles.mainFlaggedContent}>
{ isFetching && <Loading /> }
{
hasResults
? commenters.map((commenter, index) => {
return <User
user={commenter}
key={index}
index={index}
modActionButtons={['APPROVE', 'REJECT']}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
approveUser={props.approveUser}
suspendUser={props.suspendUser}
/>;
})
: <EmptyCard>{lang.t('community.no-flagged-accounts')}</EmptyCard>
}
</div>
</div>
);
};
export default FlaggedAccounts;
@@ -5,7 +5,7 @@ import translations from 'coral-admin/src/translations.json';
import styles from './Community.css';
import Table from './Table';
import Loading from './Loading';
import {Pager} from 'coral-ui';
import {Pager, Icon} from 'coral-ui';
import EmptyCard from '../../components/EmptyCard';
const lang = new I18n(translations);
@@ -29,28 +29,22 @@ const tableHeaders = [
}
];
const Community = ({isFetching, commenters, ...props}) => {
const People = ({isFetching, commenters, searchValue, onSearchChange, ...props}) => {
const hasResults = !isFetching && !!commenters.length;
return (
<div className={styles.container}>
<div className={styles.leftColumn}>
<form action="">
<div className={`${styles.searchBox}`}>
<label htmlFor="commenters-search">
<i className="material-icons">search</i>
</label>
<div className="">
<input
id="commenters-search"
className={`${styles.searchInput}`}
type="text"
value={props.searchValue}
onKeyDown={props.onKeyDownHandler}
onChange={props.onChangeHandler}
/>
</div>
</div>
</form>
<div className={styles.searchBox}>
<Icon name='search' className={styles.searchIcon}/>
<input
id="commenters-search"
type="text"
className={styles.searchBoxInput}
value={searchValue}
onChange={onSearchChange}
placeholder={lang.t('streams.search')}
/>
</div>
</div>
<div className={styles.mainContent}>
{ isFetching && <Loading /> }
@@ -58,7 +52,7 @@ const Community = ({isFetching, commenters, ...props}) => {
hasResults
? <Table
headers={tableHeaders}
data={commenters}
commenters={commenters}
onHeaderClickHandler={props.onHeaderClickHandler}
/>
: <EmptyCard>{lang.t('community.no-results')}</EmptyCard>
@@ -73,4 +67,4 @@ const Community = ({isFetching, commenters, ...props}) => {
);
};
export default Community;
export default People;
@@ -77,4 +77,4 @@ class Table extends Component {
}
}
export default connect(state => ({commenters: state.community.get('commenters')}))(Table);
export default connect(state => ({commenters: state.community.get('accounts')}))(Table);
@@ -0,0 +1,24 @@
import React from 'react';
import styles from '../Community.css';
import BanUserButton from '../../../components/BanUserButton';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../../../containers/ModerationQueue/helpers/moderationQueueActionsMap';
const ActionButton = ({type = '', user, ...props}) => {
if (type === 'BAN') {
return <BanUserButton icon='not interested' user={user} onClick={() => props.showBanUserDialog(user)} />;
}
return (
<Button
className={`${type.toLowerCase()} ${styles.actionButton}`}
cStyle={type.toLowerCase()}
icon={menuActionsMap[type].icon}
onClick={() => {
type === 'APPROVE' ? props.approveUser({userId: user.id}) : props.showSuspendUserDialog({user: user});
}}
>{menuActionsMap[type].text}</Button>
);
};
export default ActionButton;
@@ -0,0 +1,164 @@
.dialog {
border: none;
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
width: 500px;
top: 50%;
transform: translateY(-50%);
height: 184px;
padding: 20px;
h2 {
color: black;
font-size: 1.76em;
font-weight: 500;
margin: 0;
}
h3 {
color: black;
font-size: 1.4em;
font-weight: 500;
margin: 0;
}
}
.textField {
margin-top: 15px;
}
.textField label {
font-size: 1.08em;
font-weight: bold;
margin-bottom: 5px;
}
.textField input {
width: 100%;
display: block;
border: none;
outline: none;
border: 1px solid rgba(0,0,0,.12);
padding: 10px 6px;
box-sizing: border-box;
border-radius: 2px;
margin: 5px auto;
}
.footer {
margin: 20px auto 10px;
text-align: center;
}
.footer span {
display: block;
margin-bottom: 5px;
}
.footer a {
color: #2c69b6;
cursor: pointer;
margin: 0 5px;
}
.socialConnections {
margin-bottom: 20px;
}
.signInButton {
margin-top: 10px;
}
.close {
font-size: 20px;
line-height: 14px;
top: 10px;
right: 10px;
position: absolute;
display: block;
font-weight: bold;
color: #363636;
cursor: pointer;
}
.close:hover {
color: #6b6b6b;
}
input.error{
border: solid 2px #f44336;
}
.errorMsg, .hint {
color: grey;
font-weight: 600;
padding: 3px 0 16px;
}
.alert {
padding: 10px;
margin-bottom: 20px;
border-radius: 2px;
}
.alert--success {
border: solid 1px #1ec00e;
background: #cbf1b8;
color: #006900;
}
.alert--error {
background: #FFEBEE;
color: #B71C1C;
}
.userBox a {
color: #2c69b6;
cursor: pointer;
margin: 0px;
}
.attention {
display: inline-block;
width: 15px;
height: 15px;
background: #B71C1C;
color: #FFEBEE;
font-weight: bolder;
padding: 4px;
vertical-align: middle;
border-radius: 20px;
box-sizing: border-box;
font-size: 9px;
line-height: 7px;
text-align: center;
margin-right: 5px;
}
.action {
margin-top: 15px;
}
.passwordRequestSuccess {
border: 1px solid green;
background-color: lightgreen;
padding: 10px;
}
.passwordRequestFailure {
border: 1px solid orange;
background-color: 1px solid coral;
padding: 10px;
}
.cancel {
margin-right: 10px;
width: 47%;
}
.ban {
width: 47%;
}
.buttons {
margin: 20px 0;
}
@@ -0,0 +1,54 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={lang.t('community.ban_user')}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h2>{lang.t('community.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{lang.t('community.are_you_sure', user.username)}</h3>
<i>{lang.t('community.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{lang.t('community.cancel')}
</Button>
<Button
cStyle="black" className={styles.ban}
onClick={() => {
handleBanUser({userId: user.id}).then(() => {
handleClose();
});
}}
raised>
{lang.t('community.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
BanUserDialog.propTypes = {
handleBanUser: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
};
export default BanUserDialog;
@@ -0,0 +1,31 @@
import React from 'react';
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 CommunityMenu = () => {
const flaggedPath = '/admin/community/flagged';
const peoplePath = '/admin/community/people';
return (
<div className='mdl-tabs'>
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<div>
<Link to={flaggedPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('community.flaggedaccounts')}
</Link>
<Link to={peoplePath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('community.people')}
</Link>
</div>
</div>
</div>
);
};
export default CommunityMenu;
@@ -0,0 +1,115 @@
import React, {Component, PropTypes} from 'react';
import {Dialog, Button} from 'coral-ui';
import styles from './SuspendUserDialog.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
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 SuspendUserDialog extends Component {
state = {email: '', stage: 0}
static propTypes = {
stage: PropTypes.number,
handleClose: PropTypes.func.isRequired,
suspendUser: PropTypes.func.isRequired
}
componentDidMount() {
this.setState({email: lang.t('suspenduser.email'), about: lang.t('suspenduser.username')});
}
/*
* 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, user} = this.props;
const {stage} = this.state;
const cancel = this.props.handleClose;
const next = () => this.setState({stage: stage + 1});
const suspend = () => {
suspendUser({userId: user.user.id, message: this.state.email})
.then(() => {
this.props.handleClose();
});
};
const suspendModalActions = [
[ cancel, next ],
[ cancel, suspend ]
];
return suspendModalActions[stage][menuOption]();
}
onEmailChange = (e) => {
this.setState({email: e.target.value});
}
render () {
const {open, handleClose} = this.props;
const {stage} = this.state;
return <Dialog
className={styles.suspendDialog}
id="suspendUserDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={lang.t('suspenduser.title')}>
<div className={styles.title}>
{lang.t(stages[stage].title, lang.t('suspenduser.username'))}
</div>
<div className={styles.container}>
<div className={styles.description}>
{lang.t(stages[stage].description, lang.t('suspenduser.username'))}
</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], lang.t('suspenduser.username'))}
</Button>
))}
</div>
</div>
</Dialog>;
}
}
export default SuspendUserDialog;
@@ -0,0 +1,90 @@
import React from 'react';
import styles from '../Community.css';
import ActionButton from './ActionButton';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../../translations.json';
const lang = new I18n(translations);
// Render a single user for the list
const User = props => {
const {user, modActionButtons} = 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 ${props.selected ? 'mdl-shadow--8dp' : 'mdl-shadow--2dp'} ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.container}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span>
{user.username}
</span>
<ActionButton
className={styles.banButton}
type='BAN'
user={user}
showBanUserDialog={props.showBanUserDialog}
/>
</div>
</div>
<div className={styles.itemBody}>
<div className={styles.body}>
<div className={styles.flaggedByCount}>
<i className="material-icons">flag</i><span className={styles.flaggedByLabel}>{lang.t('community.flags')}({ user.actions.length })</span>:
{ user.action_summaries.map(
(action, i ) => {
return <span className={styles.flaggedBy} key={i}>
{lang.t(`community.${action.reason}`)} ({action.count})
</span>;
}
)}
</div>
<div className={styles.flaggedReasons}>
{ user.action_summaries.map(
(action_sum, i ) => {
return <div key={i}>
<span className={styles.flaggedByLabel}>
{lang.t(`community.${action_sum.reason}`)} ({action_sum.count})
</span>
{user.actions.map(
// find the action by action_sum.reason
(action, j) => {
if (action.reason === action_sum.reason) {
return <p className={styles.flaggedByReason} key={j}>
{action.user && action.user.username}: {action.message ? action.message : 'n/a'}
</p>;
}
return null;
}
)}
</div>;
}
)}
</div>
</div>
<div className={styles.sideActions}>
<div className={`actions ${styles.actions}`}>
{modActionButtons.map((action, i) =>
<ActionButton key={i}
type={action.toUpperCase()}
user={user}
approveUser={props.approveUser}
suspendUser={props.suspendUser}
showSuspendUserDialog={props.showSuspendUserDialog}
/>
)}
</div>
</div>
</div>
</div>
</li>;
};
export default User;
@@ -0,0 +1,339 @@
@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%;
display: flex;
justify-content: space-around;
width: 50%;
margin: 0;
}
.actionButton {
transform: scale(1.4);
}
}
}
.listItem {
border-bottom: 1px solid #e0e0e0;
font-size: 18px;
width: 100%;
max-width: 660px;
min-width: 400px;
margin: 0 auto;
position: relative;
transition: all 200ms;
padding: 10px 0 0;
min-height: 220px;
.container {
padding: 0 14px;
min-height: 180px;
}
&: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;
color: black;
max-width: 500px;
word-wrap: break-word;
font-weight: 300;
}
.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;
}
}
@@ -24,10 +24,30 @@
margin-bottom: 20px;
align-items: flex-start;
min-height: 100px;
max-width: 600px;
h3 {
margin: 0;
}
.actions {
display: inline-block;
width: 100%;
.copiedText {
display: inline-block;
color: #00796b;
padding: 12px;
font-size: 14px;
float: right;
}
.copyButton {
display: inline-block;
width: 200px;
float: right;
}
}
}
.settingsError {
@@ -41,7 +61,9 @@
.settingsHeader {
margin-top: 3px;
margin-bottom: 10px;
margin-bottom: 7px;
font-size: 18px;
font-weight: 500;
}
.disabledSettingText {
@@ -51,18 +73,12 @@
.configSettingInfoBox {
min-height: 100px;
margin-bottom: 20px;
cursor: pointer;
width: auto;
height: auto;
text-align: left;
overflow: visible;
}
.configSettingInfoBox p {
font-size: 12px;
bottom: 0;
}
.configSettingEmbed {
border: 1px solid #ccc;
border-radius: 4px;
@@ -73,6 +89,11 @@
.configTimeoutSelect {
display: inline-block;
margin-left: 20px;
i { /* fix for firefox and react-mdl-selectfield@0.2.0 */
padding: 20px 0;
vertical-align: top;
}
}
.charCountTexfield {
@@ -81,6 +102,8 @@
border-color: #ccc;
border-style: solid;
border-width: 0px 0px 1px 0px;
font-size: 14px;
text-align: center;
}
.charCountTexfieldEnabled {
@@ -96,29 +119,18 @@
color: white;
}
.copiedText {
color: #00796b;
float: right;
padding: 12px;
font-size: 14px;
}
.copyButton {
float: right;
width: 200px;
}
.embedInput {
border-radius: 3px;
border: 1px solid #ccc;
width: 100%;
display: block;
width: 90%;
vertical-align: middle;
margin-bottom: 10px;
color: #555;
padding: 14px;
outline: none;
border: 1px solid rgba(0,0,0,.12);
padding: 6px;
box-sizing: border-box;
border-radius: 2px;
margin: 5px auto;
min-height: 175px;
font-size: 14px;
letter-spacing: 0.03em;
resize: none;
}
#bannedWordlist, #suspectWordlist {
@@ -167,6 +179,28 @@
.content {
display: inline-block;
padding-left: 30px;
padding: 0px 30px;
box-sizing: border-box;
width: 100%;
}
}
.Configure {
p {
line-height: 1.2;
max-width: 550px;
}
.wrapper {
width: 100%;
}
.descriptionBox {
margin-top: 15px;
input {
height: 150px;
}
}
}
@@ -10,15 +10,19 @@ const lang = new I18n(translations);
const Domainlist = ({domains, onChangeDomainlist}) => {
return (
<Card id={styles.domainlist} className={styles.configSetting}>
<h3>{lang.t('configure.domain-list-title')}</h3>
<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)}
/>
<div className={styles.wrapper}>
<div className={styles.settingsHeader}>{lang.t('configure.domain-list-title')}</div>
<p className={styles.domainlistDesc}>{lang.t('configure.domain-list-text')}</p>
<div className={styles.wrapper}>
<TagsInput
value={domains}
inputProps={{placeholder: 'URL'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => onChangeDomainlist('whitelist', tags)}
/>
</div>
</div>
</Card>
);
};
@@ -45,13 +45,19 @@ class EmbedLink extends Component {
`.trim();
return (
<Card shadow="2" className={styles.configSetting}>
<h3>Embed Comment Stream</h3>
<p>{lang.t('configure.copy-and-paste')}</p>
<textarea rows={5} type='text' className={styles.embedInput} value={embedText} readOnly={true}/>
<Button raised className={styles.copyButton} onClick={this.copyToClipBoard} cStyle="black">
{lang.t('embedlink.copy')}
</Button>
<div className={styles.copiedText}>{this.state.copied && 'Copied!'}</div>
<div className={styles.wrapper}>
<div className={styles.settingsHeader}>Embed Comment Stream</div>
<p>{lang.t('configure.copy-and-paste')}</p>
<textarea rows={5} type='text' className={styles.embedInput} value={embedText} readOnly={true}/>
<div className={styles.actions}>
<Button raised className={styles.copyButton} onClick={this.copyToClipBoard} cStyle="black">
{lang.t('embedlink.copy')}
</Button>
<div className={styles.copiedText}>
{this.state.copied && 'Copied!'}
</div>
</div>
</div>
</Card>
);
}
@@ -16,6 +16,11 @@ const updateEmailConfirmation = (updateSettings, verify) => () => {
updateSettings({requireEmailConfirmation: !verify});
};
const updatePremodLinksEnable = (updateSettings, premodLinks) => () => {
const premodLinksEnable = !premodLinks;
updateSettings({premodLinksEnable});
};
const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
// just putting this here for shorthand below
@@ -23,7 +28,7 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
const off = styles.disabledSetting;
return (
<div>
<div className={styles.Configure}>
<Card className={`${styles.configSetting} ${settings.requireEmailConfirmation ? on : off}`}>
<div className={styles.action}>
<Checkbox
@@ -50,6 +55,19 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
</p>
</div>
</Card>
<Card className={`${styles.configSetting} ${settings.premodLinksEnable ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updatePremodLinksEnable(updateSettings, settings.premodLinksEnable)}
checked={settings.premodLinksEnable} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.enable-premod-links')}</div>
<p>
{lang.t('configure.enable-premod-links-text')}
</p>
</div>
</Card>
<Wordlist
bannedWords={settings.wordlist.banned}
suspectWords={settings.wordlist.suspect}
@@ -3,8 +3,9 @@ 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 {Textfield, Checkbox} from 'react-mdl';
import {Card, Icon} from 'coral-ui';
import {Checkbox, Textfield} from 'react-mdl';
import {Card, Icon, TextArea} from 'coral-ui';
import MarkdownEditor from 'coral-admin/src/components/MarkdownEditor';
const TIMESTAMPS = {
weeks: 60 * 60 * 24 * 7,
@@ -32,11 +33,15 @@ const updateInfoBoxEnable = (updateSettings, infoBox) => () => {
updateSettings({infoBoxEnable});
};
const updateInfoBoxContent = (updateSettings) => (event) => {
const infoBoxContent = event.target.value;
const updateInfoBoxContent = (updateSettings) => (value) => {
const infoBoxContent = value;
updateSettings({infoBoxContent});
};
const updateAutoClose = (updateSettings, autoCloseStream) => () => {
updateSettings({autoCloseStream});
};
const updateClosedMessage = (updateSettings) => (event) => {
const closedMessage = event.target.value;
updateSettings({closedMessage});
@@ -64,7 +69,7 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
const off = styles.disabledSetting;
return (
<div>
<div className={styles.Configure}>
<Card className={`${styles.configSetting} ${settings.charCountEnable ? on : off}`}>
<div className={styles.action}>
<Checkbox
@@ -79,7 +84,9 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
className={`${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
htmlFor='charCount'
onChange={updateCharCount(updateSettings, settingsError)}
value={settings.charCount}/>
value={settings.charCount}
disabled={settings.charCountEnable ? '' : 'disabled'}
/>
<span>{lang.t('configure.comment-count-text-post')}</span>
{
errors.charCount &&
@@ -92,41 +99,46 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
</p>
</div>
</Card>
<Card className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? on : off}`}>
<Card className={`${styles.configSetting} ${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>
<div className={styles.settingsHeader}>
{lang.t('configure.include-comment-stream')}
</div>
<p className={settings.infoBoxEnable ? '' : styles.disabledSettingText}>
{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>
<MarkdownEditor
className={styles.descriptionBox}
onChange={updateInfoBoxContent(updateSettings)}
value={settings.infoBoxContent}
/>
</div>
</div>
</Card>
<Card className={styles.configSettingInfoBox}>
<div className={styles.content}>
{lang.t('configure.closed-comments-desc')}
<Card className={`${styles.configSetting} ${styles.configSettingInfoBox}`}>
<div className={styles.wrapper}>
<div className={styles.settingsHeader}>{lang.t('configure.closed-stream-settings')}</div>
<p>{lang.t('configure.closed-comments-desc')}</p>
<div>
<Textfield
onChange={updateClosedMessage(updateSettings)}
value={settings.closedMessage}
label={lang.t('configure.closed-comments-label')}
rows={3}/>
<TextArea className={styles.descriptionBox}
onChange={updateClosedMessage(updateSettings)}
value={settings.closedMessage}
/>
</div>
</div>
</Card>
<Card className={styles.configSettingInfoBox}>
<Card className={`${styles.configSetting} ${styles.configSettingInfoBox}`}>
<div className={styles.action}>
<Checkbox
onChange={updateAutoClose(updateSettings, !settings.autoCloseStream)}
checked={settings.autoCloseStream} />
</div>
<div className={styles.content}>
{lang.t('configure.close-after')}
<br />
@@ -149,6 +161,7 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
</div>
</div>
</Card>
{/* the above card should be the last one if at all possible because of z-index issues with the selects */}
</div>
);
};
@@ -14,19 +14,20 @@ const updateCustomCssUrl = (updateSettings) => (event) => {
const TechSettings = ({settings, onChangeDomainlist, updateSettings}) => {
return (
<div>
<div className={styles.Configure}>
<Domainlist
domains={settings.domains.whitelist}
onChangeDomainlist={onChangeDomainlist} />
<EmbedLink />
<Card className={styles.configSetting}>
<h3>{lang.t('configure.custom-css-url')}</h3>
<p>{lang.t('configure.custom-css-url-desc')}</p>
<br />
<input
className={styles.customCSSInput}
value={settings.customCssUrl}
onChange={updateCustomCssUrl(updateSettings)} />
<div className={styles.wrapper}>
<div className={styles.settingsHeader}>{lang.t('configure.custom-css-url')}</div>
<p>{lang.t('configure.custom-css-url-desc')}</p>
<input
className={styles.customCSSInput}
value={settings.customCssUrl}
onChange={updateCustomCssUrl(updateSettings)} />
</div>
</Card>
</div>
);
@@ -8,25 +8,29 @@ import {Card} from 'coral-ui';
const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
<div>
<Card id={styles.bannedWordlist} className={styles.configSetting}>
<h3>{lang.t('configure.banned-words-title')}</h3>
<div className={styles.settingsHeader}>{lang.t('configure.banned-words-title')}</div>
<p className={styles.wordlistDesc}>{lang.t('configure.banned-word-text')}</p>
<TagsInput
value={bannedWords}
inputProps={{placeholder: 'word or phrase'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => onChangeWordlist('banned', tags)}
/>
<div className={styles.wrapper}>
<TagsInput
value={bannedWords}
inputProps={{placeholder: 'word or phrase'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => onChangeWordlist('banned', tags)}
/>
</div>
</Card>
<Card id={styles.suspectWordlist} className={styles.configSetting}>
<h3>{lang.t('configure.suspect-words-title')}</h3>
<div className={styles.settingsHeader}>{lang.t('configure.suspect-words-title')}</div>
<p className={styles.wordlistDesc}>{lang.t('configure.suspect-word-text')}</p>
<TagsInput
value={suspectWords}
inputProps={{placeholder: 'word or phrase'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => onChangeWordlist('suspect', tags)} />
<div className={styles.wrapper}>
<TagsInput
value={suspectWords}
inputProps={{placeholder: 'word or phrase'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => onChangeWordlist('suspect', tags)} />
</div>
</Card>
</div>
);
@@ -0,0 +1,49 @@
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './Widget.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
const lang = new I18n(translations);
const ActivityWidget = ({assets}) => {
return (
<div className={styles.widget}>
<h2 className={styles.heading}>Articles with the most conversations</h2>
<div className={styles.widgetHead}>
<p>{lang.t('streams.article')}</p>
<p>{lang.t('dashboard.comment_count')}</p>
</div>
<div className={styles.widgetTable}>
{
assets.length
? assets.map(asset => {
return (
<div className={styles.rowLinkify} key={asset.id}>
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
<p className={styles.widgetCount}>{asset.commentCount}</p>
<Link className={styles.linkToAsset} to={`${asset.url}#coralStreamEmbed_iframe`} target="_blank">
<p className={styles.assetTitle}>{asset.title}</p>
</Link>
<p className={styles.lede}>{asset.author} Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</div>
);
})
: <div className={styles.rowLinkify}>{lang.t('dashboard.no_activity')}</div>
}
</div>
</div>
);
};
ActivityWidget.propTypes = {
assets: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
url: PropTypes.string,
commentCount: PropTypes.number,
author: PropTypes.string,
created_at: PropTypes.string
})).isRequired
};
export default ActivityWidget;
@@ -7,3 +7,28 @@
font-size: 1.5rem;
font-weight: bold;
}
.autoUpdate {
background-color: #d5d5d5;
padding: 3px 10px 10px 10px;
margin-bottom: 0;
i {
position: relative;
top: 7px;
}
b {
float: right;
border-radius: 20px;
cursor: pointer;
background-color: #c0c0c0;
width: 30px;
height: 30px;
text-align: center;
top: 4px;
position: relative;
line-height: 1.7em;
font-size: 1.3em;
}
}
@@ -4,25 +4,33 @@ import {compose} from 'react-apollo';
import {connect} from 'react-redux';
import {getMetrics} from 'coral-admin/src/graphql/queries';
import FlagWidget from './FlagWidget';
import LikeWidget from './LikeWidget';
import ActivityWidget from './ActivityWidget';
import CountdownTimer from 'coral-admin/src/components/CountdownTimer';
import {showBanUserDialog, hideBanUserDialog} from 'coral-admin/src/actions/moderation';
import {Spinner} from 'coral-ui';
class Dashboard extends React.Component {
reloadData = () => {
this.props.data.refetch();
}
render () {
if (this.props.data && this.props.data.loading) {
return <Spinner />;
}
const {data: {assetsByLike, assetsByFlag}} = this.props;
const {data: {assetsByActivity, assetsByFlag}} = this.props;
return (
<div className={styles.Dashboard}>
<FlagWidget assets={assetsByFlag} />
<LikeWidget assets={assetsByLike} />
<div>
<CountdownTimer handleTimeout={this.reloadData} />
<div className={styles.Dashboard}>
<FlagWidget assets={assetsByFlag} />
<ActivityWidget assets={assetsByActivity} />
</div>
</div>
);
}
@@ -1,4 +1,4 @@
import React from 'react';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './Widget.css';
import I18n from 'coral-framework/modules/i18n/i18n';
@@ -6,50 +6,50 @@ import translations from 'coral-admin/src/translations';
const lang = new I18n(translations);
const FlagWidget = (props) => {
const {assets} = props;
const FlagWidget = ({assets}) => {
return (
<div className={styles.widget}>
<h2 className={styles.heading}>Articles with the most flags</h2>
<table className={styles.widgetTable}>
<thead className={styles.widgetHead}>
<tr>
<th>{lang.t('streams.article')}</th>
<th>{lang.t('dashboard.flags')}</th>
</tr>
</thead>
<tbody>
{
assets.length
? assets.map(asset => {
const flagSummary = asset.action_summaries.find(s => s.type === 'FlagAssetActionSummary');
return (
<tr className={styles.rowLinkify} key={asset.id}>
<td>
<Link className={styles.linkToAsset} to={`/admin/moderate/flagged/${asset.id}`}>
<p className={styles.assetTitle}>{asset.title}</p>
<p className={styles.lede}>{asset.author} Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</Link>
</td>
<td>
<Link className={styles.linkToAsset} to={`/admin/moderate/flagged/${asset.id}`}>
<p className={styles.widgetCount}>{flagSummary ? flagSummary.actionCount : 0}</p>
</Link>
</td>
</tr>
);
})
: <tr className={styles.rowLinkify}><td colSpan="2">{lang.t('dashboard.no_flags')}</td></tr>
}
{ /* rows in a table with a fixed height will expand and ignore height.
this extra row will expand to fill the extra space. */
assets.length < 10 ? <tr></tr> : null
}
</tbody>
</table>
<h2 className={styles.heading}>{lang.t('dashboard.most_flags')}</h2>
<div className={styles.widgetHead}>
<p>{lang.t('streams.article')}</p>
<p>{lang.t('dashboard.flags')}</p>
</div>
<div className={styles.widgetTable}>
{
assets.length
? assets.map(asset => {
let flagSummary = null;
if (asset.action_summaries) {
flagSummary = asset.action_summaries.find(s => s.type === 'FlagAssetActionSummary');
}
return (
<div className={styles.rowLinkify} key={asset.id}>
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
<p className={styles.widgetCount}>{flagSummary ? flagSummary.actionCount : 0}</p>
<Link className={styles.linkToAsset} to={`${asset.url}#coralStreamEmbed_iframe`} target="_blank">
<p className={styles.assetTitle}>{asset.title}</p>
</Link>
<p className={styles.lede}>{asset.author} Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</div>
);
})
: <div className={styles.rowLinkify}>{lang.t('dashboard.no_flags')}</div>
}
</div>
</div>
);
};
FlagWidget.propTypes = {
assets: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
url: PropTypes.string,
action_summaries: PropTypes.array,
author: PropTypes.string,
created_at: PropTypes.string
})).isRequired
};
export default FlagWidget;
@@ -1,4 +1,4 @@
import React from 'react';
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './Widget.css';
import I18n from 'coral-framework/modules/i18n/i18n';
@@ -6,51 +6,46 @@ import translations from 'coral-admin/src/translations';
const lang = new I18n(translations);
const LikeWidget = (props) => {
const {assets} = props;
const LikeWidget = ({assets}) => {
return (
<div className={styles.widget}>
<h2 className={styles.heading}>Articles with the most likes</h2>
<table className={styles.widgetTable}>
<thead className={styles.widgetHead}>
<tr>
<th>{lang.t('streams.article')}</th>
<th>{lang.t('modqueue.likes')}</th>
</tr>
</thead>
<tbody>
{
assets.length
? assets.map(asset => {
const likeSummary = asset.action_summaries.find(s => s.type === 'LikeAssetActionSummary');
return (
<tr className={styles.rowLinkify} key={asset.id}>
<td>
<Link className={styles.linkToAsset} to={`/admin/moderate/flagged/${asset.id}`}>
<p className={styles.assetTitle}>{asset.title}</p>
<p className={styles.lede}>{asset.author} Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</Link>
</td>
<td>
<Link className={styles.linkToAsset} to={`/admin/moderate/flagged/${asset.id}`}>
<p className={styles.widgetCount}>{likeSummary ? likeSummary.actionCount : 0}</p>
</Link>
</td>
</tr>
);
})
: <tr className={styles.rowLinkify}><td colSpan="2">{lang.t('dashboard.no_likes')}</td></tr>
}
{ /* rows in a table with a fixed height will expand and ignore height.
this extra row will expand to fill the extra space. */
assets.length < 10 ? <tr></tr> : null
}
</tbody>
</table>
<div className={styles.widgetHead}>
<p>{lang.t('streams.article')}</p>
<p>{lang.t('modqueue.likes')}</p>
</div>
<div className={styles.widgetTable}>
{
assets.length
? assets.map(asset => {
const likeSummary = asset.action_summaries.find(s => s.type === 'LikeAssetActionSummary');
return (
<div className={styles.rowLinkify} key={asset.id}>
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
<p className={styles.widgetCount}>{likeSummary ? likeSummary.actionCount : 0}</p>
<Link className={styles.linkToAsset} to={`${asset.url}#coralStreamEmbed_iframe`} target="_blank">
<p className={styles.assetTitle}>{asset.title}</p>
</Link>
<p className={styles.lede}>{asset.author} Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</div>
);
})
: <div className={styles.rowLinkify}>{lang.t('dashboard.no_likes')}</div>
}
</div>
</div>
);
};
LikeWidget.propTypes = {
assets: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
url: PropTypes.string,
action_summaries: PropTypes.array,
author: PropTypes.string,
created_at: PropTypes.string
})).isRequired
};
export default LikeWidget;
@@ -1,14 +1,17 @@
:root {
--row-height: 80px;
--row-height: 60px;
}
.widget {
box-sizing: border-box;
margin: 10px 5px 5px 5px;
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.2);
padding: 15px;
flex: 1;
background-color: white;
box-sizing: border-box;
}
.widget * {
box-sizing: border-box;
}
.heading {
@@ -19,21 +22,28 @@
}
.widgetTable {
width: 100%;
border-collapse: collapse;
user-select: none;
height: calc(var(--row-height) * 10);
}
.widgetTable thead th {
.widgetTable + div:after {
content: '';
clear: both;
display: block;
}
.widgetHead p {
color: rgb(35, 102, 223);
padding: 10px;
text-align: left;
text-transform: capitalize;
display: inline-block;
box-sizing: border-box;
margin-bottom: 0;
}
.widgetTable thead th:last-child {
width: 10%;
.widgetHead p:last-child {
float: right;
margin-right: 100px;
}
.rowLinkify {
@@ -41,6 +51,7 @@
border-bottom: 1px solid lightgrey;
color: #555;
height: var(--row-height);
padding: 10px;
}
.rowLinkify:last-child {
@@ -51,13 +62,22 @@
background-color: #f8f8f8;
}
.widgetTable tbody td {
padding: 10px;
.linkToAsset {
display: inline-block;
text-decoration: none;
}
.linkToAsset {
display: block;
.linkToModerate {
background-color: #e0e0e0;
padding: 10px 14px;
text-decoration: none;
color: black;
float: right;
margin-left: 15px;
}
.linkToModerate:hover {
background-color: #ccc;
}
.lede {
@@ -81,4 +101,6 @@
color: #555;
font-size: 1.3em;
font-weight: 400;
float: right;
margin-top: 7px;
}
@@ -2,13 +2,16 @@ import React, {Component} from 'react';
import {connect} from 'react-redux';
import Layout from '../components/ui/Layout';
import {checkLogin, handleLogin, logout, requestPasswordReset} from '../actions/auth';
import {fetchConfig} from '../actions/config';
import {FullLoading} from '../components/FullLoading';
import AdminLogin from '../components/AdminLogin';
class LayoutContainer extends Component {
componentWillMount () {
const {checkLogin} = this.props;
const {checkLogin, fetchConfig} = this.props;
checkLogin();
fetchConfig();
}
render () {
const {
@@ -16,15 +19,19 @@ class LayoutContainer extends Component {
loggedIn,
loadingUser,
loginError,
loginMaxExceeded,
passwordRequestSuccess
} = this.props.auth;
const {handleLogout} = this.props;
const {handleLogout, TALK_RECAPTCHA_PUBLIC} = this.props;
if (loadingUser) { return <FullLoading />; }
if (!isAdmin) {
return <AdminLogin
loginMaxExceeded={loginMaxExceeded}
handleLogin={this.props.handleLogin}
requestPasswordReset={this.props.requestPasswordReset}
passwordRequestSuccess={passwordRequestSuccess}
recaptchaPublic={TALK_RECAPTCHA_PUBLIC}
errorMessage={loginError} />;
}
if (isAdmin && loggedIn) { return <Layout handleLogout={handleLogout} {...this.props} />; }
@@ -33,12 +40,14 @@ class LayoutContainer extends Component {
}
const mapStateToProps = state => ({
auth: state.auth.toJS()
auth: state.auth.toJS(),
TALK_RECAPTCHA_PUBLIC: state.config.get('data').get('TALK_RECAPTCHA_PUBLIC', null)
});
const mapDispatchToProps = dispatch => ({
checkLogin: () => dispatch(checkLogin()),
handleLogin: (username, password) => dispatch(handleLogin(username, password)),
fetchConfig: () => dispatch(fetchConfig()),
handleLogin: (username, password, recaptchaResponse) => dispatch(handleLogin(username, password, recaptchaResponse)),
requestPasswordReset: email => dispatch(requestPasswordReset(email)),
handleLogout: () => dispatch(logout())
});
@@ -3,6 +3,7 @@ import {connect} from 'react-redux';
import {compose} from 'react-apollo';
import key from 'keymaster';
import isEqual from 'lodash/isEqual';
import styles from './components/styles.css';
import {modQueueQuery} from '../../graphql/queries';
import {banUser, setCommentStatus} from '../../graphql/mutations';
@@ -17,22 +18,88 @@ import ModerationQueue from './ModerationQueue';
import ModerationMenu from './components/ModerationMenu';
import ModerationHeader from './components/ModerationHeader';
import NotFoundAsset from './components/NotFoundAsset';
import ModerationKeysModal from '../../components/ModerationKeysModal';
class ModerationContainer extends Component {
state = {
selectedIndex: 0,
sort: 'REVERSE_CHRONOLOGICAL'
}
componentWillMount() {
const {toggleModal, singleView} = this.props;
this.props.fetchSettings();
key('s', () => singleView());
key('shift+/', () => toggleModal(true));
key('esc', () => toggleModal(false));
key('j', this.select(true));
key('k', this.select(false));
key('r', this.moderate(false));
key('t', this.moderate(true));
}
moderate = (accept) => () => {
const {acceptComment, rejectComment} = this.props;
const {selectedIndex} = this.state;
const comments = this.getComments();
const commentId = {commentId: comments[selectedIndex].id};
if (accept) {
acceptComment(commentId);
} else {
rejectComment(commentId);
}
}
getComments = () => {
const {data, route} = this.props;
const activeTab = route.path === ':id' ? 'premod' : route.path;
return data[activeTab];
}
select = (next) => () => {
if (next) {
this.setState(prevState =>
({
...prevState,
selectedIndex: prevState.selectedIndex < this.getComments().length - 1
? prevState.selectedIndex + 1 : prevState.selectedIndex
}));
} else {
this.setState(prevState =>
({
...prevState,
selectedIndex: prevState.selectedIndex > 0 ?
prevState.selectedIndex - 1 : prevState.selectedIndex
}));
}
}
selectSort = (sort) => {
this.setState({sort});
this.props.modQueueResort(sort);
}
componentWillUnmount() {
key.unbind('s');
key.unbind('shift+/');
key.unbind('esc');
key.unbind('j');
key.unbind('k');
key.unbind('r');
key.unbind('t');
}
componentDidUpdate(_, prevState) {
// If paging through using keybaord shortcuts, scroll the page to keep the selected
// comment in view.
if (prevState.selectedIndex !== this.state.selectedIndex) {
// the 'smooth' flag only works in FF as of March 2017
document.querySelector(`.${styles.selected}`).scrollIntoView({behavior: 'smooth'});
}
}
componentWillReceiveProps(nextProps) {
@@ -43,13 +110,13 @@ class ModerationContainer extends Component {
}
render () {
const {data, moderation, settings, assets, modQueueResort, ...props} = this.props;
const {data, moderation, settings, assets, onClose, ...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) {
if (!('premodCount' in data)) {
return <div><Spinner/></div>;
}
@@ -66,6 +133,18 @@ class ModerationContainer extends Component {
}
const comments = data[activeTab];
let activeTabCount;
switch(activeTab) {
case 'premod':
activeTabCount = data.premodCount;
break;
case 'flagged':
activeTabCount = data.flaggedCount;
break;
case 'rejected':
activeTabCount = data.rejectedCount;
break;
}
return (
<div>
@@ -75,23 +154,37 @@ class ModerationContainer extends Component {
premodCount={data.premodCount}
rejectedCount={data.rejectedCount}
flaggedCount={data.flaggedCount}
modQueueResort={modQueueResort}
selectSort={this.selectSort}
sort={this.state.sort}
/>
<ModerationQueue
currentAsset={asset}
comments={comments}
activeTab={activeTab}
singleView={moderation.singleView}
selectedIndex={this.state.selectedIndex}
bannedWords={settings.wordlist.banned}
suspectWords={settings.wordlist.suspect}
showBanUserDialog={props.showBanUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
loadMore={props.loadMore}
assetId={providedAssetId}
sort={this.state.sort}
commentCount={activeTabCount}
/>
<BanUserDialog
open={moderation.banDialog}
user={moderation.user}
commentId={moderation.commentId}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
showRejectedNote={moderation.showRejectedNote}
rejectComment={props.rejectComment}
/>
<ModerationKeysModal
open={moderation.modalOpen}
onClose={onClose}/>
</div>
);
}
@@ -109,7 +202,7 @@ const mapDispatchToProps = dispatch => ({
singleView: () => dispatch(singleView()),
updateAssets: assets => dispatch(updateAssets(assets)),
fetchSettings: () => dispatch(fetchSettings()),
showBanUserDialog: (user, commentId) => dispatch(showBanUserDialog(user, commentId)),
showBanUserDialog: (user, commentId, showRejectedNote) => dispatch(showBanUserDialog(user, commentId, showRejectedNote)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
});
@@ -1,15 +1,17 @@
import React, {PropTypes} from 'react';
import Comment from './components/Comment';
import styles from './components/styles.css';
import EmptyCard from '../../components/EmptyCard';
import {actionsMap} from './helpers/moderationQueueActionsMap';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
import LoadMore from './components/LoadMore';
const lang = new I18n(translations);
const ModerationQueue = ({comments, ...props}) => {
const ModerationQueue = ({comments, selectedIndex, commentCount, singleView, loadMore, activeTab, sort, ...props}) => {
return (
<div id="moderationList">
<div id="moderationList" className={`${styles.list} ${singleView ? styles.singleView : ''}`}>
<ul style={{paddingLeft: 0}}>
{
comments.length
@@ -19,8 +21,10 @@ const ModerationQueue = ({comments, ...props}) => {
key={i}
index={i}
comment={comment}
commentType={props.activeTab}
commentType={activeTab}
selected={i === selectedIndex}
suspectWords={props.suspectWords}
bannedWords={props.bannedWords}
actions={actionsMap[status]}
showBanUserDialog={props.showBanUserDialog}
acceptComment={props.acceptComment}
@@ -31,11 +35,20 @@ const ModerationQueue = ({comments, ...props}) => {
: <EmptyCard>{lang.t('modqueue.emptyqueue')}</EmptyCard>
}
</ul>
<LoadMore
comments={comments}
loadMore={loadMore}
sort={sort}
tab={activeTab}
showLoadMore={comments.length < commentCount}
assetId={props.assetId}
/>
</div>
);
};
ModerationQueue.propTypes = {
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
showBanUserDialog: PropTypes.func.isRequired,
@@ -10,6 +10,7 @@ import FlagBox from './FlagBox';
import CommentType from './CommentType';
import ActionButton from 'coral-admin/src/components/ActionButton';
import BanUserButton from 'coral-admin/src/components/BanUserButton';
import {getActionSummary} from 'coral-framework/utils';
const linkify = new Linkify();
@@ -17,56 +18,65 @@ 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;
const Comment = ({actions = [], comment, ...props}) => {
const links = linkify.getMatches(comment.body);
const linkText = links ? links.map(link => link.raw) : [];
const flagActionSummaries = getActionSummary('FlagActionSummary', comment);
const flagActions = comment.actions && comment.actions.filter(a => a.__typename === 'FlagAction');
return (
<li tabIndex={props.index}
className={`mdl-card mdl-shadow--2dp ${styles.Comment} ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<li tabIndex={props.index} className={`mdl-card ${props.selected ? 'mdl-shadow--8dp' : 'mdl-shadow--2dp'} ${styles.Comment} ${styles.listItem}`}>
<div className={styles.container}>
<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('-', '_'))}
<div className={styles.author}>
<span>
{comment.user.name}
</span>
<BanUserButton user={props.comment.user} onClick={() => props.showBanUserDialog(props.comment.user, props.comment.id)} />
<CommentType type={props.commentType} />
</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})}
/>
)}
<span className={styles.created}>
{timeago().format(comment.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}
</span>
<BanUserButton user={comment.user} onClick={() => props.showBanUserDialog(comment.user, comment.id, comment.status !== 'REJECTED')} />
<CommentType type={props.commentType} />
</div>
{props.comment.user.status === 'banned' ?
{comment.user.status === 'banned' ?
<span className={styles.banned}>
<Icon name='error_outline'/>
<Icon name='error_outline'/>
{lang.t('comment.banned_user')}
</span>
</span>
: null}
</div>
</div>
{!props.currentAsset && (
<div className={styles.moderateArticle}>
Story: {props.comment.asset.title} <Link to={`/admin/moderate/${props.comment.asset.id}`}>Moderate &rarr;</Link>
</div>
)}
<div className={styles.moderateArticle}>
Story: {comment.asset.title}
{!props.currentAsset && (
<Link to={`/admin/moderate/${comment.asset.id}`}>Moderate </Link>
)}
</div>
<div className={styles.itemBody}>
<p className={styles.body}>
<Linkify component='span' properties={{style: linkStyles}}>
<Highlighter searchWords={props.suspectWords} textToHighlight={props.comment.body}/>
</Linkify>
<Highlighter
searchWords={[...props.suspectWords, ...props.bannedWords, ...linkText]}
textToHighlight={comment.body} />
</p>
<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={comment.user}
acceptComment={() => props.acceptComment({commentId: comment.id})}
rejectComment={() => props.rejectComment({commentId: comment.id})}
/>
)}
</div>
</div>
</div>
</div>
{actionSummaries && <FlagBox actionSummaries={actionSummaries} />}
{
flagActions && flagActions.length
? <FlagBox actions={flagActions} actionSummaries={flagActionSummaries} />
: null
}
</li>
);
};
@@ -75,10 +85,12 @@ Comment.propTypes = {
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
comment: PropTypes.shape({
body: PropTypes.string.isRequired,
action_summaries: PropTypes.array,
actions: PropTypes.array,
created_at: PropTypes.string.isRequired,
user: PropTypes.shape({
status: PropTypes.string
@@ -90,9 +102,4 @@ Comment.propTypes = {
})
};
const linkStyles = {
backgroundColor: 'rgb(255, 219, 135)',
padding: '1px 2px'
};
export default Comment;
@@ -4,12 +4,12 @@
top: 11px;
color: white;
background: grey;
padding: 2px 13px;
font-size: 14px;
height: 32px;
box-sizing: border-box;
line-height: 29px;
padding-left: 26px;
padding: 2px 8px 2px 26px;
border-radius: 2px;
font-size: 12px;
i {
font-size: 14px;
@@ -53,3 +53,17 @@
font-size: 12px;
}
}
.lessDetail {
display: inline-block;
margin-right: 10px;
}
.subDetail {
font-weight: normal;
color: #888;
span {
color: black;
}
}
@@ -1,6 +1,17 @@
import React, {Component, PropTypes} from 'react';
import {Icon} from 'coral-ui';
import styles from './FlagBox.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
const shortReasons = {
'This comment is offensive': lang.t('modqueue.offensive'),
'This looks like an ad/marketing': lang.t('modqueue.spam/ads'),
'This user is impersonating': lang.t('modqueue.impersonating'),
'I don\'t like this username': lang.t('modqueue.dont-like-username'),
'Other': lang.t('modqueue.other')
};
class FlagBox extends Component {
constructor () {
@@ -16,27 +27,50 @@ class FlagBox extends Component {
}));
}
reasonMap = (reason) => {
const shortReason = shortReasons[reason];
// if the short reason isn't found, just return the long one.
return shortReason ? shortReason : reason;
}
render() {
const {props} = this;
const {actionSummaries, actions} = this.props;
const {showDetail} = this.state;
return (
<div className={styles.flagBox}>
<div className={styles.container}>
<div className={styles.header}>
<Icon name='flag'/><h3>Flags ({props.actionSummaries.length}):</h3>
<Icon name='flag'/><h3>Flags ({actionSummaries.length}):</h3>
<ul>
{props.actionSummaries.map((action, i) =>
<li key={i}>{!action.reason ? <i>No reason provided</i> : action.reason} (<strong>{action.count}</strong>)</li>
{actionSummaries.map((action, i) =>
<li key={i} className={styles.lessDetail}>{this.reasonMap(action.reason)} (<strong>{action.count}</strong>)</li>
)}
</ul>
{/* <a onClick={this.toggleDetail} className={styles.moreDetail}>More detail</a>*/}
<a onClick={this.toggleDetail} className={styles.moreDetail}>{showDetail ? lang.t('modqueue.less-detail') : lang.t('modqueue.more-detail')}</a>
</div>
{this.state.showDetail && (<div className={styles.detail}>
<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>)}
{showDetail && (
<div className={styles.detail}>
<ul>
{actionSummaries.map((summary, i) => {
const actionList = actions.filter(a => a.reason === summary.reason);
return (
<li key={i}>
{this.reasonMap(summary.reason)} (<strong>{summary.count}</strong>)
<ul>
{
actionList.map((action, j) => <li key={`${i}_${j}`} className={styles.subDetail}><span>{action.user.username}</span> {action.message}</li>)
}
</ul>
</li>
);
})}
</ul>
</div>
)}
</div>
</div>
);
@@ -44,7 +78,14 @@ class FlagBox extends Component {
}
FlagBox.propTypes = {
actionSummaries: PropTypes.array.isRequired
actionSummaries: PropTypes.arrayOf(PropTypes.shape({
reason: PropTypes.string,
count: PropTypes.number
})).isRequired,
actions: PropTypes.arrayOf(PropTypes.shape({
message: PropTypes.string,
user: PropTypes.shape({username: PropTypes.string})
})).isRequired
};
export default FlagBox;
@@ -0,0 +1,31 @@
import React, {PropTypes} from 'react';
import {Button} from 'coral-ui';
import styles from './styles.css';
const LoadMore = ({comments, loadMore, sort, tab, assetId, showLoadMore}) =>
<div className={styles.loadMoreContainer}>
{
showLoadMore && <Button
className={styles.loadMore}
onClick={() =>
loadMore({
cursor: comments[comments.length - 1].created_at,
sort,
tab,
asset_id: assetId
})}>
Load More
</Button>
}
</div>;
LoadMore.propTypes = {
comments: PropTypes.array.isRequired,
loadMore: PropTypes.func.isRequired,
sort: PropTypes.oneOf(['CHRONOLOGICAL', 'REVERSE_CHRONOLOGICAL']).isRequired,
tab: PropTypes.oneOf(['rejected', 'premod', 'flagged']).isRequired,
assetId: PropTypes.string,
showLoadMore: PropTypes.bool.isRequired
};
export default LoadMore;
@@ -10,17 +10,17 @@ const ModerationHeader = props => (
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 className="mdl-tabs__tab" href={props.asset.url}>
<span>{props.asset.title}</span>
<Icon className={styles.settingsButton} name="open_in_new"/>
</a>
<Link className="mdl-tabs__tab" to="/admin/streams">Select Stream</Link>
<Link className="mdl-tabs__tab" to="/admin/stories">Select Stream</Link>
</div>
:
<div className={`mdl-tabs__tab-bar ${styles.moderateAsset}`}>
<a className="mdl-tabs__tab" />
<a className="mdl-tabs__tab">All Streams</a>
<Link className="mdl-tabs__tab" to="/admin/streams">Select Stream</Link>
<Link className="mdl-tabs__tab" to="/admin/stories">Select Stream</Link>
</div>
}
</div>
@@ -1,4 +1,4 @@
import React, {PropTypes, Component} from 'react';
import React, {PropTypes} from 'react';
import CommentCount from './CommentCount';
import styles from './styles.css';
import {SelectField, Option} from 'react-mdl-selectfield';
@@ -8,57 +8,63 @@ import {Link} from 'react-router';
const lang = new I18n(translations);
class ModerationMenu extends Component {
state = {
sort: 'REVERSE_CHRONOLOGICAL',
}
const ModerationMenu = (
{asset, premodCount, rejectedCount, flaggedCount, selectSort, sort}
) => {
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';
static propTypes = {
premodCount: PropTypes.number.isRequired,
rejectedCount: PropTypes.number.isRequired,
flaggedCount: PropTypes.number.isRequired,
asset: PropTypes.shape({
id: PropTypes.string
})
}
selectSort = (sort) => {
this.setState({sort});
this.props.modQueueResort(sort);
}
render() {
const {asset, premodCount, rejectedCount, flaggedCount} = this.props;
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 className={styles.tabBarPadding}/>
<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>
<SelectField
className={styles.selectField}
label='Sort'
value={this.state.sort}
onChange={sort => this.selectSort(sort)}>
<Option value={'REVERSE_CHRONOLOGICAL'}>Newest First</Option>
<Option value={'CHRONOLOGICAL'}>Oldest First</Option>
</SelectField>
return (
<div className="mdl-tabs">
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<div className={styles.tabBarPadding} />
<div>
<Link
to={premodPath}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.premod')} <CommentCount count={premodCount} />
</Link>
<Link
to={flagPath}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.flagged')} <CommentCount count={flaggedCount} />
</Link>
<Link
to={rejectPath}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.rejected')} <CommentCount count={rejectedCount} />
</Link>
</div>
<SelectField
className={styles.selectField}
label="Sort"
value={sort}
onChange={sort => selectSort(sort)}>
<Option value={'REVERSE_CHRONOLOGICAL'}>Newest First</Option>
<Option value={'CHRONOLOGICAL'}>Oldest First</Option>
</SelectField>
</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;
@@ -6,7 +6,7 @@ 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>
<Link className={styles.goToStreams} to="/admin/stories">Go to Streams</Link>
</p>
</div>
);
@@ -84,11 +84,10 @@ span {
margin-bottom: -1px;
.settingsButton {
i {
vertical-align: middle;
margin-left: 10px;
margin-top: -4px;
}
vertical-align: middle;
margin-left: 10px;
margin-top: -4px;
font-size: 16px;
}
.moderateAsset {
@@ -114,7 +113,15 @@ span {
}
&:nth-child(2) {
text-align: center;
span {
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-width: 344px;
display: inline-block;
vertical-align: top;
}
}
&:last-child {
@@ -136,7 +143,7 @@ span {
display: none;
}
&.singleView .listItem.activeItem {
&.singleView .listItem.selected {
display: block;
height: 100%;
font-size: 1.5em;
@@ -147,7 +154,6 @@ span {
position: fixed;
bottom: 60px;
left: 25%;
margin: 0 auto;
display: flex;
justify-content: space-around;
width: 50%;
@@ -164,18 +170,17 @@ span {
border-bottom: 1px solid #e0e0e0;
font-size: 18px;
width: 100%;
max-width: 660px;
max-width: 700px;
min-width: 400px;
margin: 0 auto;
position: relative;
transition: box-shadow 200ms;
margin-top: 0;
padding: 4px 0 0;
transition: all 200ms;
padding: 10px 0 0;
min-height: 220px;
.container {
padding: 0 14px;
min-height: 220px;
min-height: 180px;
}
&:hover {
@@ -186,6 +191,11 @@ span {
border-bottom: none;
}
&.selected {
max-width: 720px;
max-height: 410px;
}
.context {
a {
color: #f36451;
@@ -195,11 +205,8 @@ span {
}
.sideActions {
position: absolute;
right: 0;
height: 100%;
top: 0;
padding: 100px 12px 65px;
box-sizing: border-box;
}
@@ -209,17 +216,21 @@ span {
justify-content: space-between;
.author {
font-weight: 300;
min-width: 230px;
display: flex;
align-items: center;
color: #262626;
font-size: 16px;
}
}
.itemBody {
display: flex;
justify-content: space-between;
margin-top: 15px;
font-size: 16px;
font-size: 14px;
line-height: 1.5;
font-weight: 300;
}
.avatar {
@@ -233,9 +244,11 @@ span {
}
.created {
color: #666;
font-size: 13px;
color: #262626;
font-size: 14px;
margin-left: 15px;
line-height: 1px;
font-weight: 300;
}
.actionButton {
@@ -246,10 +259,11 @@ span {
.body {
margin-top: 0px;
flex: 1;
font-size: 0.88em;
color: black;
max-width: 500px;
word-wrap: break-word;
font-weight: 300;
font-size: 16px;
}
.flagged {
@@ -319,9 +333,12 @@ span {
.Comment {
.moderateArticle {
font-size: 15px;
margin-top: 14px;
font-size: 14px;
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
max-width: 500px;
a {
display: inline-block;
color: #063b9a;
@@ -381,3 +398,23 @@ span {
cursor: pointer;
}
}
.loadMoreContainer {
display: flex;
justify-content: center;
width: 100%;
};
.loadMore {
width: 100%;
text-align: center;
color: #FFF;
max-width: 660px;
margin-bottom: 30px;
background-color: #2376D8;
cursor: pointer;
}
.loadMore:hover {
background-color: #4399FF;
}
@@ -0,0 +1,192 @@
import React, {Component} from 'react';
import styles from './Stories.css';
import {connect} from 'react-redux';
import I18n from 'coral-framework/modules/i18n/i18n';
import {fetchAssets, updateAssetState} from 'coral-admin/src/actions/assets';
import translations from 'coral-admin/src/translations.json';
import {Link} from 'react-router';
import {Pager, Icon} from 'coral-ui';
import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl';
import EmptyCard from 'coral-admin/src/components/EmptyCard';
import sortBy from 'lodash/sortBy';
const limit = 25;
class Stories extends Component {
state = {
search: '',
sort: 'desc',
filter: 'all',
statusMenus: {},
timer: null,
page: 0
}
componentDidMount () {
this.props.fetchAssets(0, limit, '', this.state.sortBy);
}
onSettingChange = (setting) => (e) => {
let options = this.state;
this.setState({[setting]: e.target.value});
options[setting] = e.target.value;
this.props.fetchAssets(0, limit, options.search, options.sort, options.filter);
}
onSearchChange = (e) => {
const search = e.target.value;
this.setState((prevState) => {
prevState.search = search;
clearTimeout(prevState.timer);
const fetchAssets = this.props.fetchAssets;
prevState.timer = setTimeout(() => {
fetchAssets(0, limit, search, this.state.sort, this.state.filter);
}, 350);
return prevState;
});
}
renderDate = (date) => {
const d = new Date(date);
return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
}
onStatusClick = (closeStream, id, statusMenuOpen) => () => {
if (statusMenuOpen) {
this.setState(prev => {
prev.statusMenus[id] = false;
return prev;
});
this.props.updateAssetState(id, closeStream ? Date.now() : null)
.then(() => {
const {search, sort, filter, page} = this.state;
this.props.fetchAssets(page, limit, search, sort, filter);
});
} else {
this.setState(prev => {
prev.statusMenus[id] = true;
return prev;
});
}
}
renderTitle = (title, {id}) => <Link to={`/admin/moderate/${id}`}>{title}</Link>
renderStatus = (closedAt, {id}) => {
const closed = closedAt && new Date(closedAt).getTime() < Date.now();
const statusMenuOpen = this.state.statusMenus[id];
return <div className={styles.statusMenu}>
<div
className={closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(closed, id, statusMenuOpen)}>
{!statusMenuOpen && <Icon className={styles.statusMenuIcon} name='keyboard_arrow_down'/>}
{closed ? lang.t('streams.closed') : lang.t('streams.open')}
</div>
{
statusMenuOpen &&
<div
className={!closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(!closed, id, statusMenuOpen)}>
{!closed ? lang.t('streams.closed') : lang.t('streams.open')}
</div>
}
</div>;
}
onPageClick = (page) => {
this.setState({page});
const {search, sort, filter} = this.state;
this.props.fetchAssets((page - 1) * limit, limit, search, sort, filter);
}
render () {
const {search, sort, filter} = this.state;
const {assets} = this.props;
const assetsIds = sortBy(assets.ids.map((id) => assets.byId[id]), 'publication_date');
if (this.state.sort === 'desc') {
assetsIds.reverse();
}
return (
<div className={styles.container}>
<div className={styles.leftColumn}>
<div className={styles.searchBox}>
<Icon name='search' className={styles.searchIcon}/>
<input
type='text'
value={search}
className={styles.searchBoxInput}
onChange={this.onSearchChange}
placeholder={lang.t('streams.search')}/>
</div>
<div className={styles.optionHeader}>{lang.t('streams.filter-streams')}</div>
<div className={styles.optionDetail}>{lang.t('streams.stream-status')}</div>
<RadioGroup
name='status filter'
value={filter}
childContainer='div'
onChange={this.onSettingChange('filter')}
className={styles.radioGroup}
>
<Radio value='all'>{lang.t('streams.all')}</Radio>
<Radio value='open'>{lang.t('streams.open')}</Radio>
<Radio value='closed'>{lang.t('streams.closed')}</Radio>
</RadioGroup>
<div className={styles.optionHeader}>{lang.t('streams.sort-by')}</div>
<RadioGroup
name='sort by'
value={sort}
childContainer='div'
onChange={this.onSettingChange('sort')}
className={styles.radioGroup}
>
<Radio value='desc'>{lang.t('streams.newest')}</Radio>
<Radio value='asc'>{lang.t('streams.oldest')}</Radio>
</RadioGroup>
</div>
{
assetsIds.length
? <div className={styles.mainContent}>
<DataTable className={styles.streamsTable} rows={assetsIds} onClick={this.goToModeration}>
<TableHeader name="title" cellFormatter={this.renderTitle}>{lang.t('streams.article')}</TableHeader>
<TableHeader name="publication_date" cellFormatter={this.renderDate}>
{lang.t('streams.pubdate')}
</TableHeader>
<TableHeader name="closedAt" cellFormatter={this.renderStatus} className={styles.status}>
{lang.t('streams.status')}
</TableHeader>
</DataTable>
<Pager
totalPages={Math.ceil((assets.count || 0) / limit)}
page={this.state.page}
onNewPageHandler={this.onPageClick} />
</div>
: <EmptyCard>{lang.t('streams.empty_result')}</EmptyCard>
}
</div>
);
}
}
const mapStateToProps = ({assets}) => {
return {
assets: assets.toJS()
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchAssets: (...args) => {
dispatch(fetchAssets.apply(this, args));
},
updateAssetState: (...args) => dispatch(updateAssetState.apply(this, args))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Stories);
const lang = new I18n(translations);
@@ -1,5 +1,5 @@
import React, {Component} from 'react';
import styles from './Streams.css';
import styles from './Stories.css';
import {connect} from 'react-redux';
import I18n from 'coral-framework/modules/i18n/i18n';
import {fetchAssets, updateAssetState} from '../../actions/assets';
@@ -12,7 +12,7 @@ import EmptyCard from 'coral-admin/src/components/EmptyCard';
const limit = 25;
class Streams extends Component {
class Stories extends Component {
state = {
search: '',
@@ -80,8 +80,8 @@ class Streams extends Component {
<div
className={closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(closed, id, statusMenuOpen)}>
{closed ? lang.t('streams.closed') : lang.t('streams.open')}
{!statusMenuOpen && <Icon className={styles.statusMenuIcon} name='keyboard_arrow_down'/>}
{closed ? lang.t('streams.closed') : lang.t('streams.open')}
</div>
{
statusMenuOpen &&
@@ -182,6 +182,6 @@ const mapDispatchToProps = (dispatch) => {
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Streams);
export default connect(mapStateToProps, mapDispatchToProps)(Stories);
const lang = new I18n(translations);
@@ -4,6 +4,7 @@ fragment metrics on Asset {
url
author
created_at
commentCount
action_summaries {
type: __typename
actionCount
@@ -12,4 +12,19 @@ fragment commentView on Comment {
id
title
}
action_summaries {
count
... on FlagActionSummary {
reason
}
}
actions {
... on FlagAction {
reason
message
user {
username
}
}
}
}
@@ -1,6 +1,7 @@
import {graphql} from 'react-apollo';
import SET_USER_STATUS from './setUserStatus.graphql';
import SET_COMMENT_STATUS from './setCommentStatus.graphql';
import SUSPEND_USER from './suspendUser.graphql';
export const banUser = graphql(SET_USER_STATUS, {
props: ({mutate}) => ({
@@ -9,11 +10,40 @@ export const banUser = graphql(SET_USER_STATUS, {
variables: {
userId,
status: 'BANNED'
}
},
refetchQueries: ['Users']
});
}}),
});
export const setUserStatus = graphql(SET_USER_STATUS, {
props: ({mutate}) => ({
approveUser: ({userId}) => {
return mutate({
variables: {
userId,
status: 'APPROVED'
},
refetchQueries: ['Users']
});
}
})
});
export const suspendUser = graphql(SUSPEND_USER, {
props: ({mutate}) => ({
suspendUser: ({userId, message}) => {
return mutate({
variables: {
userId,
message
},
refetchQueries: ['Users']
});
}
})
});
export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
props: ({mutate}) => ({
acceptComment: ({commentId}) => {
@@ -22,7 +52,26 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
commentId,
status: 'ACCEPTED'
},
refetchQueries: ['ModQueue']
updateQueries: {
ModQueue: (oldData) => {
const premod = oldData.premod.filter(c => c.id !== commentId);
const flagged = oldData.flagged.filter(c => c.id !== commentId);
const rejected = oldData.rejected.filter(c => c.id !== commentId);
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
const rejectedCount = rejected.length < oldData.rejected.length ? oldData.rejectedCount - 1 : oldData.rejectedCount;
return {
...oldData,
premodCount,
flaggedCount,
rejectedCount,
premod,
flagged,
rejected,
};
}
}
});
},
rejectComment: ({commentId}) => {
@@ -31,7 +80,27 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
commentId,
status: 'REJECTED'
},
refetchQueries: ['ModQueue']
updateQueries: {
ModQueue: (oldData) => {
const comment = oldData.premod.concat(oldData.flagged).filter(c => c.id === commentId)[0];
const rejected = [comment].concat(oldData.rejected);
const premod = oldData.premod.filter(c => c.id !== commentId);
const flagged = oldData.flagged.filter(c => c.id !== commentId);
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
const rejectedCount = oldData.rejectedCount + 1;
return {
...oldData,
premodCount,
flaggedCount,
rejectedCount,
premod,
flagged,
rejected
};
}
}
});
}
})
@@ -0,0 +1,7 @@
mutation suspendUser($userId: ID!, $message: String) {
suspendUser(id: $userId, message: $message) {
errors {
translation_key
}
}
}
@@ -1,6 +1,8 @@
import {graphql} from 'react-apollo';
import MOD_QUEUE_QUERY from './modQueueQuery.graphql';
import MOD_QUEUE_LOAD_MORE from './loadMore.graphql';
import MOD_USER_FLAGGED_QUERY from './modUserFlaggedQuery.graphql';
import METRICS from './metricsQuery.graphql';
export const modQueueQuery = graphql(MOD_QUEUE_QUERY, {
@@ -14,7 +16,8 @@ export const modQueueQuery = graphql(MOD_QUEUE_QUERY, {
},
props: ({ownProps: {params: {id = null}}, data}) => ({
data,
modQueueResort: modQueueResort(id, data.fetchMore)
modQueueResort: modQueueResort(id, data.fetchMore),
loadMore: loadMore(data.fetchMore)
})
});
@@ -30,6 +33,50 @@ export const getMetrics = graphql(METRICS, {
}
});
export const loadMore = (fetchMore) => ({limit, cursor, sort, tab, asset_id}) => {
let statuses;
switch(tab) {
case 'premod':
statuses = ['PREMOD'];
break;
case 'flagged':
statuses = ['NONE', 'PREMOD'];
break;
case 'rejected':
statuses = ['REJECTED'];
break;
}
return fetchMore({
query: MOD_QUEUE_LOAD_MORE,
variables: {
limit,
cursor,
sort,
statuses,
asset_id
},
updateQuery: (oldData, {fetchMoreResult:{comments}}) => {
return {
...oldData,
[tab]: [
...oldData[tab],
...comments
]
};
}
});
};
export const modUserFlaggedQuery = graphql(MOD_USER_FLAGGED_QUERY, {
options: ({params: {action_type = 'FLAG'}}) => {
return {
variables: {
action_type: action_type
}
};
}
});
export const modQueueResort = (id, fetchMore) => (sort) => {
return fetchMore({
query: MOD_QUEUE_QUERY,
@@ -0,0 +1,13 @@
#import "../fragments/commentView.graphql"
query LoadMoreModQueue($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!]) {
comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort}) {
...commentView
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
}
@@ -4,7 +4,7 @@ query Metrics ($from: Date!, $to: Date!) {
assetsByFlag: assetMetrics(from: $from, to: $to, sort: FLAG) {
...metrics
}
assetsByLike: assetMetrics(from: $from, to: $to, sort: LIKE) {
assetsByActivity: assetMetrics(from: $from, to: $to, sort: ACTIVITY) {
...metrics
}
}
@@ -15,12 +15,6 @@ query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
sort: $sort
}) {
...commentView
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
rejected: comments(query: {
statuses: [REJECTED],
@@ -0,0 +1,26 @@
query Users ($action_type: ACTION_TYPE) {
users (query:{action_type: $action_type}){
id
username
status
roles
actions{
id
created_at
... on FlagAction {
reason
message
user {
id
username
}
}
}
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
}
-1
View File
@@ -7,7 +7,6 @@ import store from './services/store';
import App from './components/App';
import 'react-mdl/extra/material.css';
import 'react-mdl/extra/material.js';
render(
+9 -4
View File
@@ -23,8 +23,13 @@ export default function assets (state = initialState, action) {
}
const replaceAssets = (action, state) => {
const assets = fromJS(action.assets.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {}));
return state.set('byId', assets)
.set('count', action.count)
.set('ids', List(assets.keys()));
const assets = fromJS(action.assets.reduce((prev, curr) => {
prev[curr.id] = curr;
return prev;
}, {}));
return state
.set('byId', assets)
.set('count', action.count)
.set('ids', List(assets.keys()));
};
+7
View File
@@ -6,6 +6,7 @@ const initialState = Map({
user: null,
isAdmin: false,
loginError: null,
loginMaxExceeded: false,
passwordRequestSuccess: null
});
@@ -29,12 +30,18 @@ export default function auth (state = initialState, action) {
return initialState;
case actions.LOGIN_REQUEST:
return state.set('loginError', null);
case actions.LOGIN_SUCCESS:
return state.set('loginMaxExceeded', false).set('loginError', null);
case actions.LOGIN_FAILURE:
return state.set('loginError', action.message);
case actions.FETCH_FORGOT_PASSWORD_REQUEST:
return state.set('passwordRequestSuccess', null);
case actions.FETCH_FORGOT_PASSWORD_SUCCESS:
return state.set('passwordRequestSuccess', 'If you have a registered account, a password reset link was sent to that email.');
case actions.LOGIN_MAXIMUM_EXCEEDED:
return state
.set('loginMaxExceeded', true)
.set('loginError', action.message);
default :
return state;
}
+51 -21
View File
@@ -6,58 +6,88 @@ import {
FETCH_COMMENTERS_SUCCESS,
SORT_UPDATE,
SET_ROLE,
SET_COMMENTER_STATUS
SET_COMMENTER_STATUS,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG,
SHOW_SUSPENDUSER_DIALOG,
HIDE_SUSPENDUSER_DIALOG
} from '../constants/community';
const initialState = Map({
community: Map(),
isFetching: false,
error: '',
commenters: [],
field: 'created_at',
asc: false,
totalPages: 0,
page: 0
isFetchingPeople: false,
errorPeople: '',
accounts: [],
fieldPeople: 'created_at',
ascPeople: false,
totalPagesPeople: 0,
pagePeople: 0,
user: Map({}),
banDialog: false,
suspendDialog: false
});
export default function community (state = initialState, action) {
switch (action.type) {
case FETCH_COMMENTERS_REQUEST :
return state
.set('isFetching', true);
.set('isFetchingPeople', true);
case FETCH_COMMENTERS_FAILURE :
return state
.set('isFetching', false)
.set('error', action.error);
.set('isFetchingPeople', false)
.set('errorPeople', action.error);
case FETCH_COMMENTERS_SUCCESS : {
const {commenters, type, ...rest} = action; // eslint-disable-line
const {accounts, type, page, count, limit, totalPages, ...rest} = action; // eslint-disable-line
return state
.merge({
isFetching: false,
error: '',
isFetchingPeople: false,
errorPeople: '',
pagePeople: page,
countPeople: count,
limitPeople: limit,
totalPagesPeople: totalPages,
...rest
})
.set('commenters', commenters); // Sets to normal array
.set('accounts', accounts); // Sets to normal array
}
case SET_ROLE : {
const commenters = state.get('commenters');
const commenters = state.get('accounts');
const idx = commenters.findIndex(el => el.id === action.id);
commenters[idx].roles[0] = action.role;
return state.set('commenters', commenters.map(id => id));
return state.set('accounts', commenters.map(id => id));
}
case SET_COMMENTER_STATUS: {
const commenters = state.get('commenters');
const commenters = state.get('accounts');
const idx = commenters.findIndex(el => el.id === action.id);
commenters[idx].status = action.status;
return state.set('commenters', commenters.map(id => id));
return state.set('accounts', commenters.map(id => id));
}
case SORT_UPDATE :
return state
.set('field', action.sort.field)
.set('asc', !state.get('asc'));
.set('fieldPeople', action.sort.field)
.set('ascPeople', !state.get('ascPeople'));
case HIDE_BANUSER_DIALOG:
return state
.set('banDialog', false);
case SHOW_BANUSER_DIALOG:
return state
.merge({
user: Map(action.user),
banDialog: true
});
case HIDE_SUSPENDUSER_DIALOG:
return state
.set('suspendDialog', false);
case SHOW_SUSPENDUSER_DIALOG:
return state
.merge({
user: Map(action.user),
suspendDialog: true
});
default :
return state;
}
+16
View File
@@ -0,0 +1,16 @@
import {Map} from 'immutable';
import * as actions from '../actions/config';
const initialState = Map({
data: Map({})
});
export default function config (state = initialState, action) {
switch (action.type) {
case actions.CONFIG_UPDATED:
return state.set('data', Map(action.data));
default:
return state;
}
}
+3 -1
View File
@@ -4,6 +4,7 @@ import settings from './settings';
import community from './community';
import moderation from './moderation';
import install from './install';
import config from './config';
export default {
auth,
@@ -11,5 +12,6 @@ export default {
settings,
community,
moderation,
install
install,
config
};
@@ -19,6 +19,7 @@ export default function moderation (state = initialState, action) {
.merge({
user: Map(action.user),
commentId: action.commentId,
showRejectedNote: action.showRejectedNote,
banDialog: true
});
case actions.SET_ACTIVE_TAB:
@@ -1,7 +1,9 @@
import ApolloClient, {addTypename} from 'apollo-client';
import getNetworkInterface from './transport';
import fragmentMatcher from './fragmentMatcher';
export const client = new ApolloClient({
fragmentMatcher,
addTypename: true,
queryTransformer: addTypename,
dataIdFromObject: (result) => {

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