Compare commits

...
689 Commits
Author SHA1 Message Date
Kim Gardner a1f200de37 Merge pull request #436 from coralproject/translations
Translations
2017-03-24 16:46:21 -04: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
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
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 6b6d3d8bbb highlight suspect, banned words and links 2017-03-23 16:09:36 -06: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
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
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
Wyatt Johnson 2c98aa2097 Merge pull request #368 from coralproject/asset-name-fixes
Asset Name + Profile Tab Bugs
2017-03-01 16:46:41 -07:00
Wyatt Johnson 23540cb2c2 Merge branch 'master' into asset-name-fixes 2017-03-01 16:43:25 -07:00
Wyatt Johnson 5114b64935 Merge pull request #367 from coralproject/move-config-around
removing dead code
2017-03-01 16:43:16 -07:00
Wyatt Johnson e08d737a69 Merge branch 'master' into asset-name-fixes 2017-03-01 16:42:37 -07:00
Wyatt Johnson 57fc4b8a3f Fixed test 2017-03-01 16:40:47 -07:00
Kim Gardner ca635a34cc Merge branch 'master' into move-config-around 2017-03-01 18:39:09 -05:00
Kim Gardner 4cb796e54f Merge pull request #366 from coralproject/reply-no-reply
make sure reply box works
2017-03-01 18:38:49 -05:00
Wyatt Johnson cefe872bf0 Fixed queries 2017-03-01 16:38:27 -07:00
Riley Davis 5aabf2b177 Merge branch 'master' into move-config-around 2017-03-01 16:36:31 -07:00
Riley Davis a839596c20 Merge branch 'master' into reply-no-reply 2017-03-01 16:34:42 -07:00
Riley Davis ed0df4b738 make sure reply box works 2017-03-01 16:32:01 -07:00
Kim Gardner 36b707f213 Merge pull request #365 from coralproject/config-wordz
add some missing translations
2017-03-01 18:08:55 -05:00
Riley Davis fa5a94c4be add some missing translations 2017-03-01 16:00:42 -07:00
Wyatt Johnson 472b087a6c Merge pull request #364 from coralproject/embed-stream-fixes
Touched up embed + stream query
2017-03-01 15:54:46 -07:00
Wyatt Johnson 3d13380fcc Touched up embed + stream query 2017-03-01 15:49:39 -07:00
Kim Gardner eeb82ff7bb Merge pull request #363 from coralproject/move-config-around
re-organize settings so they make a bit more sense
2017-03-01 17:30:56 -05:00
riley c4edbd4db4 removing dead code 2017-03-01 15:28:44 -07:00
Riley Davis 0c89dd6d98 sam's feedback 2017-03-01 15:23:17 -07:00
gaba acc9b2d9bf Approve and Reject buttons. Still misssing suspending 2017-03-01 14:21:49 -08:00
Riley Davis 97496f8b3d Merge branch 'master' into move-config-around 2017-03-01 14:44:27 -07:00
Riley Davis 62793f5947 keepin' it DRY 2017-03-01 14:40:10 -07:00
Kim Gardner 9b9715f39f Merge pull request #360 from coralproject/bugfix-suspect-reporting
Comments with suspect words should not show up as "Reported" by the user who posted them.
2017-03-01 16:10:18 -05:00
Kim Gardner fb68bc3d48 Merge branch 'master' into bugfix-suspect-reporting 2017-03-01 16:06:08 -05:00
Wyatt Johnson 4461f47428 Merge pull request #361 from coralproject/yarn-docker
Yarn fixes for Docker
2017-03-01 14:03:20 -07:00
gaba f5ef33d24e Ban Button with graphql mutation. 2017-03-01 12:57:12 -08:00
Wyatt Johnson 9fb0f2f241 Merge branch 'master' into yarn-docker 2017-03-01 13:55:04 -07:00
Wyatt Johnson fb96d432ee Moved action creation upstream 2017-03-01 13:51:32 -07:00
Wyatt Johnson 49477ead6a Fixed temp issue 2017-03-01 13:47:08 -07:00
Kim Gardner 09eb2edb32 Merge branch 'master' into bugfix-suspect-reporting 2017-03-01 15:44:51 -05:00
Kim Gardner 516a05eca0 Merge pull request #359 from coralproject/bugfix-premod-optimistic
Preventing premod comments from being posted optimistically.
2017-03-01 15:44:34 -05:00
Kim Gardner 58e803223f Merge branch 'master' into bugfix-suspect-reporting 2017-03-01 15:41:22 -05:00
Kim Gardner 285430e1ce Merge branch 'master' into bugfix-premod-optimistic 2017-03-01 15:40:52 -05:00
Kim Gardner fdb051c810 Merge pull request #354 from coralproject/admin-login-upgrade
Admin login upgrade - RIP Communicorn part deux
2017-03-01 15:39:03 -05:00
Kim Gardner 43d2b29921 Merge branch 'master' into admin-login-upgrade 2017-03-01 15:35:01 -05:00
David Jay 9cb835b08b Setting action user_id to null on suspect words. 2017-03-01 14:49:42 -05:00
David Jay 0085d2bb04 Preventing premod comments from being posted optimistically. 2017-03-01 14:21:03 -05:00
Wyatt Johnson 2f05cec380 Removed yarn install (already has yarn!) 2017-03-01 11:50:32 -07:00
Wyatt Johnson eb8b733ac7 Merge branch 'master' into 138617379_best_comments 2017-03-01 11:46:04 -07:00
Wyatt Johnson f87525d0a4 Merge pull request #358 from coralproject/version-pin
Pinning to 7.6 until upstream issues resolved
2017-03-01 11:43:30 -07:00
Kim Gardner e56a04abf1 Merge branch 'master' into admin-login-upgrade 2017-03-01 13:42:56 -05:00
Riley Davis 4e5bcbc27f even more design updates 2017-03-01 11:42:03 -07:00
Wyatt Johnson af8c926296 Pinning to 7.6 until upstream issues resolved, see #357 2017-03-01 11:37:29 -07:00
Kim Gardner 4b39587837 Merge pull request #356 from coralproject/make-links-less-linky
Make links less linky
2017-03-01 13:31:44 -05:00
Kim Gardner 70c69e3752 Merge branch 'master' into make-links-less-linky 2017-03-01 13:27:13 -05:00
riley b346083d11 Merged master into admin-login-upgrade 2017-03-01 11:19:48 -07:00
Kim Gardner 23917944b2 Merge pull request #353 from coralproject/bugfix-user-state
Refetching on login and logout.
2017-03-01 13:14:26 -05:00
Riley Davis 00b2bf37ee fix height of the table 2017-03-01 11:14:13 -07:00
David Jay 3b04dab0d5 Merge branch 'master' into bugfix-user-state 2017-03-01 12:57:40 -05:00
Wyatt Johnson 45b14d026d Added more conditional checks to exclusion 2017-03-01 10:49:47 -07:00
Riley Davis bbf70aedab put it back to 5 minutes 2017-03-01 10:47:31 -07:00
Riley Davis 045fa3e2a9 also ignore this 2017-03-01 10:42:01 -07:00
Riley Davis 58870e1630 Merged branch master into admin-login-upgrade 2017-03-01 10:40:51 -07:00
Riley Davis 04ab00d053 ignore this 2017-03-01 10:40:40 -07:00
Riley Davis 1f70a2d3eb Merged branch master into make-links-less-linky 2017-03-01 10:37:50 -07:00
Riley Davis 674585adeb design updates 2017-03-01 10:37:39 -07:00
Wyatt Johnson 69deba245a Merge branch 'master' into 138617379_best_comments 2017-03-01 10:32:57 -07:00
Wyatt Johnson 74c457a0b0 Merge pull request #349 from coralproject/production-mode
Routes Production mode
2017-03-01 09:58:53 -07:00
Wyatt Johnson 79296999c0 Merge branch 'master' into production-mode 2017-03-01 09:52:46 -07:00
Wyatt Johnson a1e2077ef1 Merge branch 'master' into 138617379_best_comments 2017-03-01 09:43:44 -07:00
Kim Gardner a984fc9beb Merge pull request #355 from coralproject/comment-component
UX Comment Component
2017-03-01 11:03:54 -05:00
Belen Curcio 6cd0c12ba4 tweaks 2017-03-01 12:35:59 -03:00
Belen Curcio 545410b27e renaming duplicate statement 2017-03-01 11:50:05 -03:00
Belen Curcio 453894be80 Merge 2017-03-01 11:46:28 -03:00
Belen Curcio ea0602b7a5 Design Comment Component 2017-03-01 11:42:08 -03:00
Belen Curcio c33ae1f7f8 Merge branch 'master' of github.com:coralproject/talk into comment-component 2017-03-01 11:08:46 -03:00
Kim Gardner 87dddaf5b3 Merge branch 'master' into admin-login-upgrade 2017-03-01 09:01:15 -05:00
Kim Gardner 0af08fcaea Merge pull request #352 from coralproject/fix-permalink
Fix permalink
2017-03-01 09:00:59 -05:00
Kim Gardner 945e576ce7 Merge branch 'master' into fix-permalink 2017-03-01 08:57:17 -05: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
Belen Curcio 42b380b942 Flag box 2017-02-28 22:35:37 -03:00
Belen Curcio 339aefa72a Merge branch 'master' of github.com:coralproject/talk into comment-component 2017-02-28 21:26:57 -03:00
riley 7ccf77b1ad add reset password link 2017-02-28 17:10:51 -07:00
Belén Curcio cc9de0b02c Merge pull request #348 from coralproject/whizard-whitelist
Permitted Domains for Talk Install
2017-02-28 21:06:16 -03:00
Belen Curcio dc81ec1e80 Merge branch 'whizard-whitelist' of github.com:coralproject/talk into whizard-whitelist 2017-02-28 20:44:55 -03:00
Belen Curcio 1db0a75010 Merge 2017-02-28 20:42:03 -03:00
Riley Davis 1f3eb5f516 Merged branch master into admin-login-upgrade 2017-02-28 16:31:49 -07:00
riley 8f23ca678d add Alert component to coral-ui 2017-02-28 16:31:30 -07:00
David Jay 230488117f Merge branch 'master' into bugfix-user-state 2017-02-28 18:20:00 -05:00
David Jay d8117494d1 Cleaning up after merge conflict. 2017-02-28 18:18:17 -05:00
Wyatt Johnson e6b7098a6c Merge branch 'master' into production-mode 2017-02-28 16:10:27 -07:00
David Jay e68358a211 Merge branch 'master' into fix-permalink 2017-02-28 18:07:31 -05:00
Kim Gardner 7bd9411e73 Merge pull request #332 from coralproject/assets-by-like
Dashboard: Articles with most likes, articles with most flags
2017-02-28 16:57:47 -05:00
Riley Davis b369fb9101 re-rename 2017-02-28 14:38:19 -07:00
Riley Davis bb1aea63ba add a couple of routes 2017-02-28 14:33:57 -07:00
Riley Davis e2273f5810 change PermissionRequired to LoginView and create a login form. delete old login page 2017-02-28 14:32:01 -07:00
David Jay fed28b3eec Refetching on login and logout. 2017-02-28 16:13:51 -05:00
David Jay 037f5be377 Merge branch 'master' of github.com:coralproject/talk into fix-permalink 2017-02-28 14:28:32 -05:00
David Jay 62fe63d323 Scrolling to permalinked comment or reply. 2017-02-28 14:27:53 -05:00
David Jay 158f31d603 Highlighting permalinked comment. 2017-02-28 13:55:28 -05:00
Riley Davis c6885c8f1c Merge branch 'master' into assets-by-like 2017-02-28 11:16:26 -07:00
Riley Davis 86a6b545cc create a fragment for asset metrics. add some comments 2017-02-28 11:15:44 -07:00
Gabriela Rodríguez Berón 1db68a12a8 Merge branch 'master' into whizard-whitelist 2017-02-28 09:54:39 -08:00
Riley Davis 65e8e42ae6 remove query from Dashboard component per belen's advice 2017-02-28 10:51:14 -07:00
Kim Gardner b3b10efbba Use tú to match other translations 2017-02-28 12:35:20 -05:00
Kim Gardner 3bfde67d58 Copy updates with translations 2017-02-28 12:32:51 -05:00
Kim Gardner cdbc065c90 Merge pull request #351 from coralproject/mod-queue-sort
Fixing order bug with mod queue sorting.
2017-02-28 12:22:41 -05:00
David Jay b9297b6079 Merge branch 'master' into mod-queue-sort 2017-02-28 12:11:05 -05:00
David Jay 95a9259c06 Fixing bug with mod queue sorting. 2017-02-28 12:06:27 -05:00
David Jay bf23fc2974 Moving comment id into query. 2017-02-28 12:02:51 -05:00
Riley Davis ac3e6a12bd Merge branch 'master' into assets-by-like 2017-02-28 09:27:46 -07: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
Belen Curcio 061efd4734 Comment Component Design 2017-02-28 09:03:19 -03: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
Wyatt Johnson 87d64e575b Merge branch 'master' into whizard-whitelist 2017-02-27 15:52:16 -07:00
Wyatt Johnson aa88eb2374 Adjusted build process 2017-02-27 15:33:52 -07:00
Wyatt Johnson 6cd3c84853 Moved route definitions into env wrapped check, webpackified embed 2017-02-27 14:21:48 -07:00
David Jay 303148e5ba Merge pull request #345 from coralproject/bugfix-second-new-comments
Prevents "0" from appearing on empty comment streams
2017-02-27 15:34:40 -05:00
Belen Curcio 95876fa0e8 Merge branch 'master' of github.com:coralproject/talk into whizard-whitelist 2017-02-27 17:19:54 -03:00
Belen Curcio 555faa02f5 new translations for configure 2017-02-27 17:18:37 -03:00
Belen Curcio 1234a99a0d copy for install permitted domains updated 2017-02-27 17:16:19 -03:00
Belen Curcio b9e9d3e7fa status 2017-02-27 17:06:22 -03:00
Belen Curcio 628e8844f3 final translations 2017-02-27 17:04:14 -03:00
Belen Curcio b47fd35437 add user translations 2017-02-27 16:58:16 -03:00
David Jay 1b495b9b09 Merge branch 'master' into bugfix-second-new-comments 2017-02-27 14:57:24 -05:00
David Jay 373d0de576 Merge pull request #346 from coralproject/mod-queue-sort
Adding sort to moderation queue.
2017-02-27 14:57:09 -05:00
Belen Curcio 7341fdff4e add Organization translations 2017-02-27 16:54:03 -03:00
Belen Curcio 1e52c579cc Ínitial translations 2017-02-27 16:50:04 -03:00
Belen Curcio 013625df27 no validation for domains 2017-02-27 16:39:52 -03:00
David Jay ab16e5bfe7 Retrieving comment y id from backend 2017-02-27 14:26:20 -05:00
David Jay dfd211deda Merge branch 'master' into mod-queue-sort 2017-02-27 14:05:04 -05:00
Belen Curcio 9305853502 Renamed to PermittedDomains 2017-02-27 15:50:07 -03:00
Belen Curcio b1ec512721 Adding whitelist step, sending whitelist 2017-02-27 14:42:57 -03:00
gaba ca4b03872b Merge branch 'master' into story-138187767-mod-flag-names 2017-02-27 09:39:52 -08:00
David Jay 83e713d889 Merge branch 'master' into bugfix-second-new-comments 2017-02-27 12:34:11 -05:00
David Jay a47e2378e9 Merge pull request #331 from coralproject/dont-agree
Don't Agree flags
2017-02-27 12:32:56 -05:00
David Jay 491f254206 Merge branch 'mod-queue-sort' of github.com:coralproject/talk into mod-queue-sort 2017-02-27 12:30:04 -05:00
David Jay 2b22ce076c Merge branch 'master' into mod-queue-sort 2017-02-27 12:29:42 -05:00
David Jay 22793c02e2 Merge branch 'master' into dont-agree 2017-02-27 12:29:19 -05:00
David Jay 37b08fd4e4 Displaying oldest first by default. 2017-02-27 12:28:13 -05:00
Kim Gardner 7914296997 Merge pull request #347 from coralproject/header
[Tablet] - Header and Drawer
2017-02-27 11:50:30 -05:00
gaba 714b496c9d Merge branch 'master' into story-138187767-mod-flag-names 2017-02-27 07:46:23 -08:00
Belen Curcio e8a92b768c logout and version in drawer 2017-02-27 11:52:21 -03:00
Belen Curcio f85a6d4da1 Drawer 2017-02-27 11:49:53 -03:00
Kim Gardner 3a6a8ff199 Merge pull request #334 from coralproject/story-140080313-modal-dn
Prompt for username when signing up with Facebook
2017-02-27 08:08:19 -05:00
David Jay 847df91cbc Merge branch 'master' into bugfix-second-new-comments 2017-02-24 20:03:42 -05:00
gaba a429af3c0e Removes varialbe not used. 2017-02-24 16:10:39 -08:00
gaba d650779c1a Removes ; 2017-02-24 16:10:38 -08:00
gaba 7557592dc4 Get more data into the store. 2017-02-24 16:06:18 -08:00
David Jay 66a890458c Adding sort to moderation queue. 2017-02-24 17:42:29 -05:00
David Jay 6a9d8c0436 Removing refetch and prevent 0 from displaying on empty comment streams. 2017-02-24 15:51:43 -05:00
David Erwin f1c1214229 Merge branch 'master' into story-140080313-modal-dn 2017-02-24 15:40:11 -05:00
David Erwin 2739a01cd3 Merge pull request #341 from coralproject/notifications-alerts
copy updates and remove notification
2017-02-24 15:37:34 -05:00
Wyatt Johnson 212037891a Merge branch 'master' into assets-by-like 2017-02-24 13:36:54 -07:00
Riley Davis 11a664d446 Merge branch 'master'
Conflicts:
	client/coral-plugin-commentbox/CommentBox.js
2017-02-24 13:33:25 -07:00
David Erwin b981bea97f Merge pull request #343 from coralproject/updateCountCache-bug
fix Reply bug
2017-02-24 15:31:16 -05:00
Riley Davis d20fa3f497 Merge branch 'master' into notifications-alerts 2017-02-24 13:29:44 -07:00
Riley Davis 5c441e90e7 do not update countCache if the CommentBox is in a reply 2017-02-24 13:08:48 -07:00
Riley Davis 893fe97e66 Merged branch master into assets-by-like 2017-02-24 12:58:33 -07:00
Riley Davis aefcb29877 remove comment count from results 2017-02-24 12:58:06 -07:00
David Jay dcc16dc598 Removing extra semicolon. 2017-02-24 14:45:47 -05:00
David Jay 4015bea1db Merge branch 'master' into story-140080313-modal-dn 2017-02-24 14:39:51 -05:00
Riley Davis 1058bff8ad remove logs 2017-02-24 11:30:59 -07:00
Riley Davis 38e093aac3 remove most liked comments widget 2017-02-24 11:26:23 -07:00
Riley Davis 886240a7bc translations. remove console.log on frontend. remove notification on POST moderation 2017-02-24 11:11:18 -07:00
Wyatt Johnson 799727264e Removed log 2017-02-24 09:57:57 -07:00
Wyatt Johnson f5277ce5e3 Patched silly bug 2017-02-24 09:57:24 -07:00
Riley Davis c283f193de remove console.log 2017-02-24 09:07:03 -07:00
riley c8c3c4acaf lint 2017-02-24 08:59:23 -07:00
Riley Davis dc5f95ec7f simplify moderationqueue 2017-02-24 08:50:52 -07:00
Riley Davis 5d1139dbdd Merge branch 'master'
Conflicts:
	client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js
2017-02-24 08:49:30 -07: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
Riley Davis be25d6f56d "works" 2017-02-23 15:46:44 -07:00
Riley Davis 2a0f4906a7 Merged branch assets-by-like into assets-by-like 2017-02-23 15:23:15 -07:00
gaba 92aa107eeb Merge branch 'master' into story-140080313-modal-dn 2017-02-23 13:58:10 -08:00
gaba 022667c9ae Brings some of the components into FakeComment 2017-02-23 13:55:51 -08:00
Wyatt Johnson 30b6259e87 Patched bug with asset metrics 2017-02-23 14:43:25 -07:00
Riley Davis 2f314c6937 omg, a bunch of UI madness 2017-02-23 14:29:51 -07:00
David Jay f795a96f46 Returning dontagree id on post. 2017-02-23 13:19:09 -05:00
gaba 5d0ea6a880 Rmove continue button, fix bug with facebook signup. 2017-02-23 09:48:59 -08:00
David Jay 3f5d751a80 Setting reason to null for dontAgree. 2017-02-23 12:24:32 -05:00
David Jay 8cb0f9cd90 Merge branch 'master' into dont-agree 2017-02-23 12:12:53 -05:00
Riley Davis 197569714b Merged branch assets-by-like into assets-by-like 2017-02-22 14:25:19 -07:00
Wyatt Johnson 9e759c7667 Adjusted BE beheviour 2017-02-22 13:55:48 -07:00
Riley Davis e6b78a3e63 change some copy 2017-02-22 13:33:50 -07:00
Riley Davis 111b956522 change index route to Dashboard component 2017-02-22 13:10:50 -07:00
gaba 32bc754bf7 It needs to send the translation key 2017-02-22 11:22:51 -08:00
Riley Davis 5ef5a01f59 Merged branch master into assets-by-like 2017-02-22 11:15:35 -07:00
Riley Davis 691d001213 simplify a bit 2017-02-22 10:49:48 -07:00
Riley Davis bc48496327 add most liked comments widget 2017-02-22 10:47:23 -07:00
Wyatt Johnson 6398e2faad Fixed issue with comment hydration 2017-02-22 09:32:51 -07:00
Gabriela Rodríguez Berón 224e58f3e2 Merge branch 'master' into story-140080313-modal-dn 2017-02-22 08:16:51 -08:00
Gabriela Rodríguez Berón 3e2b8b1a20 Merge branch 'master' into dont-agree 2017-02-22 08:09:55 -08:00
gaba 2c60285981 The component needs the banned value. 2017-02-22 07:00:15 -08:00
gaba 1b79a10580 Change username when it changes. 2017-02-21 16:36:50 -08:00
gaba 5d0730b4d5 Adds fake comment component. 2017-02-21 14:52:19 -08:00
Wyatt Johnson 2360f280d2 Added commentMetrics edge. 2017-02-21 14:30:52 -07:00
Riley Davis 30fbcd30a9 Merge branch 'master' into assets-by-like 2017-02-21 14:18:22 -07:00
Riley Davis f32fe2629c css is the worst 2017-02-21 14:16:02 -07:00
Riley Davis 5d94a98305 Like and Flag summaries work again 2017-02-21 14:05:49 -07:00
gaba 02c05e8ae1 CSS, translations and code for new create username dialog. 2017-02-21 12:43:02 -08:00
David Jay 0aaa52e01f Merge branch 'master' into dont-agree 2017-02-21 15:02:33 -05:00
David Jay 3377aa3da2 Displaying DontAgree actions as flags. 2017-02-21 14:55:00 -05:00
David Jay 1d8ce1ae34 Posting DontAgree actions. 2017-02-21 14:41:33 -05:00
Riley Davis 364cf1808e assets by like? 2017-02-21 10:28:33 -07:00
Riley Davis 8866366d3c storing my place in case we want to come back to this 2017-02-17 16:07:34 -07:00
David Jay d10936749d Adding DontAgreeActionSummaries 2017-02-17 11:14:42 -05:00
David Jay e4e7ff27d2 Adding don't agree action to typedefs. 2017-02-16 16:54:05 -05:00
261 changed files with 6293 additions and 2200 deletions
-1
View File
@@ -5,7 +5,6 @@
],
"plugins": [
"add-module-exports",
"transform-async-to-generator",
"transform-class-properties",
"transform-decorators-legacy",
"transform-object-assign",
+3
View File
@@ -4,6 +4,9 @@
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2017
},
"rules": {
"indent": ["error",
2
+4
View File
@@ -12,3 +12,7 @@ dump.rdb
*.cfg
.idea/
coverage/
# remove plugin folders
plugins
plugins.json
+1 -4
View File
@@ -1,8 +1,5 @@
FROM node:7
# Install yarn
RUN npm install -g yarn
# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
@@ -20,4 +17,4 @@ RUN yarn install --production
# Bundle app source
COPY . /usr/src/app
CMD [ "yarn", "start" ]
CMD ["yarn", "start"]
+265
View File
@@ -0,0 +1,265 @@
# Talk Plugins
Plugins for Talk can take various forms, currently we are only supporting server
side plugins.
## Plugin Registration: `plugins.json`
All plugins must be registered in the root file `plugins.json`.
The format for this file is thus:
```js
{
"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 `plugins.json` would
require a plugin from `plugins/people`, which must provide a `index.js` file
that returns an object that matches the Plugin Specification.
## Server Plugins
### 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.
### 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;
}
}
}
}
};
```
+6 -1
View File
@@ -1,6 +1,9 @@
# 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)
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)
Third party licenses are available via the `/client/3rdpartylicenses.txt`
endpoint when the server is running with built assets.
## Contributing to Talk
@@ -38,6 +41,8 @@ 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.
Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for
alternative methods of loading configuration during development.
+4 -4
View File
@@ -34,6 +34,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 +46,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(),
})
};
+1 -1
View File
@@ -1,6 +1,6 @@
machine:
node:
version: 7
version: 7.6
services:
- docker
- redis
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "../.babelrc",
"plugins": [
"transform-async-to-generator",
]
}
+2 -1
View File
@@ -18,6 +18,7 @@
],
"rules": {
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error"
"react/jsx-uses-vars": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
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';
@@ -15,10 +15,10 @@ const routes = (
<div>
<Route exact path="/admin/install" component={InstallContainer}/>
<Route path='/admin' component={LayoutContainer}>
<IndexRoute component={ModerationContainer} />
<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} />
{/* Moderation Routes */}
+46 -5
View File
@@ -1,5 +1,42 @@
import * as actions from '../constants/auth';
import coralApi from '../../../coral-framework/helpers/response';
import coralApi from 'coral-framework/helpers/response';
// Log In.
export const handleLogin = (email, password, recaptchaResponse) => dispatch => {
dispatch({type: actions.LOGIN_REQUEST});
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 => {
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});
}
});
};
const forgotPassowordRequest = () => ({type: actions.FETCH_FORGOT_PASSWORD_REQUEST});
const forgotPassowordSuccess = () => ({type: actions.FETCH_FORGOT_PASSWORD_SUCCESS});
const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILURE});
export const requestPasswordReset = email => dispatch => {
dispatch(forgotPassowordRequest(email));
return coralApi('/account/password/reset', {method: 'POST', body: {email}})
.then(() => dispatch(forgotPassowordSuccess()))
.catch(error => dispatch(forgotPassowordFailure(error)));
};
// Check Login
@@ -10,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);
@@ -28,7 +69,7 @@ const logOutFailure = () => ({type: actions.LOGOUT_FAILURE});
export const logout = () => dispatch => {
dispatch(logOutRequest());
coralApi('/auth', {method: 'DELETE'})
return coralApi('/auth', {method: 'DELETE'})
.then(() => dispatch(logOutSuccess()))
.catch(error => dispatch(logOutFailure(error)));
};
+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});
};
+32 -23
View File
@@ -20,13 +20,17 @@ const validation = (formData, dispatch, next) => {
return dispatch(hasError());
}
const validKeys = Object.keys(formData)
.filter(name => name !== 'domains');
// Required Validation
const empty = Object.keys(formData).filter(name => {
const empty = validKeys
.filter(name => {
const cond = !formData[name].length;
if (cond) {
// Adding Error
// Adding Error
dispatch(addError(name, 'This field is required.'));
} else {
dispatch(addError(name, ''));
@@ -40,18 +44,19 @@ const validation = (formData, dispatch, next) => {
}
// RegExp Validation
const validation = Object.keys(formData).filter(name => {
const cond = !validate[name](formData[name]);
if (cond) {
const validation = validKeys
.filter(name => {
const cond = !validate[name](formData[name]);
if (cond) {
// Adding Error
dispatch(addError(name, errorMsj[name]));
} else {
dispatch(addError(name, ''));
}
dispatch(addError(name, errorMsj[name]));
} else {
dispatch(addError(name, ''));
}
return cond;
});
return cond;
});
if (validation.length) {
return dispatch(hasError());
@@ -71,23 +76,27 @@ export const submitSettings = () => (dispatch, getState) => {
export const submitUser = () => (dispatch, getState) => {
const userFormData = getState().install.toJS().data.user;
validation(userFormData, dispatch, function() {
const data = getState().install.toJS().data;
dispatch(installRequest());
coralApi('/setup', {method: 'POST', body: data})
.then(result => {
console.log(result);
dispatch(installSuccess());
dispatch(nextStep());
})
.catch(error => {
console.error(error);
dispatch(installFailure(`${error.translation_key}`));
});
dispatch(nextStep());
});
};
export const finishInstall = () => (dispatch, getState) => {
const data = getState().install.toJS().data;
dispatch(installRequest());
return coralApi('/setup', {method: 'POST', body: data})
.then(() => {
dispatch(installSuccess());
dispatch(nextStep());
})
.catch(error => {
console.error(error);
dispatch(installFailure(`${error.translation_key}`));
});
};
export const updateSettingsFormData = (name, value) => ({type: actions.UPDATE_FORMDATA_SETTINGS, name, value});
export const updateUserFormData = (name, value) => ({type: actions.UPDATE_FORMDATA_USER, name, value});
export const updatePermittedDomains = (value) => ({type: actions.UPDATE_PERMITTED_DOMAINS_SETTINGS, value});
const checkInstallRequest = () => ({type: actions.CHECK_INSTALL_REQUEST});
const checkInstallSuccess = installed => ({type: actions.CHECK_INSTALL_SUCCESS, installed});
+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});
@@ -1,21 +1,16 @@
import React from 'react';
import styles from './ModerationList.css';
import BanUserButton from './BanUserButton';
import {FabButton} from 'coral-ui';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap';
const ActionButton = ({type = '', user, ...props}) => {
if (type === 'BAN') {
return <BanUserButton user={user} onClick={() => props.showBanUserDialog(props.user, props.id)} />;
}
const ActionButton = ({type = '', ...props}) => {
return (
<FabButton
<Button
className={`${type.toLowerCase()} ${styles.actionButton}`}
cStyle={type.toLowerCase()}
icon={menuActionsMap[type].icon}
onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment}
/>
>{menuActionsMap[type].text}</Button>
);
};
@@ -0,0 +1,113 @@
import React, {PropTypes} from 'react';
import Layout from 'coral-admin/src/components/ui/Layout';
import styles from './NotFound.css';
import {Button, TextField, Alert, Success} from 'coral-ui';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
import Recaptcha from 'react-recaptcha';
const lang = new I18n(translations);
class AdminLogin extends React.Component {
constructor (props) {
super(props);
this.state = {email: '', password: '', requestPassword: false};
}
handleSignIn = e => {
e.preventDefault();
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, loginMaxExceeded, recaptchaPublic} = this.props;
const signInForm = (
<form onSubmit={this.handleSignIn}>
{errorMessage && <Alert>{lang.t(`errors.${errorMessage}`)}</Alert>}
<TextField
label='Email Address'
value={this.state.email}
onChange={e => this.setState({email: e.target.value})} />
<TextField
label='Password'
value={this.state.password}
onChange={e => this.setState({password: e.target.value})}
type='password' />
<div style={{height: 10}}></div>
<Button
type='submit'
cStyle='black'
full
onClick={this.handleSignIn}>Sign In</Button>
<p className={styles.forgotPasswordCTA}>
Forgot your password? <a href="#" className={styles.forgotPasswordLink} onClick={e => {
e.preventDefault();
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 = (
this.props.passwordRequestSuccess
? <p className={styles.passwordRequestSuccess} onClick={() => {
location.href = location.href;
}}>
{this.props.passwordRequestSuccess} <a className={styles.signInLink} href="#">Sign in</a>
<Success />
</p>
: <form onSubmit={this.handleRequestPassword}>
<TextField
label='Email Address'
value={this.state.email}
onChange={e => this.setState({email: e.target.value})} />
<Button
type='submit'
cStyle='black'
full
onClick={this.handleRequestPassword}>Reset Password</Button>
</form>
);
return (
<Layout fixedDrawer restricted={true}>
<div className={styles.loginLayout}>
<h1 className={styles.loginHeader}>Team sign in</h1>
<p className={styles.loginCTA}>Sign in to interact with your community.</p>
{ this.state.requestPassword ? requestPasswordForm : signInForm }
</div>
</Layout>
);
}
}
AdminLogin.propTypes = {
loginMaxExceeded: PropTypes.bool.isRequired,
handleLogin: PropTypes.func.isRequired,
passwordRequestSuccess: PropTypes.string,
loginError: PropTypes.string,
recaptchaPublic: PropTypes.string
};
export default AdminLogin;
@@ -1,10 +1,11 @@
.banButton {
width: 114px;
letter-spacing: 1px;
-webkit-transform: scale(.8);
transform: scale(.8);
margin: 0;
i {
vertical-align: middle;
margin-right: 10px;
margin-right: 5px;
font-size: 14px;
}
}
@@ -8,7 +8,7 @@ const lang = new I18n(translations);
const BanUserButton = ({user, ...props}) => (
<div className={styles.ban}>
<Button cStyle='darkGrey'
<Button cStyle='ban'
className={`ban ${styles.banButton}`}
disabled={user.status === 'BANNED' ? 'disabled' : ''}
onClick={props.onClick}
@@ -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;
@@ -1,66 +0,0 @@
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './FlagWidget.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
const lang = new I18n(translations);
const FlagWidget = ({assets}) => {
return (
<table className={styles.widgetTable}>
<thead className={styles.widgetHead}>
<tr>
<th></th>{/* empty on purpose */}
<th>{lang.t('streams.article')}</th>
<th>{lang.t('modqueue.flagged')}</th>
<th>{lang.t('modqueue.likes')}</th>
<th>{lang.t('dashboard.comment_count')}</th>
</tr>
</thead>
<tbody>
{
assets.length
? assets.map((asset, index) => {
const flagSummary = asset.action_summaries.find(s => s.__typename === 'FlagAssetActionSummary');
const likeSummary = asset.action_summaries.find(s => s.__typename === 'LikeAssetActionSummary');
return (
<tr key={asset.id}>
<td>{index + 1}.</td>
<td>
<Link to={`/admin/moderate/flagged/${asset.id}`}>{asset.title}</Link>
<p className={styles.lede}>{asset.author} - Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</td>
<td>{flagSummary ? flagSummary.actionCount : 0}</td>
<td>{likeSummary ? likeSummary.actionCount : 0}</td>
<td>{asset.commentCount}</td>
</tr>
);
})
: <tr><td colSpan="3">{lang.t('dashboard.no_flags')}</td></tr>
}
</tbody>
</table>
);
};
FlagWidget.propTypes = {
assets: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
title: PropTypes.string,
url: PropTypes.string,
commentCount: PropTypes.number,
action_summaries: PropTypes.arrayOf(
PropTypes.shape({
__typename: PropTypes.string.isRequired,
actionCount: PropTypes.number.isRequired,
actionableItemCount: PropTypes.number.isRequired
})
)
})
).isRequired
};
export default FlagWidget;
@@ -178,8 +178,13 @@
}
}
.selected {
border-radius: 10px;
}
.actionButton {
transform: scale(.8);
margin: 0;
width: 140px;
}
+30 -5
View File
@@ -1,12 +1,37 @@
.layout {
max-width: 800px;
margin: 0 auto;
max-width: 800px;
margin: 0 auto;
}
.loginLayout {
max-width: 400px;
margin: 0 auto;
}
.loginHeader, .loginCTA, .forgotPasswordCTA, .passwordRequestSuccess {
text-align: center;
font-size: 16px;
}
.forgotPasswordLink, .signInLink {
color: blue;
font-weight: normal;
text-decoration: none;
}
.forgotPasswordLink:hover, .signInLink:hover {
text-decoration: underline;
}
.layout h1 {
font-size: 40px;
font-size: 40px;
}
.layout img {
width: 100%;
.loginHeader {
font-size: 30px;
}
.passwordRequestSuccess {
cursor: pointer;
padding: 8px 14px;
}
@@ -1,13 +0,0 @@
import React from 'react';
import {Layout} from 'react-mdl';
import styles from './NotFound.css';
export const PermissionRequired = () => (
<Layout fixedDrawer>
<div className={styles.layout} >
<h1>Permission Required</h1>
<p>Were sorry, but you dont have access to that page.</p>
<img src="https://coralproject.net/images/communicorn.jpg" alt="Communicorn"/>
</div>
</Layout>
);
+47 -10
View File
@@ -1,18 +1,55 @@
import React from 'react';
import React, {PropTypes} from 'react';
import {Navigation, Drawer} from 'react-mdl';
import {Link} from 'react-router';
import styles from './Header.css';
import {IndexLink, Link} from 'react-router';
import styles from './Drawer.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
export default () => (
<Drawer>
<Navigation>
<Link className={styles.navLink} to="/admin">{lang.t('configure.moderate')}</Link>
<Link className={styles.navLink} to="/admin/community">{lang.t('configure.community')}</Link>
<Link className={styles.navLink} to="/admin/configure">{lang.t('configure.configure')}</Link>
</Navigation>
const CoralDrawer = ({handleLogout, restricted = false}) => (
<Drawer className={styles.header}>
{ !restricted ?
<div>
<Navigation className={styles.nav}>
<IndexLink
className={styles.navLink}
to="/admin/dashboard"
activeClassName={styles.active}>
{lang.t('configure.dashboard')}
</IndexLink>
<Link
className={styles.navLink}
to="/admin/moderate"
activeClassName={styles.active}>
{lang.t('configure.moderate')}
</Link>
<Link className={styles.navLink}
to="/admin/stories"
activeClassName={styles.active}>
{lang.t('configure.stories')}
</Link>
<Link className={styles.navLink}
to="/admin/community"
activeClassName={styles.active}>
{lang.t('configure.community')}
</Link>
<Link
className={styles.navLink}
to="/admin/configure"
activeClassName={styles.active}>
{lang.t('configure.configure')}
</Link>
<a onClick={handleLogout}>Sign Out</a>
<span>{`v${process.env.VERSION}`}</span>
</Navigation>
</div> : null }
</Drawer>
);
CoralDrawer.propTypes = {
handleLogout: PropTypes.func.isRequired,
restricted: PropTypes.bool // hide app elements from a logged out user
};
const lang = new I18n(translations);
export default CoralDrawer;
@@ -1,7 +1,25 @@
@custom-media --table-viewport (max-width: 1024px);
@media (--table-viewport) {
.logo {
margin-left: 58px;
h1 {
background: #696969;
}
span {
color: #fcfcfc;
}
}
.nav {
display: none;
}
}
.header {
background-color: transparent;
box-shadow: none;
min-height: 58px;
display: block;
}
.header > div {
@@ -56,8 +74,12 @@
background-color: transparent;
transition: background-color 200ms;
&:hover {
background-color: #232323;
}
&.active {
background-color: #232323;
background-color: #232323;
}
}
+21 -7
View File
@@ -1,4 +1,4 @@
import React from 'react';
import React, {PropTypes} from 'react';
import {Navigation, Header, IconButton, MenuItem, Menu} from 'react-mdl';
import {Link, IndexLink} from 'react-router';
import styles from './Header.css';
@@ -6,36 +6,43 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import {Logo} from './Logo';
export default ({handleLogout, restricted = false}) => (
const CoralHeader = ({handleLogout, restricted = false}) => (
<Header className={styles.header}>
<Logo />
<Logo className={styles.logo} />
{
!restricted ?
<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}>
@@ -64,4 +71,11 @@ export default ({handleLogout, restricted = false}) => (
</Header>
);
CoralHeader.propTypes = {
handleLogout: PropTypes.func.isRequired,
restricted: PropTypes.bool // hide elemnts from a user that's logged out
};
const lang = new I18n(translations);
export default CoralHeader;
+12 -5
View File
@@ -1,15 +1,22 @@
import React from 'react';
import React, {PropTypes} from 'react';
import {Layout as LayoutMDL} from 'react-mdl';
import Header from './Header';
import Drawer from './Drawer';
import styles from './Layout.css';
export const Layout = ({children, ...props}) => (
const Layout = ({children, handleLogout = () => {}, restricted = false, ...props}) => (
<LayoutMDL fixedDrawer>
<Header {...props}/>
<Drawer />
<div className={styles.layout} >
<Header handleLogout={handleLogout} restricted={restricted} {...props} />
<Drawer handleLogout={handleLogout} restricted={restricted} {...props} />
<div className={styles.layout}>
{children}
</div>
</LayoutMDL>
);
Layout.propTypes = {
handleLogout: PropTypes.func,
restricted: PropTypes.bool // hide elements from a user that's logged out
};
export default Layout;
@@ -19,6 +19,7 @@
background: #E5E5E5;
height: 100%;
width: 128px;
z-index: 10;
}
.base {
+2 -2
View File
@@ -2,8 +2,8 @@ import React from 'react';
import styles from './Logo.css';
import {CoralLogo} from 'coral-ui';
export const Logo = () => (
<div className={styles.logo}>
export const Logo = ({className = ''}) => (
<div className={`${styles.logo} ${className}`}>
<h1>
<CoralLogo className={styles.base} />
<span>Talk</span>
+9
View File
@@ -7,3 +7,12 @@ export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN';
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
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';
export const FETCH_FORGOT_PASSWORD_FAILURE = 'FETCH_FORGOT_PASSWORD_FAILURE';
@@ -10,6 +10,7 @@ export const INSTALL_SUCCESS = 'INSTALL_SUCCESS';
export const INSTALL_FAILURE = 'INSTALL_FAILURE';
export const UPDATE_FORMDATA_USER = 'UPDATE_FORMDATA_USER';
export const UPDATE_FORMDATA_SETTINGS = 'UPDATE_FORMDATA_SETTINGS';
export const UPDATE_PERMITTED_DOMAINS_SETTINGS = 'UPDATE_PERMITTED_DOMAINS_SETTINGS';
export const CHECK_INSTALL_REQUEST = 'CHECK_INSTALL_REQUEST';
export const CHECK_INSTALL_SUCCESS = 'CHECK_INSTALL_SUCCESS';
@@ -3,7 +3,7 @@
h3 {
color: black;
font-size: 1.76em;
font-size: 1.26em;
font-weight: 500;
}
}
@@ -24,6 +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 {
@@ -37,7 +61,9 @@
.settingsHeader {
margin-top: 3px;
margin-bottom: 10px;
margin-bottom: 7px;
font-size: 18px;
font-weight: 500;
}
.disabledSettingText {
@@ -54,11 +80,6 @@
overflow: visible;
}
.configSettingInfoBox p {
font-size: 12px;
bottom: 0;
}
.configSettingEmbed {
border: 1px solid #ccc;
border-radius: 4px;
@@ -69,6 +90,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 {
@@ -77,6 +103,8 @@
border-color: #ccc;
border-style: solid;
border-width: 0px 0px 1px 0px;
font-size: 14px;
text-align: center;
}
.charCountTexfieldEnabled {
@@ -92,44 +120,34 @@
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 {
width: 100%;
padding: 10px;
display: block;
input {
width: 100%;
}
}
.wordlistHeader {
font-weight: bold;
font-size:18px;
margin-bottom:3px;
.customCSSInput {
width: 100%;
font-size: 14px;
padding: 14px;
letter-spacing: 0.03em;
color: #555;
box-sizing: border-box;
}
.enabledSetting {
@@ -150,7 +168,7 @@
margin-top: 38px;
}
.commentSettingsSection {
.settingsSection {
padding-bottom: 200px;
.action {
display: inline-block;
@@ -165,3 +183,24 @@
padding-left: 30px;
}
}
.Configure {
p {
line-height: 1.2;
max-width: 550px;
}
.wrapper {
width: 550px;
}
.descriptionBox {
margin-top: 15px;
max-width: 550px;
input {
height: 150px;
}
}
}
@@ -8,22 +8,20 @@ import {
updateDomainlist
} from '../../actions/settings';
import {Button, List, Item} from 'coral-ui';
import {Button, List, Item, Card, Spinner} from 'coral-ui';
import styles from './Configure.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import EmbedLink from './EmbedLink';
import CommentSettings from './CommentSettings';
import Wordlist from './Wordlist';
import Domainlist from './Domainlist';
import has from 'lodash/has';
import translations from 'coral-admin/src/translations.json';
import StreamSettings from './StreamSettings';
import ModerationSettings from './ModerationSettings';
import TechSettings from './TechSettings';
class Configure extends Component {
constructor (props) {
super(props);
this.state = {
activeSection: 'comments',
activeSection: 'stream',
changed: false,
errors: {}
};
@@ -70,40 +68,48 @@ class Configure extends Component {
getSection (section) {
const pageTitle = this.getPageTitle(section);
let sectionComponent;
switch(section){
case 'comments':
return <CommentSettings
title={pageTitle}
fetchingSettings={this.props.fetchingSettings}
case 'stream':
sectionComponent = <StreamSettings
settings={this.props.settings}
updateSettings={this.onSettingUpdate}
errors={this.state.errors}
settingsError={this.onSettingError}/>;
case 'embed':
return has(this, 'props.settings.domains.whitelist')
? <div>
<Domainlist
domains={this.props.settings.domains.whitelist}
onChangeDomainlist={this.onChangeDomainlist}/>
<EmbedLink title={pageTitle} />
</div>
: <EmbedLink title={pageTitle} />;
case 'wordlist':
return has(this, 'props.settings.wordlist')
? <Wordlist
bannedWords={this.props.settings.wordlist.banned}
suspectWords={this.props.settings.wordlist.suspect}
onChangeWordlist={this.onChangeWordlist} />
: <p>loading wordlists</p>;
break;
case 'moderation':
sectionComponent = <ModerationSettings
onChangeWordlist={this.onChangeWordlist}
settings={this.props.settings}
updateSettings={this.onSettingUpdate} />;
break;
case 'tech':
sectionComponent = <TechSettings
onChangeDomainlist={this.onChangeDomainlist}
settings={this.props.settings}
updateSettings={this.onSettingUpdate} />;
}
if (this.props.settings.fetchingSettings) {
return <Card shadow="4"><Spinner/>Loading settings...</Card>;
}
return (
<div className={styles.settingsSection}>
<h3>{pageTitle}</h3>
{sectionComponent}
</div>
);
}
getPageTitle (section) {
switch(section) {
case 'comments':
return lang.t('configure.comment-settings');
case 'embed':
return lang.t('configure.embed-comment-stream');
case 'stream':
return lang.t('configure.stream-settings');
case 'moderation':
return lang.t('configure.moderation-settings');
case 'tech':
return lang.t('configure.tech-settings');
default:
return '';
}
@@ -120,14 +126,14 @@ class Configure extends Component {
<div className={styles.container}>
<div className={styles.leftColumn}>
<List onChange={this.changeSection} activeItem={activeSection}>
<Item itemId='comments' icon="settings">
{lang.t('configure.comment-settings')}
<Item itemId='stream' icon='speaker_notes'>
{lang.t('configure.stream-settings')}
</Item>
<Item itemId='embed' icon='code'>
{lang.t('configure.embed-comment-stream')}
<Item itemId='moderation' icon='thumbs_up_down'>
{lang.t('configure.moderation-settings')}
</Item>
<Item itemId='wordlist' icon='settings'>
{lang.t('configure.wordlist')}
<Item itemId='tech' icon='code'>
{lang.t('configure.tech-settings')}
</Item>
</List>
<div className={styles.saveBox}>
@@ -1,26 +1,30 @@
import React from 'react';
import {Card} from 'coral-ui';
import styles from './Configure.css';
import TagsInput from 'react-tagsinput';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import TagsInput from 'react-tagsinput';
import styles from './Configure.css';
import {Card} from 'coral-ui';
const lang = new I18n(translations);
const Domainlist = ({domains, onChangeDomainlist}) => (
<div>
<h3>{lang.t('configure.domain-list-title')}</h3>
<Card id={styles.domainlist}>
<p className={styles.domainlistDesc}>{lang.t('configure.domain-list-text')}</p>
<TagsInput
value={domains}
inputProps={{placeholder: 'URL'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => onChangeDomainlist('whitelist', tags)}
/>
const Domainlist = ({domains, onChangeDomainlist}) => {
return (
<Card id={styles.domainlist} className={styles.configSetting}>
<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>
</div>
);
);
};
export default Domainlist;
const lang = new I18n(translations);
@@ -44,19 +44,21 @@ class EmbedLink extends Component {
"></script>
`.trim();
return (
<div>
<h3>{this.props.title}</h3>
<div>
<Card shadow="2">
<p>{lang.t('configure.copy-and-paste')}</p>
<textarea rows={5} type='text' className={styles.embedInput} value={embedText} readOnly={true}/>
<Card shadow="2" className={styles.configSetting}>
<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>
</Card>
<div className={styles.copiedText}>
{this.state.copied && 'Copied!'}
</div>
</div>
</div>
</div>
</Card>
);
}
}
@@ -0,0 +1,91 @@
import React, {PropTypes} from 'react';
import styles from './Configure.css';
import {Card} from 'coral-ui';
import {Checkbox} from 'react-mdl';
import Wordlist from './Wordlist';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
const lang = new I18n(translations);
const updateModeration = (updateSettings, mod) => () => {
const moderation = mod === 'PRE' ? 'POST' : 'PRE';
updateSettings({moderation});
};
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
const on = styles.enabledSetting;
const off = styles.disabledSetting;
return (
<div className={styles.Configure}>
<Card className={`${styles.configSetting} ${settings.requireEmailConfirmation ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateEmailConfirmation(updateSettings, settings.requireEmailConfirmation)}
checked={settings.requireEmailConfirmation} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.require-email-verification')}</div>
<p className={settings.requireEmailConfirmation ? '' : styles.disabledSettingText}>
{lang.t('configure.require-email-verification-text')}
</p>
</div>
</Card>
<Card className={`${styles.configSetting} ${settings.moderation === 'PRE' ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateModeration(updateSettings, settings.moderation)}
checked={settings.moderation === 'PRE'} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.enable-pre-moderation')}</div>
<p className={settings.moderation === 'PRE' ? '' : styles.disabledSettingText}>
{lang.t('configure.enable-pre-moderation-text')}
</p>
</div>
</Card>
<Card className={`${styles.configSetting} ${settings.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}
onChangeWordlist={onChangeWordlist} />
</div>
);
};
ModerationSettings.propTypes = {
onChangeWordlist: PropTypes.func.isRequired,
settings: PropTypes.shape({
moderation: PropTypes.string.isRequired,
wordlist: PropTypes.shape({
banned: PropTypes.array.isRequired,
suspect: PropTypes.array.isRequired
})
}).isRequired,
updateSettings: PropTypes.func.isRequired
};
export default ModerationSettings;
@@ -3,8 +3,8 @@ import {SelectField, Option} from 'react-mdl-selectfield';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import styles from './Configure.css';
import {Textfield, Checkbox} from 'react-mdl';
import {Card, Icon, Spinner} from 'coral-ui';
import {Checkbox, Textfield} from 'react-mdl';
import {Card, Icon, TextArea} from 'coral-ui';
const TIMESTAMPS = {
weeks: 60 * 60 * 24 * 7,
@@ -27,15 +27,6 @@ const updateCharCount = (updateSettings, settingsError) => (event) => {
updateSettings({charCount: charCount});
};
const updateModeration = (updateSettings, mod) => () => {
const moderation = mod === 'PRE' ? 'POST' : 'PRE';
updateSettings({moderation});
};
const updateEmailConfirmation = (updateSettings, verify) => () => {
updateSettings({requireEmailConfirmation: !verify});
};
const updateInfoBoxEnable = (updateSettings, infoBox) => () => {
const infoBoxEnable = !infoBox;
updateSettings({infoBoxEnable});
@@ -51,12 +42,6 @@ const updateClosedMessage = (updateSettings) => (event) => {
updateSettings({closedMessage});
};
const updateCustomCssUrl = (updateSettings) => (event) => {
console.log('updateCustomCssUrl', event.target.value);
const customCssUrl = event.target.value;
updateSettings({customCssUrl});
};
// If we are changing the measure we need to recalculate using the old amount
// Same thing if we are just changing the amount
const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => {
@@ -72,44 +57,14 @@ const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => {
}
};
const CommentSettings = ({fetchingSettings, title, updateSettings, settingsError, settings, errors}) => {
if (fetchingSettings) {
return <Card shadow="4"><Spinner/>Loading settings...</Card>;
}
const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
// just putting this here for shorthand below
const on = styles.enabledSetting;
const off = styles.disabledSetting;
return (
<div className={styles.commentSettingsSection}>
<h3>{title}</h3>
<Card className={`${styles.configSetting} ${settings.moderation === 'PRE' ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateModeration(updateSettings, settings.moderation)}
checked={settings.moderation === 'PRE'} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.enable-pre-moderation')}</div>
<p className={settings.moderation === 'PRE' ? '' : styles.disabledSettingText}>
{lang.t('configure.enable-pre-moderation-text')}
</p>
</div>
</Card>
<Card className={`${styles.configSetting} ${settings.requireEmailConfirmation ? on : off}`}>
<div className={styles.action}>
<Checkbox
onChange={updateEmailConfirmation(updateSettings, settings.requireEmailConfirmation)}
checked={settings.requireEmailConfirmation} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.require-email-verification')}</div>
<p className={settings.requireEmailConfirmation ? '' : styles.disabledSettingText}>
{lang.t('configure.require-email-verification-text')}
</p>
</div>
</Card>
<div className={styles.Configure}>
<Card className={`${styles.configSetting} ${settings.charCountEnable ? on : off}`}>
<div className={styles.action}>
<Checkbox
@@ -124,7 +79,9 @@ const CommentSettings = ({fetchingSettings, title, updateSettings, settingsError
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 &&
@@ -137,41 +94,43 @@ const CommentSettings = ({fetchingSettings, title, updateSettings, settingsError
</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
<div>
<TextArea
className={styles.descriptionBox}
onChange={updateInfoBoxContent(updateSettings)}
value={settings.infoBoxContent}
label={lang.t('configure.include-text')}
rows={3}/>
/>
</div>
</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.settingsHeader}>{lang.t('configure.closed-stream-settings')}</div>
<div className={styles.wrapper}>
<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.content}>
{lang.t('configure.close-after')}
<br />
@@ -194,23 +153,12 @@ const CommentSettings = ({fetchingSettings, title, updateSettings, settingsError
</div>
</div>
</Card>
<Card className={styles.configSettingInfoBox}>
<div className={styles.content}>
{lang.t('configure.custom-css-url')}
<p>{lang.t('configure.custom-css-url-desc')}</p>
<br />
<Textfield
style={{width: '100%'}}
label={lang.t('configure.custom-css-url')}
value={settings.customCssUrl}
onChange={updateCustomCssUrl(updateSettings)} />
</div>
</Card>
{/* the above card should be the last one if at all possible because of z-index issues with the selects */}
</div>
);
};
export default CommentSettings;
export default StreamSettings;
// To see if we are talking about weeks, days or hours
// We talk the remainder of the division and see if it's 0
@@ -0,0 +1,45 @@
import React, {PropTypes} from 'react';
import {Card} from 'coral-ui';
import Domainlist from './Domainlist';
import EmbedLink from './EmbedLink';
import styles from './Configure.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
const lang = new I18n(translations);
const updateCustomCssUrl = (updateSettings) => (event) => {
const customCssUrl = event.target.value;
updateSettings({customCssUrl});
};
const TechSettings = ({settings, onChangeDomainlist, updateSettings}) => {
return (
<div className={styles.Configure}>
<Domainlist
domains={settings.domains.whitelist}
onChangeDomainlist={onChangeDomainlist} />
<EmbedLink />
<Card className={styles.configSetting}>
<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>
);
};
TechSettings.propTypes = {
settings: PropTypes.shape({
domains: PropTypes.shape({
whitelist: PropTypes.array.isRequired
})
}).isRequired,
updateSettings: PropTypes.func.isRequired
};
export default TechSettings;
@@ -7,28 +7,30 @@ import {Card} from 'coral-ui';
const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
<div>
<h3>{lang.t('configure.banned-words-title')}</h3>
<Card id={styles.bannedWordlist}>
<p className={styles.wordlistHeader}>{lang.t('configure.banned-word-header')}</p>
<Card id={styles.bannedWordlist} className={styles.configSetting}>
<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>
<h3>{lang.t('configure.suspect-words-title')}</h3>
<Card id={styles.suspectWordlist}>
<p className={styles.wordlistHeader}>{lang.t('configure.suspect-word-header')}</p>
<Card id={styles.suspectWordlist} className={styles.configSetting}>
<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;
@@ -1,19 +1,5 @@
.Dashboard {
display: flex;
padding: 5px;
}
.widget {
margin-top: 10px;
flex: 1;
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.2);
margin-right: 10px;
padding: 15px;
}
.widget:last-child {
margin-right: 0;
}
.heading {
@@ -21,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;
}
}
@@ -1,38 +1,54 @@
import React from 'react';
import {compose} from 'react-apollo';
import {mostFlags} from 'coral-admin/src/graphql/queries';
import {Spinner} from 'coral-ui';
import styles from './Dashboard.css';
import FlagWidget from '../../components/FlagWidget';
import {compose} from 'react-apollo';
import {connect} from 'react-redux';
import {getMetrics} from 'coral-admin/src/graphql/queries';
import FlagWidget from './FlagWidget';
import ActivityWidget from './ActivityWidget';
import CountdownTimer from 'coral-admin/src/components/CountdownTimer';
import {showBanUserDialog, hideBanUserDialog} from 'coral-admin/src/actions/moderation';
import {Spinner} from 'coral-ui';
class Dashboard extends React.Component {
reloadData = () => {
this.props.data.refetch();
}
render () {
const {data} = this.props;
const {metrics: assets} = data;
if (data.loading) {
if (this.props.data && this.props.data.loading) {
return <Spinner />;
}
if (data.error) {
return <code><pre>{data.error}</pre></code>;
}
const {data: {assetsByActivity, assetsByFlag}} = this.props;
return (
<div className={styles.Dashboard}>
<div className={styles.widget}>
<h2 className={styles.heading}>Top Ten Articles with the most flagged comments</h2>
<FlagWidget assets={assets} />
</div>
<div className={styles.widget}>
<h2 className={styles.heading}>Top ten comments with the most likes</h2>
<div>
<CountdownTimer handleTimeout={this.reloadData} />
<div className={styles.Dashboard}>
<FlagWidget assets={assetsByFlag} />
<ActivityWidget assets={assetsByActivity} />
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
settings: state.settings.toJS(),
moderation: state.moderation.toJS()
};
};
const mapDispatchToProps = dispatch => ({
showBanUserDialog: (user, commentId) => dispatch(showBanUserDialog(user, commentId)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false))
});
export default compose(
mostFlags
connect(mapStateToProps, mapDispatchToProps),
getMetrics
)(Dashboard);
@@ -0,0 +1,55 @@
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 FlagWidget = ({assets}) => {
return (
<div className={styles.widget}>
<h2 className={styles.heading}>Articles with the 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;
@@ -0,0 +1,51 @@
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 LikeWidget = ({assets}) => {
return (
<div className={styles.widget}>
<h2 className={styles.heading}>Articles with the most likes</h2>
<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;
@@ -0,0 +1,40 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
import ModerationQueue from 'coral-admin/src/containers/ModerationQueue/ModerationQueue';
import styles from './Widget.css';
import BanUserDialog from 'coral-admin/src/components/BanUserDialog';
const lang = new I18n(translations);
const MostLikedCommentsWidget = props => {
const {
comments,
moderation,
settings,
handleBanUser,
showBanUserDialog,
hideBanUserDialog,
acceptComment,
rejectComment
} = props;
return (
<div className={styles.widget}>
<h2 className={styles.heading}>{lang.t('most_liked_comments')}</h2>
<ModerationQueue
comments={comments}
suspectWords={settings.wordlist.suspect}
showBanUserDialog={showBanUserDialog}
acceptComment={acceptComment}
rejectComment={rejectComment} />
<BanUserDialog
open={moderation.banDialog}
user={moderation.user}
handleClose={hideBanUserDialog}
handleBanUser={handleBanUser} />
</div>
);
};
export default MostLikedCommentsWidget;
@@ -0,0 +1,106 @@
:root {
--row-height: 60px;
}
.widget {
margin: 10px 5px 5px 5px;
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.2);
flex: 1;
background-color: white;
box-sizing: border-box;
}
.widget * {
box-sizing: border-box;
}
.heading {
margin: 0;
padding-left: 10px;
font-size: 1.5rem;
font-weight: bold;
}
.widgetTable {
height: calc(var(--row-height) * 10);
}
.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;
}
.widgetHead p:last-child {
float: right;
margin-right: 100px;
}
.rowLinkify {
cursor: pointer;
border-bottom: 1px solid lightgrey;
color: #555;
height: var(--row-height);
padding: 10px;
}
.rowLinkify:last-child {
border-bottom: none;
}
.rowLinkify:hover {
background-color: #f8f8f8;
}
.linkToAsset {
display: inline-block;
text-decoration: none;
}
.linkToModerate {
background-color: #e0e0e0;
padding: 10px 14px;
text-decoration: none;
color: black;
float: right;
margin-left: 15px;
}
.linkToModerate:hover {
background-color: #ccc;
}
.lede {
font-size: 0.9em;
color: #aaa;
}
.assetTitle {
color: #555;
text-decoration: none;
font-size: 1.2em;
font-weight: normal;
margin: 0;
}
.assetTitle:hover {
text-decoration: underline;
}
.widgetCount {
color: #555;
font-size: 1.3em;
font-weight: 400;
float: right;
margin-top: 7px;
}
@@ -2,22 +2,25 @@ import React, {Component} from 'react';
import {connect} from 'react-redux';
import styles from './style.css';
import {Wizard, WizardNav} from 'coral-ui';
import {Layout} from '../../components/ui/Layout';
import Layout from 'coral-admin/src/components/ui/Layout';
import {
nextStep,
previousStep,
goToStep,
nextStep,
submitUser,
checkInstall,
previousStep,
finishInstall,
submitSettings,
updateUserFormData,
updateSettingsFormData,
submitSettings,
submitUser,
checkInstall
updatePermittedDomains
} from '../../actions/install';
import InitialStep from './components/Steps/InitialStep';
import AddOrganizationName from './components/Steps/AddOrganizationName';
import CreateYourAccount from './components/Steps/CreateYourAccount';
import PermittedDomainsStep from './components/Steps/PermittedDomainsStep';
import FinalStep from './components/Steps/FinalStep';
class InstallContainer extends Component {
@@ -43,6 +46,7 @@ class InstallContainer extends Component {
<InitialStep/>
<AddOrganizationName/>
<CreateYourAccount/>
<PermittedDomainsStep/>
<FinalStep/>
</Wizard>
</div>
@@ -65,10 +69,14 @@ const mapStateToProps = state => ({
});
const mapDispatchToProps = dispatch => ({
checkInstall: next => dispatch(checkInstall(next)),
nextStep: () => dispatch(nextStep()),
previousStep: () => dispatch(previousStep()),
goToStep: step => dispatch(goToStep(step)),
previousStep: () => dispatch(previousStep()),
finishInstall: () => dispatch(finishInstall()),
checkInstall: next => dispatch(checkInstall(next)),
handleDomainsChange: value => {
dispatch(updatePermittedDomains(value));
},
handleSettingsChange: e => {
const {name, value} = e.currentTarget;
dispatch(updateSettingsFormData(name, value));
@@ -2,25 +2,26 @@ import React from 'react';
import styles from './style.css';
import {TextField, Button} from 'coral-ui';
const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const AddOrganizationName = props => {
const {handleSettingsChange, handleSettingsSubmit, install} = props;
return (
<div className={styles.step}>
<p>
Please tell us the name of your organization. This will appear in emails when
inviting new team members
</p>
<p>{lang.t('ADD_ORGANIZATION.DESCRIPTION')}</p>
<div className={styles.form}>
<form onSubmit={handleSettingsSubmit}>
<TextField
className={styles.TextField}
id="organizationName"
type="text"
label='Organization name'
label={lang.t('ADD_ORGANIZATION.LABEL')}
onChange={handleSettingsChange}
showErrors={install.showErrors}
errorMsg={install.errors.organizationName} />
<Button type="submit" cStyle='black' full>Save</Button>
<Button type="submit" cStyle='black' full>{lang.t('ADD_ORGANIZATION.SAVE')}</Button>
</form>
</div>
</div>
@@ -2,6 +2,10 @@ import React from 'react';
import styles from './style.css';
import {TextField, Button, Spinner} from 'coral-ui';
const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const InitialStep = props => {
const {handleUserChange, handleUserSubmit, install} = props;
return (
@@ -12,7 +16,7 @@ const InitialStep = props => {
className={styles.textField}
id="email"
type="email"
label='Email address'
label={lang.t('CREATE.EMAIL')}
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.email}
@@ -23,7 +27,7 @@ const InitialStep = props => {
className={styles.textField}
id="username"
type="text"
label='Username'
label={lang.t('CREATE.USERNAME')}
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.username}
@@ -33,7 +37,7 @@ const InitialStep = props => {
className={styles.textField}
id="password"
type="password"
label='Password'
label={lang.t('CREATE.PASSWORD')}
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.password}
@@ -43,7 +47,7 @@ const InitialStep = props => {
className={styles.textField}
id="confirmPassword"
type="password"
label='Confirm Password'
label={lang.t('CREATE.CONFIRM_PASSWORD')}
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.confirmPassword}
@@ -51,7 +55,7 @@ const InitialStep = props => {
{
!props.install.isLoading ?
<Button cStyle='black' type="submit" full>Save</Button>
<Button cStyle='black' type="submit" full>{lang.t('CREATE.SAVE')}</Button>
:
<Spinner />
}
@@ -3,16 +3,16 @@ import styles from './style.css';
import {Button} from 'coral-ui';
import {Link} from 'react-router';
const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const InitialStep = () => {
return (
<div className={`${styles.step} ${styles.finalStep}`}>
<p>
Thanks for installing Talk! We sent an email to verify your email
address. While you finish setting the account, you can start engaging
with your readers now.
</p>
<Button raised><Link to='/admin'>Launch Talk</Link></Button>
<Button cStyle='black' raised><a href="http://coralproject.net">Close this Installer</a></Button>
<p>{lang.t('FINAL.DESCRIPTION')}</p>
<Button raised><Link to='/admin'>{lang.t('FINAL.LAUNCH')}</Link></Button>
<Button cStyle='black' raised><a href="http://coralproject.net">{lang.t('FINAL.CLOSE')}</a></Button>
</div>
);
};
@@ -2,16 +2,16 @@ import React from 'react';
import styles from './style.css';
import {Button} from 'coral-ui';
const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const InitialStep = props => {
const {nextStep} = props;
return (
<div className={styles.step}>
<p>
The remainder of the Talk installation will take about ten minutes.
Once you complete the following two steps, you will have a free
installation and provision of Mongo and Redis.
</p>
<Button cStyle='green' onClick={nextStep} raised>Get Started</Button>
<p>{lang.t('INITIAL.DESCRIPTION')}</p>
<Button cStyle='green' onClick={nextStep} raised>{lang.t('INITIAL.SUBMIT')}</Button>
</div>
);
};
@@ -0,0 +1,31 @@
import React from 'react';
import styles from './style.css';
import {Button, Card} from 'coral-ui';
import TagsInput from 'react-tagsinput';
const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const PermittedDomainsStep = props => {
const {finishInstall, install, handleDomainsChange} = props;
const domains = install.data.settings.domains.whitelist;
return (
<div className={styles.step}>
<h3>{lang.t('PERMITTED_DOMAINS.TITLE')}</h3>
<Card className={styles.card}>
<p>{lang.t('PERMITTED_DOMAINS.DESCRIPTION')}</p>
<TagsInput
value={domains}
inputProps={{placeholder: 'URL'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => handleDomainsChange(tags)}
/>
</Card>
<Button cStyle='green' onClick={finishInstall} raised>{lang.t('PERMITTED_DOMAINS.SUBMIT')}</Button>
</div>
);
};
export default PermittedDomainsStep;
@@ -59,6 +59,12 @@
}
}
}
.card {
max-width: 500px;
margin: 20px auto;
text-align: left;
}
}
.finalStep {
@@ -0,0 +1,58 @@
{
"en": {
"INITIAL" : {
"DESCRIPTION": "Let's set up your Talk community in just a few short steps.",
"SUBMIT": "Get Started"
},
"ADD_ORGANIZATION": {
"DESCRIPTION": "Please tell us the name of your organization. This will appear in emails when inviting new team members.",
"LABEL": "Organization Name",
"SAVE": "Save"
},
"CREATE": {
"EMAIL": "Email address",
"USERNAME": "Username",
"PASSWORD": "Password",
"CONFIRM_PASSWORD": "Confirm Password",
"SAVE": "Save"
},
"PERMITTED_DOMAINS": {
"TITLE": "Permitted domains",
"DESCRIPTION": "Enter the domains you would like to permit for Talk, e.g. your local, staging and production environments (ex. localhost:3000, staging.domain.com, domain.com).",
"SUBMIT": "Finish install"
},
"FINAL": {
"DESCRIPTION": "Thanks for installing Talk! We sent an email to verify your email address. While you finish setting up the account, you can start engaging with your readers now.",
"LAUNCH": "Launch Talk",
"CLOSE": "Close this Installer"
}
},
"es": {
"INITIAL" : {
"DESCRIPTION": "Configuremos tu comunidad de Talk en sólo algunos pasos.",
"SUBMIT": "Empezá!"
},
"ADD_ORGANIZATION": {
"DESCRIPTION": "Por favor, dinos el nombre de tu organización. Este aparecerá en los emails cuando invites nuevos miembros.",
"LABEL": "Nombre de la Organización",
"SAVE": "Guardar"
},
"CREATE": {
"EMAIL": "Dirección de E-Mail",
"USERNAME": "Usuario",
"PASSWORD": "Contraseña",
"CONFIRM_PASSWORD": "Confirmar contraseña",
"SAVE": "Guardar"
},
"PERMITTED_DOMAINS": {
"TITLE": "Dominios Permitidos",
"DESCRIPTION": "Agrega dominios permitidos a Talk, e.g. tu localhost, staging y ambientes de production (ex. localhost:3000, staging.domain.com, domain.com).",
"SUBMIT": "Finalizar instalación"
},
"FINAL": {
"DESCRIPTION": "Gracias por instalar Talk! Te enviamos un email para verificar tu identidad. Mientras se termina de configurar la cuenta, ya puedes empezar a interactuar con tus lectores",
"LAUNCH": "Lanzar Talk",
"CLOSE": "Cerrar este instalador"
}
}
}
@@ -1,34 +1,54 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Layout} from '../components/ui/Layout';
import {checkLogin, logout} from '../actions/auth';
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 {PermissionRequired} from '../components/PermissionRequired';
import AdminLogin from '../components/AdminLogin';
class LayoutContainer extends Component {
componentWillMount () {
const {checkLogin} = this.props;
checkLogin().then(() => {
if (!this.props.auth.isAdmin) {
location.href = '/admin/login';
}
});
const {checkLogin, fetchConfig} = this.props;
checkLogin();
fetchConfig();
}
render () {
const {isAdmin, loggedIn, loadingUser} = this.props.auth;
const {
isAdmin,
loggedIn,
loadingUser,
loginError,
loginMaxExceeded,
passwordRequestSuccess
} = this.props.auth;
const {handleLogout, TALK_RECAPTCHA_PUBLIC} = this.props;
if (loadingUser) { return <FullLoading />; }
if (!isAdmin) { return <PermissionRequired />; }
if (isAdmin && loggedIn) { return <Layout {...this.props} />; }
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} />; }
return <FullLoading />;
}
}
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()),
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,7 +110,7 @@ class ModerationContainer extends Component {
}
render () {
const {data, moderation, settings, assets, ...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;
@@ -54,7 +121,6 @@ class ModerationContainer extends Component {
}
if (data.error) {
console.log(data);
return <div>Error</div>;
}
@@ -66,6 +132,20 @@ 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>
<ModerationHeader asset={asset} />
@@ -74,22 +154,37 @@ class ModerationContainer extends Component {
premodCount={data.premodCount}
rejectedCount={data.rejectedCount}
flaggedCount={data.flaggedCount}
selectSort={this.selectSort}
sort={this.state.sort}
/>
<ModerationQueue
data={data}
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>
);
}
@@ -107,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,27 +1,30 @@
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 = ({activeTab = 'premod', ...props}) => {
const areComments = props.data[activeTab].length;
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}}>
{
areComments
? props.data[activeTab].map((comment, i) => {
comments.length
? comments.map((comment, i) => {
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
return <Comment
key={i}
index={i}
comment={comment}
commentType={activeTab}
selected={i === selectedIndex}
suspectWords={props.suspectWords}
bannedWords={props.bannedWords}
actions={actionsMap[status]}
showBanUserDialog={props.showBanUserDialog}
acceptComment={props.acceptComment}
@@ -32,17 +35,26 @@ const ModerationQueue = ({activeTab = 'premod', ...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 = {
data: PropTypes.object.isRequired,
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
showBanUserDialog: PropTypes.func.isRequired,
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired
showBanUserDialog: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
comments: PropTypes.array.isRequired
};
export default ModerationQueue;
@@ -6,8 +6,10 @@ import {Link} from 'react-router';
import styles from './styles.css';
import {Icon} from 'coral-ui';
import ActionButton from '../../../components/ActionButton';
import FlagBox from './FlagBox';
import CommentType from './CommentType';
import ActionButton from 'coral-admin/src/components/ActionButton';
import BanUserButton from 'coral-admin/src/components/BanUserButton';
const linkify = new Linkify();
@@ -17,50 +19,55 @@ const lang = new I18n(translations);
const Comment = ({actions = [], ...props}) => {
const links = linkify.getMatches(props.comment.body);
const linkText = links ? links.map(link => link.raw) : [];
const actionSummaries = props.comment.action_summaries;
return (
<li tabIndex={props.index}
className={`mdl-card mdl-shadow--2dp ${styles.Comment} ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span>{props.comment.user.name}</span>
<li tabIndex={props.index} className={`mdl-card ${props.selected ? 'mdl-shadow--8dp' : 'mdl-shadow--2dp'} ${styles.Comment} ${styles.listItem} ${props.selected ? styles.selected : ''}`}>
<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('-', '_'))}
</span>
{props.comment.action_summaries ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
<BanUserButton user={props.comment.user} onClick={() => props.showBanUserDialog(props.comment.user, props.comment.id, props.comment.status !== 'REJECTED')} />
<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})}
showBanUserDialog={() => props.showBanUserDialog(props.comment.user, props.comment.id)}
/>
)}
</div>
{props.comment.user.status === 'banned' ?
<span className={styles.banned}>
<Icon name='error_outline'/>
{lang.t('comment.banned_user')}
</span>
: null}
: null}
</div>
</div>
{!props.currentAsset && (
<div className={styles.moderateArticle}>
Article: {props.comment.asset.title} <Link to={`/admin/moderate/${props.comment.asset.id}`}>Moderate Article</Link>
Story: {props.comment.asset.title}
{!props.currentAsset && (
<Link to={`/admin/moderate/${props.comment.asset.id}`}>Moderate &rarr;</Link>
)}
</div>
<div className={styles.itemBody}>
<p className={styles.body}>
<Highlighter
searchWords={[...props.suspectWords, ...props.bannedWords, ...linkText]}
textToHighlight={props.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={props.comment.user}
acceptComment={() => props.acceptComment({commentId: props.comment.id})}
rejectComment={() => props.rejectComment({commentId: props.comment.id})}
/>
)}
</div>
</div>
</div>
)}
<div className={styles.itemBody}>
<p className={styles.body}>
<Linkify component='span' properties={{style: linkStyles}}>
<Highlighter searchWords={props.suspectWords} textToHighlight={props.comment.body}/>
</Linkify>
</p>
</div>
{actionSummaries && <FlagBox actionSummaries={actionSummaries} />}
</li>
@@ -71,8 +78,8 @@ 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,
isActive: PropTypes.bool.isRequired,
comment: PropTypes.shape({
body: PropTypes.string.isRequired,
action_summaries: PropTypes.array,
@@ -87,9 +94,4 @@ Comment.propTypes = {
})
};
const linkStyles = {
backgroundColor: 'rgb(255, 219, 135)',
padding: '1px 2px'
};
export default Comment;
@@ -0,0 +1,33 @@
.commentType {
position: absolute;
right: 15px;
top: 11px;
color: white;
background: grey;
height: 32px;
box-sizing: border-box;
line-height: 29px;
padding: 2px 8px 2px 26px;
border-radius: 2px;
font-size: 12px;
i {
font-size: 14px;
position: absolute;
left: 6px;
top: 8px;
margin: 0;
}
&.premod {
background: #063B9A;
}
&.flagged {
background: #d03235;
}
&.no-type {
display: none;
}
}
@@ -0,0 +1,30 @@
import React, {PropTypes} from 'react';
import styles from './CommentType.css';
import {Icon} from 'coral-ui';
const CommentType = props => {
const typeData = getTypeData(props.type);
return (
<span className={`${styles.commentType} ${styles[typeData.className]}`}>
<Icon name={typeData.icon}/>{typeData.text}
</span>
);
};
const getTypeData = type => {
switch (type) {
case 'premod':
return {icon: 'query_builder', text: 'Pre-Mod', className: 'premod'};
case 'flagged':
return {icon: 'flag', text: 'Flagged', className: 'flagged'};
default:
return {icon: 'flag', className: 'no-type'};
}
};
CommentType.propTypes = {
type: PropTypes.string.isRequired
};
export default CommentType;
@@ -0,0 +1,55 @@
.flagBox {
border-top: 1px solid rgba(66, 66, 66, 0.12);
.container {
padding: 0 14px;
}
.detail {
padding: 0 20px 16px;
ul {
padding: 0;
list-style: none;
font-size: 12px;
font-weight: 500;
}
}
.header {
position: relative;
.moreDetail {
float: right;
font-size: 12px;
font-weight: 500;
margin-right: 10px;
margin-top: 8px;
color: black;
&:hover {
opacity: 0.9;
cursor: pointer;
}
}
i {
vertical-align: middle;
font-size: 12px;
}
ul {
vertical-align: middle;
list-style: none;
display: inline-block;
padding: 0;
margin-left: 10px;
font-size: 12px;
}
}
h3 {
vertical-align: middle;
margin: 0;
font-weight: 500;
display: inline-block;
margin-left: 7px;
font-size: 12px;
}
}
@@ -1,16 +1,47 @@
import React, {PropTypes} from 'react';
import styles from './styles.css';
import React, {Component, PropTypes} from 'react';
import {Icon} from 'coral-ui';
import styles from './FlagBox.css';
const FlagBox = props => (
<div className={styles.flagBox}>
<h3>Flags:</h3>
<ul>
{props.actionSummaries.map((action, i) =>
<li key={i}>{!action.reason ? <i>No reason provided</i> : action.reason} (<strong>{action.count}</strong>)</li>
)}
</ul>
</div>
);
class FlagBox extends Component {
constructor () {
super();
this.state = {
showDetail: false
};
}
toggleDetail = () => {
this.setState((state) => ({
showDetail: !state.showDetail
}));
}
render() {
const {props} = this;
return (
<div className={styles.flagBox}>
<div className={styles.container}>
<div className={styles.header}>
<Icon name='flag'/><h3>Flags ({props.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>
)}
</ul>
{/* <a onClick={this.toggleDetail} className={styles.moreDetail}>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>)}
</div>
</div>
);
}
}
FlagBox.propTypes = {
actionSummaries: PropTypes.array.isRequired
@@ -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,9 +10,9 @@ 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>
</div>
@@ -1,30 +1,58 @@
import React, {PropTypes} from 'react';
import React, { PropTypes } from 'react';
import CommentCount from './CommentCount';
import styles from './styles.css';
import { SelectField, Option } from 'react-mdl-selectfield';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
import {Link} from 'react-router';
import { Link } from 'react-router';
const lang = new I18n(translations);
const ModerationMenu = ({asset, premodCount, rejectedCount, flaggedCount}) => {
const premodPath = asset ? `/admin/moderate/premod/${asset.id}` : '/admin/moderate/premod';
const rejectPath = asset ? `/admin/moderate/rejected/${asset.id}` : '/admin/moderate/rejected';
const flagPath = asset ? `/admin/moderate/flagged/${asset.id}` : '/admin/moderate/flagged';
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';
return (
<div className='mdl-tabs'>
<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}>
<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}>
<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>
<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={sort}
onChange={sort => selectSort(sort)}>
<Option value={'REVERSE_CHRONOLOGICAL'}>Newest First</Option>
<Option value={'CHRONOLOGICAL'}>Oldest First</Option>
</SelectField>
</div>
</div>
);
@@ -8,6 +8,12 @@
.tabBar {
background-color: rgba(44, 44, 44, 0.89);
z-index: 5;
display: flex;
justify-content: space-between;
}
.tabBarPadding {
width: 150px;
}
.tab {
@@ -78,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 {
@@ -108,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 {
@@ -130,7 +143,7 @@ span {
display: none;
}
&.singleView .listItem.activeItem {
&.singleView .listItem.selected {
display: block;
height: 100%;
font-size: 1.5em;
@@ -141,7 +154,6 @@ span {
position: fixed;
bottom: 60px;
left: 25%;
margin: 0 auto;
display: flex;
justify-content: space-around;
width: 50%;
@@ -156,16 +168,20 @@ span {
.listItem {
border-bottom: 1px solid #e0e0e0;
font-size: 16px;
font-size: 18px;
width: 100%;
max-width: 660px;
max-width: 700px;
min-width: 400px;
margin: 0 auto;
padding: 16px 14px;
position: relative;
transition: box-shadow 200ms;
margin-top: 0;
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);
@@ -175,6 +191,11 @@ span {
border-bottom: none;
}
&.selected {
max-width: 720px;
max-height: 410px;
}
.context {
a {
color: #f36451;
@@ -184,11 +205,8 @@ span {
}
.sideActions {
position: absolute;
right: 0;
height: 100%;
top: 0;
padding: 40px 18px;
box-sizing: border-box;
}
@@ -198,15 +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;
font-size: 14px;
line-height: 1.5;
font-weight: 300;
}
.avatar {
@@ -220,9 +244,11 @@ span {
}
.created {
color: #666;
font-size: 13px;
margin-left: 40px;
color: #262626;
font-size: 14px;
margin-left: 15px;
line-height: 1px;
font-weight: 300;
}
.actionButton {
@@ -233,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 {
@@ -304,20 +331,29 @@ span {
}
.Comment {
.moderateArticle {
font-size: 12px;
font-size: 14px;
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
max-width: 500px;
a {
display: inline-block;
color: #679af3;
color: #063b9a;
text-decoration: none;
font-size: 1em;
font-weight: 400;
font-weight: 500;
letter-spacing: .5px;
font-size: 12px;
margin-left: 10px;
font-size: 13px;
margin-left: 5px;
padding-bottom: 0px;
border-bottom: solid 1px;
line-height: 16px;
&:hover {
text-decoration: underline;
opacity: .9;
cursor: pointer;
}
@@ -325,12 +361,60 @@ span {
}
}
.flagBox {
max-width: 480px;
border-top: 1px solid rgba(66, 66, 66, 0.12);
h3 {
font-size: 14px;
margin: 0;
font-weight: 500;
.selectField {
position: relative;
width: 140px;
height: 36px;
top: 5px;
margin-right: 10px;
background: #FFF;
padding: 10px 15px;
box-sizing: border-box;
border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
> div {
padding: 0;
}
i {
position: absolute;
top: 7px;
right: 7px;
}
input {
padding: 0;
font-size: 13px;
letter-spacing: 0.7px;
font-weight: 400;
}
label {
top: -4px;
}
&:hover {
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;
}
@@ -1,13 +1,14 @@
export const actionsMap = {
PREMOD: ['REJECT', 'APPROVE', 'BAN'],
FLAGGED: ['REJECT', 'APPROVE', 'BAN'],
REJECTED: ['APPROVE']
PREMOD: ['APPROVE', 'REJECT'],
FLAGGED: ['APPROVE', 'REJECT'],
REJECTED: ['APPROVE', 'REJECTED']
};
export const menuActionsMap = {
'REJECT': {status: 'REJECTED', icon: 'close', key: 'r'},
'APPROVE': {status: 'ACCEPTED', icon: 'done', key: 't'},
'FLAGGED': {status: 'FLAGGED', icon: 'flag', filter: 'Untouched'},
'BAN': {status: 'BANNED', icon: 'not interested'},
'REJECT': {status: 'REJECTED', text: 'Reject', icon: 'close', key: 'r'},
'REJECTED': {status: 'REJECTED', text: 'Rejected', icon: 'close'},
'APPROVE': {status: 'ACCEPTED', text: 'Approve', icon: 'done', key: 't'},
'FLAGGED': {status: 'FLAGGED', text: 'Flag', icon: 'flag', filter: 'Untouched'},
'BAN': {status: 'BANNED', text: 'Ban User', icon: 'not interested'},
'': {icon: 'done'}
};
@@ -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);
@@ -0,0 +1,13 @@
fragment metrics on Asset {
id
title
url
author
created_at
commentCount
action_summaries {
type: __typename
actionCount
actionableItemCount
}
}
@@ -22,7 +22,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 +50,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
};
}
}
});
}
})
+66 -18
View File
@@ -1,30 +1,78 @@
import {graphql} from 'react-apollo';
import MOST_FLAGS from './mostFlags.graphql';
import MOD_QUEUE_QUERY from './modQueueQuery.graphql';
export const mostFlags = graphql(MOST_FLAGS, {
options: () => {
// currently hard-coded per Greg's advice
const fiveMinutesAgo = new Date();
fiveMinutesAgo.setMinutes(fiveMinutesAgo.getMinutes() - 305);
return {
variables: {
sort: 'FLAG',
from: fiveMinutesAgo.toISOString(),
to: new Date().toISOString()
}
};
}
});
import MOD_QUEUE_LOAD_MORE from './loadMore.graphql';
import METRICS from './metricsQuery.graphql';
export const modQueueQuery = graphql(MOD_QUEUE_QUERY, {
options: ({params: {id = null}}) => {
return {
variables: {
asset_id: id
asset_id: id,
sort: 'REVERSE_CHRONOLOGICAL'
}
};
},
props: ({ownProps: {params: {id = null}}, data}) => ({
data,
modQueueResort: modQueueResort(id, data.fetchMore),
loadMore: loadMore(data.fetchMore)
})
});
export const getMetrics = graphql(METRICS, {
options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => {
return {
variables: {
from: dashboardWindowStart,
to: dashboardWindowEnd
}
};
}
});
export const loadMore = (fetchMore) => ({limit, 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:{data:{comments}}}) => {
return {
...oldData,
[tab]: [
...oldData[tab],
...comments
]
};
}
});
};
export const modQueueResort = (id, fetchMore) => (sort) => {
return fetchMore({
query: MOD_QUEUE_QUERY,
variables: {
asset_id: id,
sort
},
updateQuery: (oldData, {fetchMoreResult:{data}}) => data
});
};
@@ -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
}
}
}
}
@@ -0,0 +1,10 @@
#import "../fragments/assetMetricsView.graphql"
query Metrics ($from: Date!, $to: Date!) {
assetsByFlag: assetMetrics(from: $from, to: $to, sort: FLAG) {
...metrics
}
assetsByActivity: assetMetrics(from: $from, to: $to, sort: ACTIVITY) {
...metrics
}
}
@@ -1,16 +1,18 @@
#import "../fragments/commentView.graphql"
query ModQueue ($asset_id: ID) {
query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
premod: comments(query: {
statuses: [PREMOD],
asset_id: $asset_id
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
flagged: comments(query: {
action_type: FLAG,
asset_id: $asset_id,
statuses: [NONE, PREMOD]
statuses: [NONE, PREMOD],
sort: $sort
}) {
...commentView
action_summaries {
@@ -22,7 +24,8 @@ query ModQueue ($asset_id: ID) {
}
rejected: comments(query: {
statuses: [REJECTED],
asset_id: $asset_id
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
@@ -1,14 +0,0 @@
query Metrics ($from: Date!, $to: Date!, $sort: ACTION_TYPE!) {
metrics(from: $from, to: $to, sort: $sort) {
id
title
url
commentCount
author
created_at
action_summaries {
actionCount
actionableItemCount
}
}
}
+3
View File
@@ -7,6 +7,9 @@ import store from './services/store';
import App from './components/App';
import 'react-mdl/extra/material.css';
import 'react-mdl/extra/material.js';
render(
<ApolloProvider client={client} store={store}>
<App />
+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()));
};
+18 -1
View File
@@ -4,7 +4,10 @@ import * as actions from '../constants/auth';
const initialState = Map({
loggedIn: false,
user: null,
isAdmin: false
isAdmin: false,
loginError: null,
loginMaxExceeded: false,
passwordRequestSuccess: null
});
export default function auth (state = initialState, action) {
@@ -25,6 +28,20 @@ export default function auth (state = initialState, action) {
.set('user', action.user);
case actions.LOGOUT_SUCCESS:
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;
}
+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
};
+12 -2
View File
@@ -1,4 +1,4 @@
import {Map} from 'immutable';
import {Map, List} from 'immutable';
import * as actions from '../constants/install';
@@ -6,7 +6,10 @@ const initialState = Map({
isLoading: false,
data: Map({
settings: Map({
organizationName: ''
organizationName: '',
domains: Map({
whitelist: List()
})
}),
user: Map({
username: '',
@@ -33,6 +36,10 @@ const initialState = Map({
{
text: '2. Create your account',
step: 2
},
{
text: '3. Domain Whitelist',
step: 3
}],
installRequest: null,
installRequestError: null,
@@ -50,6 +57,9 @@ export default function install (state = initialState, action) {
case actions.GO_TO_STEP:
return state
.set('step', action.step);
case actions.UPDATE_PERMITTED_DOMAINS_SETTINGS:
return state
.setIn(['data', 'settings', 'domains', 'whitelist'], action.value);
case actions.UPDATE_FORMDATA_SETTINGS:
return state
.setIn(['data', 'settings', action.name], action.value);
@@ -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:
+13 -2
View File
@@ -1,11 +1,22 @@
import {Map, List} from 'immutable';
import * as actions from '../actions/settings';
// this is initialized here because
// currently you have to reload the dashboard to get new stats
// cleaner updates are planned in the future.
// TODO: if there are more than two fields for the dashboard being created here,
// please create a new reducer specifically for the Dashboard.
const DASHBOARD_WINDOW_MINUTES = 5;
let then = new Date();
then.setMinutes(then.getMinutes() - DASHBOARD_WINDOW_MINUTES);
const initialState = Map({
wordlist: Map({
banned: List(),
suspect: List()
}),
dashboardWindowStart: then.toISOString(),
dashboardWindowEnd: new Date().toISOString(),
domains: Map({
whitelist: List()
}),
@@ -22,7 +33,7 @@ export default function settings (state = initialState, action) {
.set('fetchSettingsError', null);
case actions.SETTINGS_RECEIVED:
return state.merge({
fetchingSettings: null,
fetchingSettings: false,
fetchSettingsError: null,
...action.settings
});
@@ -32,7 +43,7 @@ export default function settings (state = initialState, action) {
.set('fetchSettingsError', action.error);
case actions.SETTINGS_UPDATED:
return state.merge({
fetchingSettings: null,
fetchingSettings: false,
fetchSettingsError: null,
...action.settings
});
@@ -2,6 +2,7 @@ import ApolloClient, {addTypename} from 'apollo-client';
import getNetworkInterface from './transport';
export const client = new ApolloClient({
addTypename: true,
queryTransformer: addTypename,
dataIdFromObject: (result) => {
if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle
+101 -49
View File
@@ -1,5 +1,9 @@
{
"en": {
"errors": {
"NOT_AUTHORIZED": "Your username or password is not recognized by our system.",
"LOGIN_MAXIMUM_EXCEEDED": "You have made too many unsuccessful password attempts. Please wait."
},
"community": {
"username_and_email": "Username and Email",
"account_creation_date": "Account Creation Date",
@@ -50,6 +54,10 @@
"copy": "Copy to Clipboard"
},
"configure": {
"closed-stream-settings": "Closed Stream Message",
"stream-settings": "Stream Settings",
"moderation-settings": "Moderation Settings",
"tech-settings": "Tech Settings",
"custom-css-url": "Custom CSS URL",
"custom-css-url-desc": "URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external.",
"dashboard": "Dashboard",
@@ -57,13 +65,13 @@
"enable-pre-moderation-text": "Moderators must approve any comment before it is published.",
"require-email-verification": "Require Email Verification",
"require-email-verification-text": "New Users must verify their email before commenting",
"include-comment-stream": "Include Comment Stream Description for Readers.",
"include-comment-stream": "Include Comment Stream Description for Readers",
"include-comment-stream-desc": "Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc.",
"include-text": "Include your text here.",
"enable-premod-links": "Pre-Moderate Comments Containing Links",
"enable-premod-links-text": "Moderators must approve any comment containing a link before its published.",
"comment-settings": "Settings",
"embed-comment-stream": "Embed Stream",
"banned-word-header": "Write the banned words list",
"suspect-word-header": "Write the suspect words list",
"banned-word-text": "Comments which contain these words or phrases (not case-sensitive) will be automatically removed from the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list.",
"suspect-word-text": "Comments which contain these words or phrases (not case-sensitive) will be highlighted in the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list.",
"wordlist": "Banned Words",
@@ -74,8 +82,8 @@
"moderate": "Moderate",
"configure": "Configure",
"community": "Community",
"streams": "Streams",
"closed-comments-desc": "Write a message for closed threads",
"stories": "Stories",
"closed-comments-desc": "Write a message to be displayed when when your comment stream is closed and no longer accepting comments.",
"closed-comments-label": "Write a message...",
"hours": "Hours",
"days": "Days",
@@ -83,10 +91,10 @@
"close-after": "Close comments after",
"comment-count-header": "Limit Comment Length",
"comment-count-text-pre": "Comments will be limited to ",
"comment-count-text-post": " characters.",
"comment-count-text-post": " characters",
"comment-count-error": "Please enter a valid number.",
"domain-list-title": "Domain Whitelist",
"domain-list-text": "Some instructions on how to type the urls."
"domain-list-title": "Permitted Domains",
"domain-list-text": "Enter the domains you would like to permit for Talk, e.g. your local, staging and production environments (ex. localhost:3000, staging.domain.com, domain.com)."
},
"bandialog": {
"ban_user": "Ban User?",
@@ -110,8 +118,13 @@
"write_message": "Write a message"
},
"dashboard": {
"next-update": "{0} minutes until next update.",
"auto-update": "Data automatically updates every five minutes or when you Reload.",
"no_flags": "There have been no flags in the last 5 minutes! Hooray!",
"comment_count": "Comments"
"no_likes": "There have been no likes in the last 5 minutes. All quiet.",
"flags": "Flags",
"no_activity": "There haven't been any comments anywhere in the last five minutes.",
"comment_count": "comments"
},
"streams": {
"empty_result": "No assets match this search. Maybe try widening your search?",
@@ -126,25 +139,57 @@
"sort-by": "Sort By",
"open": "Open",
"closed": "Closed",
"article": "Article",
"article": "Story",
"pubdate": "Publication Date",
"status": "Status"
"status": "Stream Status"
}
},
"es": {
"errors": {
"NOT_AUTHORIZED": "Acción no autorizada.",
"LOGIN_MAXIMUM_EXCEEDED": "Ha realizado demasiados intentos fallidos de colocar la contraseña. Por favor espere."
},
"community": {
"username_and_email": "Usuario y E-mail",
"account_creation_date": "Fecha de creación de la cuenta",
"newsroom_role": "Rol en la redacción",
"admin": "Administrador",
"moderator": "Moderador",
"admin": "Administradora",
"moderator": "Moderadora",
"role": "Seleccionar rol...",
"no-results": "No se encontraron usuarios con ese nombre de usuario o correo electronico.",
"no-results": "No se encontraron usuarixs con ese nombre de usuario o e-mail.",
"status": "Estado",
"select-status": "Seleccionar estado...",
"active": "Activa",
"banned": "Suspendido",
"banned-user": "Usuario Suspendido",
"loading": "Cargando resultados",
"flaggedaccounts": "Nombres de Usuario Reportados",
"people": "Gente",
"no-flagged-accounts": "No hay ninguna cuenta reportada.",
"I don't like this username": "No me gusta ese nombre de usuario",
"This user is impersonating": "Suplantación",
"This looks like an ad/marketing": "Spam/Propaganda",
"This username is offensive": "Ofensivo",
"Other": "Otros",
"ban_user": "Quieres suspender al Usuario?",
"are_you_sure": "Estas segura que quieres suspender a {0}?",
"note": "Nota: Suspender a este usuario no le va a permitir (al usuario) borrar ni editar ni comentar.",
"cancel": "Cancelar",
"yes_ban_user": "Si, Suspendan el usuario"
},
"suspenduser": {
"title": "Suspendiendo un usuario",
"title_0": "¿Esta queriendo suspender un usuario?",
"description_0": "¿Le gustaría suspender a esta usuaria temporalmente devido a su nombre de usuario? Si lo hace sus comentarios serán escondidos temporalmente hasta que el usuario pueda cambiar su nombre.",
"title_1": "Enviarle un correo al usuario informandole que su cuenta fue suspendida",
"description_1": "Si suspende a este usuario, su cuenta va a ser deshabilitada y todos sus comentarios escondidos del sitio.",
"no_cancel": "No, cancelar",
"yes_suspend": "Si, suspender",
"send": "Enviar",
"username": "nombre de usuario",
"email_subject": "Su cuenta ha sido suspendida temporalmente",
"email": "Una persona de la comunidad recientemente marcó su nombre de usuario para ser revisado. Por su contenido, el nombre de usuario ha sido rechazado. Esto quiere decir que no puede comentar, gustar o marcar contenido hasta que modifique su nombre de usuario. Por favor, envienos un correo si tiene alguna pregunta o comentario.",
"write_message": "Escribir un mensaje",
"loading": "Cargando resultados"
},
"modqueue": {
@@ -164,37 +209,41 @@
"banned_user": "Usuario Suspendido"
},
"user": {
"user_bio": "",
"bio_flags": "",
"username_flags": ""
"user_bio": "marcas para este usuario",
"bio_flags": "marcas para esta biografia",
"username_flags": "marcas para este nombre de usuario"
},
"configure": {
"closed-stream-settings": "Mensaje a enviar cuando los comentarios están cerrados en el artículo",
"stream-settings": "Configuración de Comentarios",
"moderation-settings": "Configuración de Moderación",
"tech-settings": "Configuración Técnica",
"custom-css-url": "URL CSS a medida",
"custom-css-url-desc": "URL de una hoja de estilo que va a sobrescribir los estilos por defecto de Embed Stream. Puede ser interna o externa.",
"custom-css-url-desc": "URL de una hoja de estilo que va a sobrescribir los estilos por defecto del hilo de comentarios. Puede ser interna o externa.",
"dashboard": "Panel",
"enable-pre-moderation": "Habilitar pre-moderación",
"enable-pre-moderation-text": "Los moderadores deben aprobar cada comentario antes de que sea publicado.",
"require-email-verification": "Necesita confirmación de correo",
"require-email-verification-text": "Nuevos usuarios deben verificar sus correos antes de comentar",
"require-email-verification": "Necesita confirmación de e-mail",
"require-email-verification-text": "Nuevos usuarios deben verificar sus e-mails antes de comentar",
"include-comment-stream": "Incluir la Descripción a un Hilo de Comentario para los y las Lectoras.",
"include-comment-stream-desc": "Escribir un mensaje que será agregado a la parte de arriba del tu hilo de comentarios. Por ejemplo, un tema, guias de comunidad, etc.",
"include-text": "Incluir tu texto aqui.",
"comment-settings": "Configuración de Comentarios",
"embed-comment-stream": "Colocar Hilo de Comentarios",
"wordlist": "Palabras Suspendidas y Suspechosas",
"banned-word-header": "Escribir las palabras no permitidas",
"suspect-word-header": "Write the suspect words list",
"banned-word-text": "Comentarios que contengan estas palabras o frases, no separadas por comas y en mayusculas o minusuculas, serán automaticamente separadas de los comentarios publicados.",
"suspect-word-text": "Comments which contain these words or phrases (not case-sensitive) will be highlighted in the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list.",
"banned-words-title": "Banned words list",
"suspect-words-title": "Suspect words list",
"enable-premod-links": "Pre-Moderar Commentarios que contienen Enlaces",
"enable-premod-links-text": "Los y las Moderadoras deben aprobar cualquier comentario que contengan links antes de su publicación.",
"wordlist": "Palabras Suspendidas y Sospechosas",
"banned-word-text": "Comentarios que contengan estas palabras o frases, no separadas por comas y en mayusculas o minusuculas, serán automaticamente marcadas para separar los comentarios publicados.",
"suspect-word-text": "Comentarios que contengan estas palabras o frases, considerando mayusculas y minusculas, serán automaticamente destacadas en los comentarios publicados. Escribir una palabra y apretar Enter o Tabulador para agergarla. Opcionalmente pegar una lista separada por coma.",
"banned-words-title": "Lista de palabras prohibidas",
"suspect-words-title": "Lista de palabras sospechosas",
"save-changes": "Guardar Cambios",
"copy-and-paste": "Copiar y pegar el código de más abajo en tu CMS para colocar la caja de comentarios en tus articulos",
"moderate": "Moderar",
"configure": "Configurar",
"community": "Comunidad",
"streams": "Streams",
"closed-comments-desc": "Escribe un mensaje para cuando los comentarios se encuentran cerrados",
"stories": "Artículos",
"closed-comments-desc": "Escribe un mensaje que será mostrado cuando los comentarios estén cerrados y no se acepten más comentarios.",
"closed-comments-label": "Escribe un mensaje...",
"never": "Nunca",
"hours": "Horas",
@@ -206,38 +255,41 @@
"comment-count-text-post": " caracteres",
"comment-count-error": "Por favor escribe un número válido.",
"domain-list-title": "Lista de Dominios Permitidos",
"domain-list-text": "Instrucciones de como ingresar las URLs."
"domain-list-text": "Agrega dominios permitidos a Talk, por ejemplo tu localhost, staging y ambientes de producción (ej. localhost:3000, staging.domain.com, domain.com)."
},
"embedlink": {
"copy": "Copiar"
},
"bandialog": {
"ban_user": "Quieres suspender el Usuario?",
"are_you_sure": "Estas segura que quieres suspender a {props.author.username}?",
"note": "Nota: Suspender este usuario también va a colocar este comentario en la cola de Rechazados.",
"ban_user": "¿Quieres suspender el Usuario?",
"are_you_sure": "¿Estás segura que quieres suspender a {0}?",
"note": "Nota: Suspender a este usuario también va a colocar este comentario en la cola de Rechazados.",
"cancel": "Cancelar",
"yes_ban_user": "Si, Suspendan el usuario"
},
"dashbord": {
"next-update": "{0} minutos hasta la siguiente actualización.",
"auto-update": "Los datos se actualizan automaticamente cada 5 minutos o cuando recargas.",
"no_flags": "¡Nadie ha marcado nada en los últimos 5 minutos! ¡Bravo!",
"comment_count": "Comentarios"
"no_likes": "A nadie le ha gustado algún comentario en los últimos 5 minutos. Todo tranquilo.",
"flags": "Marcados",
"no_activity": "No hubo comentarios en los ultimos 5 minutos",
"comment_count": "comentarios"
},
"streams": {
"empty_result": "No se encuentro articulo con esta busqueda. Tal vez extender la busqueda?",
"search": "",
"filter-streams": "",
"stream-status": "",
"all": "",
"open": "",
"closed": "",
"newest": "",
"oldest": "",
"sort-by": "",
"open": "",
"closed": "",
"article": "",
"pubdate": "",
"status": ""
"empty_result": "No se encuentro articulo con esta busqueda. ¿Tal vez puedas extender la busqueda?",
"search": "buscar",
"filter-streams": "Filtrar Hilos de Comentarios",
"stream-status": "Estado del Hilo de Comentarios",
"all": "todxs",
"open": "abrir",
"closed": "cerrado",
"newest": "más nuevoß",
"oldest": "más viejo",
"sort-by": "ordenar por",
"article": "artículo",
"pubdate": "Fecha de Pblicación",
"status": "Estado"
}
}
}
@@ -34,6 +34,18 @@ export default ({handleChange, handleApply, changed, updateQuestionBoxContent, .
description: lang.t('configureCommentStream.enablePremodDescription')
}} />
</li>
<li>
<Checkbox
className={styles.checkbox}
cStyle={changed ? 'green' : 'darkGrey'}
name="premodLinks"
onChange={handleChange}
defaultChecked={props.premodLinks}
info={{
title: lang.t('configureCommentStream.enablePremodLinks'),
description: lang.t('configureCommentStream.enablePremodLinksDescription')
}} />
</li>
<li>
<Checkbox
className={styles.checkbox}
@@ -31,13 +31,14 @@ class ConfigureStreamContainer extends Component {
const questionBoxEnable = elements.qboxenable.checked;
const questionBoxContent = elements.qboxcontent.value;
// const premodLinks = elements.premodLinks.checked;
const premodLinksEnable = elements.premodLinks.checked;
const {changed} = this.state;
const newConfig = {
moderation: premod ? 'PRE' : 'POST',
questionBoxEnable,
questionBoxContent
questionBoxContent,
premodLinksEnable
};
if (changed) {
@@ -77,10 +78,9 @@ class ConfigureStreamContainer extends Component {
}
render () {
const status = this.props.asset.closedAt === null ? 'open' : 'closed';
const premod = this.props.asset.settings.moderation === 'PRE';
const questionBoxEnable = this.props.asset.settings.questionBoxEnable;
const questionBoxContent = this.props.asset.settings.questionBoxContent;
const {settings, closedAt} = this.props.asset;
const status = closedAt === null ? 'open' : 'closed';
const premod = settings.moderation === 'PRE';
return (
<div>
@@ -88,11 +88,11 @@ class ConfigureStreamContainer extends Component {
handleChange={this.handleChange}
handleApply={this.handleApply}
changed={this.state.changed}
premodLinks={false}
premodLinks={settings.premodLinks}
premod={premod}
updateQuestionBoxContent={this.updateQuestionBoxContent}
questionBoxEnable={questionBoxEnable}
questionBoxContent={questionBoxContent}
questionBoxEnable={settings.questionBoxEnable}
questionBoxContent={settings.questionBoxContent}
/>
<hr />
<h3>{status === 'open' ? 'Close' : 'Open'} Comment Stream</h3>
+2 -2
View File
@@ -20,8 +20,8 @@
"description": "Como Administrador/a puedes modificar las opciones de los comentarios en este artículo",
"enablePremod": "Activar Pre Moderación",
"enablePremodDescription": "Los y las Moderadoras deben aprobar cualquier comentario antes de su publicación",
"enablePremodLinks": "Pre-Moderar Commentarios que contienen Links",
"enablePremodLinksDescription": "Los y las Moderadoras deben probar cualquier comentario que contengan links antes de su publicación.",
"enablePremodLinks": "Pre-Moderar Comentarios que contienen Enlaces",
"enablePremodLinksDescription": "Los y las moderadoras deben aprobar cualquier comentario que contengan enlaces antes de su publicación.",
"enableQuestionBox": "Hacer una pregunta a los y las lectoras.",
"enableQuestionBoxDescription": "Esta pregunta aparecera en la parte de arriba del hilo de comentarios.",
"includeQuestionHere": "Escribir la pregunta aquí."
+10
View File
@@ -1,3 +1,13 @@
.Reply {
position: relative;
margin-bottom: 15px;
}
.Comment {
margin-bottom: 15px;
}
.pendingComment {
filter: blur(2px);
pointer-events: none;
}
+96 -24
View File
@@ -17,6 +17,7 @@ import PubDate from 'coral-plugin-pubdate/PubDate';
import {ReplyBox, ReplyButton} from 'coral-plugin-replies';
import FlagComment from 'coral-plugin-flags/FlagComment';
import LikeButton from 'coral-plugin-likes/LikeButton';
import {BestButton, IfUserCanModifyBest, BEST_TAG, commentIsBest, BestIndicator} from 'coral-plugin-best/BestButton';
import LoadMore from 'coral-embed-stream/src/LoadMore';
import styles from './Comment.css';
@@ -25,6 +26,11 @@ const getActionSummary = (type, comment) => comment.action_summaries
.filter((a) => a.__typename === type)[0];
const isStaff = (tags) => !tags.every((t) => t.name !== 'STAFF') ;
// hold actions links (e.g. Like, Reply) along the comment footer
const ActionButton = ({children}) => {
return <span className="comment__action-button comment__action-button--nowrap">{ children }</span>;
};
class Comment extends React.Component {
constructor(props) {
@@ -37,13 +43,14 @@ class Comment extends React.Component {
// id of currently opened ReplyBox. tracked in Stream.js
activeReplyBox: PropTypes.string.isRequired,
disableReply: PropTypes.bool,
setActiveReplyBox: PropTypes.func.isRequired,
refetch: PropTypes.func.isRequired,
showSignInDialog: PropTypes.func.isRequired,
postFlag: PropTypes.func.isRequired,
postLike: PropTypes.func.isRequired,
deleteAction: PropTypes.func.isRequired,
parentId: PropTypes.string,
highlighted: PropTypes.string,
addNotification: PropTypes.func.isRequired,
postItem: PropTypes.func.isRequired,
depth: PropTypes.number.isRequired,
@@ -74,7 +81,13 @@ class Comment extends React.Component {
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
}).isRequired
}).isRequired
}).isRequired,
// dispatch action to add a tag to a comment
addCommentTag: React.PropTypes.func,
// dispatch action to remove a tag from a comment
removeCommentTag: React.PropTypes.func,
}
render () {
@@ -85,34 +98,69 @@ class Comment extends React.Component {
asset,
depth,
postItem,
refetch,
addNotification,
showSignInDialog,
postLike,
highlighted,
postFlag,
postDontAgree,
loadMore,
setActiveReplyBox,
activeReplyBox,
deleteAction
deleteAction,
addCommentTag,
removeCommentTag,
disableReply,
} = this.props;
const like = getActionSummary('LikeActionSummary', comment);
const flag = getActionSummary('FlagActionSummary', comment);
const dontagree = getActionSummary('DontAgreeActionSummary', comment);
let commentClass = parentId ? `reply ${styles.Reply}` : `comment ${styles.Comment}`;
commentClass += highlighted === comment.id ? ' highlighted-comment' : '';
commentClass += comment.id === 'pending' ? ` ${styles.pendingComment}` : '';
// call a function, and if it errors, call addNotification('error', ...) (e.g. to show user a snackbar)
const notifyOnError = (fn, errorToMessage) => async () => {
if (typeof errorToMessage !== 'function') {errorToMessage = (error) => error.message;}
try {
return await fn();
} catch (error) {
addNotification('error', errorToMessage(error));
throw error;
}
};
const addBestTag = notifyOnError(() => addCommentTag({
id: comment.id,
tag: BEST_TAG,
}), () => 'Failed to tag comment as best');
const removeBestTag = notifyOnError(() => removeCommentTag({
id: comment.id,
tag: BEST_TAG,
}), () => 'Failed to remove best comment tag');
return (
<div
className={parentId ? `reply ${styles.Reply}` : `comment ${styles.Comment}`}
className={commentClass}
id={`c_${comment.id}`}
style={{marginLeft: depth * 30}}>
<hr aria-hidden={true} />
<AuthorName
author={comment.user}/>
{ isStaff(comment.tags)
? <TagLabel isStaff={true}/>
? <TagLabel>Staff</TagLabel>
: null }
{ commentIsBest(comment)
? <TagLabel><BestIndicator /></TagLabel>
: null }
<PubDate created_at={comment.created_at} />
<Content body={comment.body} />
<div className="commentActionsLeft">
<div className="commentActionsLeft comment__action-container">
<ActionButton>
<LikeButton
like={like}
id={comment.id}
@@ -120,22 +168,41 @@ class Comment extends React.Component {
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
currentUser={currentUser} />
<ReplyButton
onClick={() => setActiveReplyBox(comment.id)}
parentCommentId={parentId || comment.id}
currentUserId={currentUser && currentUser.id}
banned={false} />
</div>
<div className="commentActionsRight">
<PermalinkButton articleURL={asset.url} commentId={comment.id} />
<FlagComment
flag={flag}
id={comment.id}
author_id={comment.user.id}
postFlag={postFlag}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
currentUser={currentUser} />
</ActionButton>
{
!disableReply &&
<ActionButton>
<ReplyButton
onClick={() => setActiveReplyBox(comment.id)}
parentCommentId={parentId || comment.id}
currentUserId={currentUser && currentUser.id}
banned={false} />
</ActionButton>
}
<ActionButton>
<IfUserCanModifyBest user={currentUser}>
<BestButton
isBest={commentIsBest(comment)}
addBest={addBestTag}
removeBest={removeBestTag} />
</IfUserCanModifyBest>
</ActionButton>
</div>
<div className="commentActionsRight comment__action-container">
<ActionButton>
<PermalinkButton articleURL={asset.url} commentId={comment.id} />
</ActionButton>
<ActionButton>
<FlagComment
flag={flag && flag.current_user ? flag : dontagree}
id={comment.id}
author_id={comment.user.id}
postFlag={postFlag}
postDontAgree={postDontAgree}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
currentUser={currentUser} />
</ActionButton>
</div>
{
activeReplyBox === comment.id
@@ -155,18 +222,21 @@ class Comment extends React.Component {
comment.replies &&
comment.replies.map(reply => {
return <Comment
refetch={refetch}
setActiveReplyBox={setActiveReplyBox}
disableReply={disableReply}
activeReplyBox={activeReplyBox}
addNotification={addNotification}
parentId={comment.id}
postItem={postItem}
depth={depth + 1}
asset={asset}
highlighted={highlighted}
currentUser={currentUser}
postLike={postLike}
postFlag={postFlag}
deleteAction={deleteAction}
addCommentTag={addCommentTag}
removeCommentTag={removeCommentTag}
showSignInDialog={showSignInDialog}
reactKey={reply.id}
key={reply.id}
@@ -180,6 +250,8 @@ class Comment extends React.Component {
assetId={asset.id}
comments={comment.replies}
parentId={comment.id}
topLevel={false}
replyCount={comment.replyCount}
moreComments={comment.replyCount > comment.replies.length}
loadMore={loadMore}/>
</div>
+91 -61
View File
@@ -2,6 +2,9 @@ import React, {Component} from 'react';
import {compose} from 'react-apollo';
import {connect} from 'react-redux';
import isEqual from 'lodash/isEqual';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-framework/translations';
const lang = new I18n(translations);
import {TabBar, Tab, TabContent, Spinner} from 'coral-ui';
@@ -10,10 +13,10 @@ const {addNotification, clearNotification} = notificationActions;
const {fetchAssetSuccess} = assetActions;
import {queryStream} from 'coral-framework/graphql/queries';
import {postComment, postFlag, postLike, deleteAction} from 'coral-framework/graphql/mutations';
import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag} from 'coral-framework/graphql/mutations';
import {editName} from 'coral-framework/actions/user';
import {updateCountCache} from 'coral-framework/actions/asset';
import {Notification, notificationActions, authActions, assetActions, pym} from 'coral-framework';
import {notificationActions, authActions, assetActions, pym} from 'coral-framework';
import Stream from './Stream';
import InfoBox from 'coral-plugin-infobox/InfoBox';
@@ -25,15 +28,16 @@ import UserBox from 'coral-sign-in/components/UserBox';
import SignInContainer from 'coral-sign-in/containers/SignInContainer';
import SuspendedAccount from 'coral-framework/components/SuspendedAccount';
import ChangeUsernameContainer from '../../coral-sign-in/containers/ChangeUsernameContainer';
import SettingsContainer from 'coral-settings/containers/SettingsContainer';
import ProfileContainer from 'coral-settings/containers/ProfileContainer';
import RestrictedContent from 'coral-framework/components/RestrictedContent';
import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer';
import Comment from './Comment';
import LoadMore from './LoadMore';
import NewCount from './NewCount';
class Embed extends Component {
state = {activeTab: 0, showSignInDialog: false};
state = {activeTab: 0, showSignInDialog: false, activeReplyBox: ''};
changeTab = (tab) => {
@@ -51,28 +55,17 @@ class Embed extends Component {
data: React.PropTypes.shape({
loading: React.PropTypes.bool,
error: React.PropTypes.object
}).isRequired
}).isRequired,
// dispatch action to add a tag to a comment
addCommentTag: React.PropTypes.func,
// dispatch action to remove a tag from a comment
removeCommentTag: React.PropTypes.func,
}
componentDidMount () {
pym.sendMessage('childReady');
pym.onMessage('DOMContentLoaded', hash => {
const commentId = hash.replace('#', 'c_');
let count = 0;
const interval = setInterval(() => {
if (document.getElementById(commentId)) {
window.clearInterval(interval);
pym.scrollParentToChildEl(commentId);
}
if (++count > 100) { // ~10 seconds
// give up waiting for the comments to load.
// it would be weird for the page to jump after that long.
window.clearInterval(interval);
}
}, 100);
});
}
componentWillReceiveProps (nextProps) {
@@ -82,11 +75,29 @@ class Embed extends Component {
}
}
componentDidUpdate(prevProps) {
if(!isEqual(prevProps.data.comment, this.props.data.comment)) {
// Scroll to a permalinked comment if one is in the URL once the page is done rendering.
setTimeout(()=>pym.scrollParentToChildEl(`c_${this.props.data.comment.id}`), 0);
}
}
setActiveReplyBox = (reactKey) => {
if (!this.props.auth.user) {
const offset = document.getElementById(`c_${reactKey}`).getBoundingClientRect().top - 75;
this.props.showSignInDialog(offset);
} else {
this.setState({activeReplyBox: reactKey});
}
}
render () {
const {activeTab} = this.state;
const {closedAt, countCache = {}} = this.props.asset;
const {loading, asset, refetch} = this.props.data;
const {loading, asset, refetch, comment} = this.props.data;
const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth;
const highlightedComment = comment && comment.parent ? comment.parent : comment;
const openStream = closedAt === null;
@@ -110,10 +121,10 @@ class Embed extends Component {
<div className="commentStream">
<TabBar onChange={this.changeTab} activeTab={activeTab}>
<Tab><Count count={asset.commentCount}/></Tab>
<Tab>Settings</Tab>
<Tab>{lang.t('MY_COMMENTS')}</Tab>
<Tab restricted={!isAdmin}>Configure Stream</Tab>
</TabBar>
{loggedIn && <UserBox user={user} logout={this.props.logout} changeTab={this.changeTab}/>}
{loggedIn && <UserBox user={user} logout={() => this.props.logout().then(refetch)} changeTab={this.changeTab}/>}
<TabContent show={activeTab === 0}>
{
openStream
@@ -149,13 +160,38 @@ class Embed extends Component {
charCount={asset.settings.charCountEnable && asset.settings.charCount} />
: null
}
<ModerationLink assetId={asset.id} isAdmin={isAdmin} />
</RestrictedContent>
</div>
: <p>{asset.settings.closedMessage}</p>
}
{!loggedIn && <SignInContainer requireEmailConfirmation={asset.settings.requireEmailConfirmation} offset={signInOffset}/>}
{!loggedIn && <SignInContainer
requireEmailConfirmation={asset.settings.requireEmailConfirmation}
refetch={refetch}
offset={signInOffset}/>}
{loggedIn && user && <ChangeUsernameContainer loggedIn={loggedIn} offset={signInOffset} user={user} />}
{loggedIn && <ModerationLink assetId={asset.id} isAdmin={isAdmin} />}
{
highlightedComment &&
<Comment
refetch={refetch}
setActiveReplyBox={this.setActiveReplyBox}
activeReplyBox={this.state.activeReplyBox}
addNotification={this.props.addNotification}
depth={0}
postItem={this.props.postItem}
asset={asset}
currentUser={user}
highlighted={comment.id}
postLike={this.props.postLike}
postFlag={this.props.postFlag}
postDontAgree={this.props.postDontAgree}
loadMore={this.props.loadMore}
deleteAction={this.props.deleteAction}
showSignInDialog={this.props.showSignInDialog}
key={highlightedComment.id}
reactKey={highlightedComment.id}
comment={highlightedComment} />
}
<NewCount
commentCount={asset.commentCount}
countCache={countCache[asset.id]}
@@ -164,33 +200,36 @@ class Embed extends Component {
assetId={asset.id}
updateCountCache={this.props.updateCountCache}
/>
<Stream
refetch={refetch}
addNotification={this.props.addNotification}
postItem={this.props.postItem}
asset={asset}
currentUser={user}
postLike={this.props.postLike}
postFlag={this.props.postFlag}
getCounts={this.props.getCounts}
updateCountCache={this.props.updateCountCache}
loadMore={this.props.loadMore}
deleteAction={this.props.deleteAction}
showSignInDialog={this.props.showSignInDialog}
comments={asset.comments} />
<Notification
notifLength={4500}
clearNotification={this.props.clearNotification}
notification={{text: null}}
/>
<div className="embed__stream">
<Stream
open={openStream}
addNotification={this.props.addNotification}
postItem={this.props.postItem}
setActiveReplyBox={this.setActiveReplyBox}
activeReplyBox={this.state.activeReplyBox}
asset={asset}
currentUser={user}
postLike={this.props.postLike}
postFlag={this.props.postFlag}
postDontAgree={this.props.postDontAgree}
getCounts={this.props.getCounts}
addCommentTag={this.props.addCommentTag}
removeCommentTag={this.props.removeCommentTag}
updateCountCache={this.props.updateCountCache}
loadMore={this.props.loadMore}
deleteAction={this.props.deleteAction}
showSignInDialog={this.props.showSignInDialog}
comments={asset.comments} />
</div>
<LoadMore
topLevel={true}
assetId={asset.id}
comments={asset.comments}
moreComments={asset.commentCount > asset.comments.length}
moreComments={countCache[asset.id] > asset.comments.length}
loadMore={this.props.loadMore}/>
</TabContent>
<TabContent show={activeTab === 1}>
<SettingsContainer
<ProfileContainer
loggedIn={loggedIn}
userData={this.props.userData}
showSignInDialog={this.props.showSignInDialog}
@@ -204,11 +243,6 @@ class Embed extends Component {
/>
</RestrictedContent>
</TabContent>
<Notification
notifLength={4500}
clearNotification={this.props.clearNotification}
notification={this.props.notification}
/>
</div>
</div>
);
@@ -216,7 +250,6 @@ class Embed extends Component {
}
const mapStateToProps = state => ({
notification: state.notification.toJS(),
auth: state.auth.toJS(),
userData: state.user.toJS(),
asset: state.asset.toJS()
@@ -225,13 +258,7 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
requestConfirmEmail: () => dispatch(requestConfirmEmail()),
loadAsset: (asset) => dispatch(fetchAssetSuccess(asset)),
addNotification: (type, text) => {
pym.sendMessage('getPosition');
pym.onMessage('position', position => {
dispatch(addNotification(type, text, position));
});
},
addNotification: (type, text) => addNotification(type, text),
clearNotification: () => dispatch(clearNotification()),
editName: (username) => dispatch(editName(username)),
showSignInDialog: (offset) => dispatch(showSignInDialog(offset)),
@@ -245,6 +272,9 @@ export default compose(
postComment,
postFlag,
postLike,
postDontAgree,
addCommentTag,
removeCommentTag,
deleteAction,
queryStream
)(Embed);
+41 -18
View File
@@ -7,39 +7,62 @@ const lang = new I18n(translations);
const loadMoreComments = (assetId, comments, loadMore, parentId) => {
if (!comments.length) {
return;
let cursor = null;
if (comments.length) {
cursor = parentId
? comments[0].created_at
: comments[comments.length - 1].created_at;
}
const cursor = parentId
? comments[0].created_at
: comments[comments.length - 1].created_at;
loadMore({
limit: ADDTL_COMMENTS_ON_LOAD_MORE,
cursor,
assetId,
asset_id: assetId,
parent_id: parentId,
sort: parentId ? 'CHRONOLOGICAL' : 'REVERSE_CHRONOLOGICAL'
});
};
const LoadMore = ({assetId, comments, loadMore, moreComments, parentId}) => (
moreComments
? <Button
className='coral-load-more'
onClick={() => loadMoreComments(assetId, comments, loadMore, parentId)}>
{
lang.t('loadMore')
}
</Button>
: null
);
class LoadMore extends React.Component {
componentDidMount () {
this.initialState = true;
}
replyCountFormat = (count) => {
if (count === 1) {
return lang.t('viewReply');
}
if (this.initialState) {
return lang.t('viewAllRepliesInitial', count);
} else {
return lang.t('viewAllReplies', count);
}
}
render () {
const {assetId, comments, loadMore, moreComments, parentId, replyCount, topLevel} = this.props;
return moreComments
? <div className='coral-load-more'>
<Button
onClick={() => {
this.initialState = false;
loadMoreComments(assetId, comments, loadMore, parentId);
}}>
{topLevel ? lang.t('viewMoreComments') : this.replyCountFormat(replyCount)}
</Button>
</div>
: null;
}
}
LoadMore.propTypes = {
assetId: PropTypes.string.isRequired,
comments: PropTypes.array.isRequired,
moreComments: PropTypes.bool.isRequired,
topLevel: PropTypes.bool.isRequired,
replyCount: PropTypes.number,
loadMore: PropTypes.func.isRequired
};
+2 -1
View File
@@ -19,12 +19,13 @@ const NewCount = (props) => {
return <div className='coral-new-comments'>
{
props.countCache && newComments > 0 &&
props.countCache && newComments > 0 ?
<button onClick={onLoadMoreClick(props)} className='coral-load-more'>
{newComments === 1
? lang.t('newCount', newComments, lang.t('comment'))
: lang.t('newCount', newComments, lang.t('comments'))}
</button>
: null
}
</div>;
};

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