Compare commits

..
648 Commits
Author SHA1 Message Date
Kim Gardner e6c40a5839 Merge pull request #615 from coralproject/require-email-info
Display require email verification info
2017-05-26 16:24:06 -04:00
Chi Vinh Le 681e07306b Require email verification info 2017-05-27 03:13:00 +07:00
Kim Gardner 794af5a670 Merge pull request #611 from coralproject/auth-flow
FE Auth Flow and fix for Safari token workaround
2017-05-26 16:06:32 -04:00
Chi Vinh Le 81e8a593b3 Merge branch 'auth-flow' of ssh://github.com/coralproject/talk into auth-flow 2017-05-27 02:51:56 +07:00
Chi Vinh Le 89cb4c4502 Resolve infinite loop 2017-05-27 02:45:59 +07:00
Kim Gardner e9c8876a8f Merge branch 'master' into auth-flow 2017-05-26 15:44:12 -04:00
Kim Gardner e68124a426 Merge pull request #614 from coralproject/update-core-plugins
Update default plugins that ship with Coral core
2017-05-26 15:43:27 -04:00
Chi Vinh Le 8713a2ae87 Enable Linting 2017-05-27 02:08:54 +07:00
Kim Gardner 54e7956af5 Update default plugins that ship with Coral core 2017-05-26 15:08:47 -04:00
Chi Vinh Le ee2068f5f1 Merge branch 'master' into auth-flow
Conflicts:
	client/coral-admin/src/actions/auth.js
2017-05-27 01:07:19 +07:00
Chi Vinh Le 52b3dcab43 Fix changing username not updating 2017-05-27 01:05:12 +07:00
Chi Vinh Le a7cdcb0bf7 Fix request options and use better filename 2017-05-27 00:54:36 +07:00
Chi Vinh Le 99a9154d40 Remove unused dependency 2017-05-27 00:26:04 +07:00
Kim Gardner 3b96d08b58 Merge pull request #612 from coralproject/losowsky-patch-1
added iphone 7 support in the list
2017-05-26 12:46:38 -04:00
Andrew Losowsky 82c5d140be added iphone 7 support in the list 2017-05-26 12:30:26 -04:00
Chi Vinh Le 7b6b46f3e5 Sync username with store 2017-05-26 21:38:29 +07:00
Chi Vinh Le d4569b5f51 Set cookie for facebook login on safari 2017-05-26 21:37:11 +07:00
Chi Vinh Le 6a9f216c0f Detect mobile & desktop safari server side 2017-05-26 20:58:48 +07:00
Chi Vinh Le 6a1befc3f0 Detect mobile safari & desktop safari 2017-05-26 20:48:24 +07:00
Chi Vinh Le bab47c9d44 Separate store 2017-05-26 20:37:06 +07:00
Chi Vinh Le 276e58060a Fix change username dialog 2017-05-26 20:36:56 +07:00
Belen Curcio 8edc16ed85 Removing logs 2017-05-25 20:06:49 -03:00
Belen Curcio 8171cb53e1 Ádding another extractor 2017-05-25 20:04:32 -03:00
Belen Curcio cbf7cc67f4 Extracting token from cookie if safari 2017-05-25 18:30:01 -03:00
Kim Gardner d742d713cf Merge pull request #610 from coralproject/bump-version-1-7-0
Update version 1.7.0
2017-05-25 17:05:20 -04:00
Kim Gardner 178cecad05 Update version 1.7.0 2017-05-25 16:58:35 -04:00
Belen Curcio 460f3f292b Ádding dependencies 2017-05-25 17:41:35 -03:00
Belén Curcio 5285c84de3 Merge pull request #603 from coralproject/coral-plugin-auth
Coral plugin Auth
2017-05-25 17:09:29 -03:00
Belen Curcio a8c361e2f7 by hostname 2017-05-25 16:53:12 -03:00
Belen Curcio 21644bda8a Linting 2017-05-25 16:24:39 -03:00
Belen Curcio 75080cfe30 Password reset and valid locations 2017-05-25 16:19:19 -03:00
Belen Curcio e510553d9a Sending cookies if safari 2017-05-25 13:55:03 -03:00
Belen Curcio bb6be9a734 BE implementation 2017-05-25 11:53:40 -03:00
Belen Curcio bd21719ea5 Handling non safari browsers 2017-05-24 17:20:23 -03:00
Kim Gardner 4ad2cd76c0 Merge branch 'master' into coral-plugin-auth 2017-05-23 12:57:58 -04:00
Riley Davis 3c4b510829 Merge pull request #605 from coralproject/missed-roles
hasRoles is no longer a function
2017-05-22 15:20:17 -06:00
riley add3cbf7b3 hasRoles is no longer a function 2017-05-22 15:10:56 -06:00
Belen Curcio 8266c65043 Merge Conflicts 2017-05-22 09:39:04 -03:00
Belen Curcio 317f7134f2 linting 2017-05-22 09:03:10 -03:00
Belen Curcio 9936b6ec47 Prettier 2017-05-22 08:59:34 -03:00
Belen Curcio 1d96f67db6 Merge branch 'master' of github.com:coralproject/talk into coral-plugin-auth 2017-05-22 08:58:33 -03:00
Belen Curcio 9d00a27f1d Auth as a plugin 2017-05-22 08:40:45 -03:00
Belen Curcio f364a1e0a1 Refactor 2017-05-22 08:23:58 -03:00
Belen Curcio 1aacb565d4 linting 2017-05-19 20:10:49 -03:00
Belen Curcio 801a6f8a53 External plugin auth fully working, now refactor 2017-05-19 20:00:33 -03:00
Belen Curcio dbfd2aff7c SignInButton 2017-05-19 19:29:50 -03:00
Belen Curcio 3af49de0ce New login slots and auth plugins 2017-05-19 18:04:54 -03:00
Belen Curcio 904aef0afc Adding coral-plugin-auth 2017-05-19 17:32:54 -03:00
Belen Curcio 87ada30eff Ádding Stream Slot 2017-05-19 17:32:16 -03:00
Riley Davis f32208b35b Merge pull request #585 from coralproject/multiple-roles
Multiple roles
2017-05-19 13:12:20 -06:00
riley 2c620c868d merge master 2017-05-19 11:05:46 -06:00
Kiwi 04fbeededa Merge pull request #600 from coralproject/suspend-user
Suspend user
2017-05-19 23:29:13 +07:00
Kiwi 8cb9945e25 Merge branch 'master' into multiple-roles 2017-05-19 23:26:45 +07:00
riley 17ceadd8c9 move client-side perms to services 2017-05-19 09:18:24 -06:00
Chi Vinh Le 3688d3f970 Gracefully allow user with no email 2017-05-19 17:53:10 +07:00
Chi Vinh Le 1d6455058e Apply review suggestions 2017-05-19 17:51:58 +07:00
Chi Vinh Le 1374217680 Merge branch 'master' into suspend-user 2017-05-19 17:46:06 +07:00
Kim Gardner f35a9ec8cd Merge pull request #602 from coralproject/content-type-fix
added fix for chrome + content type negotiation
2017-05-18 18:40:35 -04:00
Wyatt Johnson 25230f2e9d added fix for chrome + content type negotiation 2017-05-18 16:30:28 -06:00
Kim Gardner f29df23c07 Merge pull request #601 from coralproject/read-only-inputs
handle no-interaction input the correct way
2017-05-18 17:21:12 -04:00
Kim Gardner b3e494d279 Merge branch 'master' into read-only-inputs 2017-05-18 17:14:48 -04:00
Kim Gardner 13376504b5 Merge pull request #598 from gobengo/edit-comment-settings
Edit Comment Window Length Setting
2017-05-18 17:14:29 -04:00
Kim Gardner 12ec058588 Merge branch 'master' into edit-comment-settings 2017-05-18 17:07:37 -04:00
Kim Gardner 5cbe9029aa Merge pull request #596 from gobengo/fix-ignore-user
Ignored user tombstones work again
2017-05-18 16:43:46 -04:00
riley 6deee7cb80 add a perms utils file 2017-05-18 14:28:43 -06:00
Riley Davis c772cffc92 handle no-interaction input the correct way 2017-05-18 13:51:48 -06:00
Chi Vinh Le 89b7918e41 Tiny renaming 2017-05-19 02:29:59 +07:00
Chi Vinh Le b521934ee2 Remove unneeded refetch 2017-05-19 02:24:47 +07:00
Chi Vinh Le 061c18670b Merge branch 'master' into suspend-user
Conflicts:
	client/coral-admin/src/constants/moderation.js
	client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
2017-05-19 02:19:24 +07:00
Chi Vinh Le 0040bc4c01 Remove remaining debug msgs 2017-05-19 02:12:27 +07:00
Chi Vinh Le 0588a8ab66 Use async / await :-) 2017-05-19 02:07:38 +07:00
Chi Vinh Le 859a0b1fbe Make until arg required 2017-05-19 02:03:36 +07:00
Chi Vinh Le d995cdde07 Make email message required 2017-05-19 02:01:47 +07:00
Chi Vinh Le f641a3ec40 Complete suspend user implementation 2017-05-19 01:52:03 +07:00
Kim Gardner e80e79f267 Merge branch 'master' into edit-comment-settings 2017-05-18 14:23:14 -04:00
riley ff75b9474a merge master 2017-05-18 12:18:00 -06:00
riley 056ba9a04c do the same thing for the Drawer 2017-05-18 12:11:11 -06:00
riley d98ce38bf5 show config tab to admins. hide menu items from users that can't access admin 2017-05-18 12:06:07 -06:00
Benjamin Goering 1d322d8c70 Merge branch 'master' into fix-ignore-user 2017-05-18 10:50:00 -07:00
Benjamin Goering aaa2da6c23 Stream container refetch then setCommentCountCache to asset.commentCount instead of silly zero 2017-05-18 10:43:38 -07:00
Wyatt Johnson 5a272413e6 Merge pull request #599 from coralproject/gzip
Added gzip compression to bundle assets
2017-05-18 11:34:05 -06:00
Wyatt Johnson 4dc83aaf63 added middleware wrap 2017-05-18 11:27:28 -06:00
Wyatt Johnson 12713dae86 Removed unused assets 2017-05-18 11:18:08 -06:00
Wyatt Johnson 555c593059 Added gzip compression to bundle assets 2017-05-18 11:08:46 -06:00
Benjamin Goering fb33eefe8e trivial changes requested by review 2017-05-18 08:19:52 -07:00
Benjamin Goering cfc6d4bfea Merge branch 'fix-ignore-user' of github.com:gobengo/talk into fix-ignore-user 2017-05-18 08:12:40 -07:00
Benjamin Goering 6bbeee3a6b StopIgnoringUser does not refetch EmbedQuery 2017-05-18 08:11:39 -07:00
Benjamin Goering 340d3cea62 contains/Stream setCommentCountCache waits for refetch promise 2017-05-18 08:10:20 -07:00
Kim Gardner e8e9dbe3ca Merge branch 'master' into fix-ignore-user 2017-05-18 09:49:54 -04:00
Benjamin Goering cb45f0a8b3 Merge branch 'master' into edit-comment-settings 2017-05-17 15:46:14 -07:00
Benjamin Goering cb1a4ee1cc remove extraneous console.log 2017-05-17 15:40:32 -07:00
Benjamin Goering d93ad38dd1 Admins can change Edit Comment Window length in Moderation Settings 2017-05-17 15:39:19 -07:00
riley c4210db307 staff should not see moderate tag. translations 2017-05-17 16:10:02 -06:00
riley 3fc1029a93 do not require auth in the Header 2017-05-17 16:00:01 -06:00
Riley Davis 54abaeb587 wrap the not-authorized warning in a template 2017-05-17 15:47:41 -06:00
Riley Davis 4a97db3357 emma's copy 2017-05-17 15:41:49 -06:00
Riley Davis 88377b3180 do not show Config tab for non-admins 2017-05-17 15:37:13 -06:00
Kim Gardner 0dfec6e619 Merge pull request #597 from coralproject/copy-button-should-work
user detail copy button should work
2017-05-17 17:35:08 -04:00
Riley Davis e343897526 user detail copy button regression 2017-05-17 15:11:00 -06:00
Benjamin Goering 294eb6c81d Merge branch 'master' into fix-ignore-user 2017-05-17 13:57:23 -07:00
Kim Gardner 413eef8834 Merge pull request #564 from coralproject/user-detail-ui
User detail ui
2017-05-17 16:41:00 -04:00
Benjamin Goering 9eac05c8fb Ignored user tombstones work again 2017-05-17 13:18:59 -07:00
Chi Vinh Le 492b973206 Add 1 extra suspension minute 2017-05-18 02:28:01 +07:00
Chi Vinh Le 2bdf413b88 Integrate into embed stream 2017-05-18 02:28:01 +07:00
Chi Vinh Le 76e87f4c34 Move previous suspendUser feature to rejectUsername 2017-05-18 02:28:01 +07:00
Riley Davis c66e3141e0 actually check permissions 2017-05-17 11:38:43 -06:00
Riley Davis a0f41bb19d show total comments and rejected % 2017-05-17 10:54:14 -06:00
Chi Vinh Le 056ff427a5 Merge branch 'master' into suspend-user 2017-05-17 21:50:28 +07:00
Chi Vinh Le 71c743dda7 Extend backend to support new suspend feature 2017-05-17 20:05:18 +07:00
Wyatt Johnson fde4dee52f Resolver cleanup 2017-05-16 17:26:34 -06:00
riley b49ace99eb add permission for EDIT_COMMENT 2017-05-16 16:38:30 -06:00
riley d081c0c5b5 remove context 2017-05-16 16:34:04 -06:00
riley 03ce044c53 merge master 2017-05-16 16:33:41 -06:00
riley 31647fb2f0 have function params in the correct order 2017-05-16 15:41:27 -06:00
Wyatt Johnson f4b9191476 Added author_id to comment count query 2017-05-16 14:20:20 -06:00
Riley Davis 4ca5c4bc09 Merge branch 'master' into user-detail-ui 2017-05-16 13:54:13 -06:00
Kiwi c21e500e58 Merge pull request #593 from coralproject/readd-last-comment
Readd lastComment field to asset resolver
2017-05-17 01:35:07 +07:00
Chi Vinh Le 7d858c7b3f Readd lastComment field to asset resolver 2017-05-17 01:25:06 +07:00
riley c7e9fe3f5f update backend with the can() method 2017-05-16 10:50:09 -06:00
Wyatt Johnson e90ce7f4c3 fixed merge issue 2017-05-16 10:20:16 -06:00
riley 62e8e4c5c5 change how constants are exposed 2017-05-16 10:15:43 -06:00
Wyatt Johnson 3e2ed222ed Merge branch 'trust-merge' into user-detail-ui 2017-05-16 09:53:35 -06:00
riley 7896b3f33b merge master 2017-05-16 09:43:49 -06:00
Chi Vinh Le 97121382c7 Merge branch 'master' into suspend-user
Conflicts:
	client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
	client/coral-admin/src/containers/ModerationQueue/components/Comment.js
	client/coral-framework/utils/index.js
	package.json
2017-05-16 21:02:33 +07:00
Belén Curcio e561819c8d Merge pull request #592 from coralproject/invalidate-old-data
Invalidate old user data
2017-05-16 08:57:32 -03:00
Chi Vinh Le c7e0577708 Merge branch 'master' into invalidate-old-data 2017-05-16 18:17:45 +07:00
Chi Vinh Le 2ab8cc9481 Allow Apollo to invalidate old user data 2017-05-16 18:17:43 +07:00
Kiwi acdc00af18 Merge pull request #591 from coralproject/refetch-profile-only-after-login
Only refetch for login on `My Profile`.
2017-05-16 18:12:54 +07:00
Belén Curcio f9cf5fb744 Merge branch 'master' into refetch-profile-only-after-login 2017-05-16 07:57:20 -03:00
Chi Vinh Le e1b80c3317 Only refetch for login 2017-05-16 17:50:17 +07:00
Belén Curcio f37c1c3b5d Merge pull request #590 from coralproject/auth-login-fixes
Auth login fixes
2017-05-16 07:42:32 -03:00
Chi Vinh Le ec19618052 Readd login fixes 2017-05-16 17:26:53 +07:00
Chi Vinh Le 81f51b69ec Clear token before dispatching logout action 2017-05-16 17:13:19 +07:00
Belén Curcio 084bd6710f Merge pull request #589 from coralproject/fix
Updated Lock File
2017-05-16 00:14:02 -03:00
Belen Curcio df843a23dd updated loc 2017-05-16 00:09:33 -03:00
Kim Gardner 95e770aff8 Merge pull request #588 from coralproject/fix
Fix for FB login.
2017-05-15 22:57:30 -04:00
Belen Curcio d3252b8c87 Done :) 2017-05-15 23:47:29 -03:00
Belen Curcio 1eada688fe Prettier 2017-05-15 23:23:22 -03:00
Belen Curcio 4b6e562492 Missing "" to send strings instead of objects 2017-05-15 23:22:36 -03:00
riley 4d3788e4c9 saving my place so i dont have too many threads 2017-05-15 16:25:32 -06:00
Wyatt Johnson 50db2d1dda Initial merge of trust functionality 2017-05-15 15:28:57 -06:00
Kim Gardner 297dee6da8 Merge pull request #587 from coralproject/edit-changes
Edit Consistency
2017-05-15 16:47:33 -04:00
Belen Curcio 2b7f545c2d lint 2017-05-15 17:31:53 -03:00
Belen Curcio b789a8e107 Removing kiwis fix since this was fixed in auth tokens branch 2017-05-15 17:23:57 -03:00
Belen Curcio b2e5c0452a The merge conficts 2017-05-15 17:13:48 -03:00
Belén Curcio afa3123829 Merge pull request #578 from coralproject/auth-tokens
JWT Auth
2017-05-15 16:51:10 -03:00
Wyatt Johnson 5aca83993b fixed build process 2017-05-15 13:35:51 -06:00
Wyatt Johnson 8ff0c3db51 Merge branch 'auth-tokens' of github.com:coralproject/talk into auth-tokens 2017-05-15 13:14:10 -06:00
Chi Vinh Le 74e1436ae4 Use correct error key 2017-05-16 01:09:33 +07:00
Chi Vinh Le def35f0a93 Fix login issues 2017-05-16 00:55:12 +07:00
Chi Vinh Le 06001763ff Remove unused files 2017-05-16 00:52:37 +07:00
Belen Curcio 75aa8d79df linting 2017-05-15 14:50:26 -03:00
Belen Curcio 843c3a16c8 Admin share same network interface, and unifying actions 2017-05-15 14:48:12 -03:00
Kiwi 963827204e Merge branch 'master' into edit-changes 2017-05-15 23:22:30 +07:00
Belen Curcio 4e86e4197c handling logout 2017-05-15 13:13:28 -03:00
Belen Curcio 5dae7bf4ac Merge branch 'master' of github.com:coralproject/talk into auth-tokens 2017-05-15 13:12:11 -03:00
Belen Curcio 48370db09a Merge branch 'auth-tokens' of github.com:coralproject/talk into auth-tokens 2017-05-15 13:11:32 -03:00
Chi Vinh Le a9207428fe Calculate "until" 2017-05-15 23:07:46 +07:00
Wyatt Johnson 9bee31198e Added logout routes 2017-05-15 10:03:09 -06:00
Kim Gardner 2a95c1eee2 Merge pull request #583 from coralproject/hooks-docs
[DOCS] - Hooks and Reactions
2017-05-15 11:55:41 -04:00
Belen Curcio 57ced201eb WebStorage events 2017-05-15 12:52:54 -03:00
Belen Curcio 370cdf8c09 Ádding middlewares to our network interface 2017-05-15 12:51:07 -03:00
Kim Gardner cb64d599fe Merge branch 'master' into hooks-docs 2017-05-15 11:50:46 -04:00
Chi Vinh Le 63fa3c4de7 Implement suspend user components 2017-05-15 22:28:04 +07:00
Belen Curcio 292f758ac9 Comment history relies on authtokens 2017-05-15 12:17:30 -03:00
Belen Curcio 76b96a66a2 Adding Bearer token to the Graph Client 2017-05-15 11:41:36 -03:00
Belen Curcio 8754ade5a4 merge 2017-05-12 21:52:29 -03:00
Wyatt Johnson e218de811b removed csrf reference 2017-05-12 15:53:25 -06:00
Wyatt Johnson 9403280c56 Merge branch 'master' into edit-changes 2017-05-12 15:44:19 -06:00
Belen Curcio 964cc362e7 Login, logout with tokens. WIP 2017-05-12 18:26:24 -03:00
Wyatt Johnson a5c9a8e4b0 cleaned up graph, standardised mutation 2017-05-12 14:04:09 -06:00
David Erwin 7c5e04e820 Merge pull request #584 from coralproject/prevent-empty-edit
Prevent empty edit on the Front-End
2017-05-12 15:44:14 -04:00
Belen Curcio 7fd5509490 Prettier 2017-05-12 15:48:19 -03:00
Belen Curcio 5e8a02c461 Removing CSRF 2017-05-12 15:40:53 -03:00
Wyatt Johnson ffd96f57e9 Comment too short fixes 2017-05-12 11:16:52 -06:00
Wyatt Johnson e3017efebf Front end edits to accomedate ignoredUsers 2017-05-12 10:25:59 -06:00
Wyatt Johnson ed27209de9 More edit changes 2017-05-12 10:09:25 -06:00
riley e32ab99edf remove hasRoles 2017-05-12 09:57:07 -06:00
Chi Vinh Le 072437b0fd Prevent empty edit 2017-05-12 22:45:33 +07:00
Belen Curcio 674ac50035 Ádding reaction docs 2017-05-12 12:29:02 -03:00
Belen Curcio 0eb88b30eb Merge branch 'master' of github.com:coralproject/talk into hooks-docs 2017-05-12 12:19:26 -03:00
Wyatt Johnson f4dfb21e15 reverted pagination work 2017-05-12 09:14:25 -06:00
Belen Curcio e5a4fbc1cd DOCS Hooks 2017-05-12 12:07:55 -03:00
David Erwin ce583142ef Merge pull request #567 from coralproject/query-mutation-api
Extendable GraphQL framework
2017-05-12 09:43:50 -04:00
Chi Vinh Le 95a7a1ca47 Rename registry 2017-05-12 20:15:04 +07:00
Chi Vinh Le 7857cadef5 Merge branch 'master' into query-mutation-api
Conflicts:
	client/coral-embed-stream/src/components/Comment.js
	client/coral-embed-stream/src/containers/Embed.js
	client/coral-embed-stream/src/containers/Stream.js
	client/coral-embed-stream/src/index.js
	client/coral-framework/graphql/fragments/commentView.graphql
	client/coral-framework/graphql/mutations/index.js
	client/coral-framework/hocs/index.js
	client/coral-framework/hocs/withFragments.js
2017-05-12 19:50:36 +07:00
Wyatt Johnson 47186c4eac some changes to loaders 2017-05-11 17:52:07 -06:00
Wyatt Johnson ae5da93730 linting fixes 2017-05-11 17:44:51 -06:00
Wyatt Johnson a65d045c65 lint fixes 2017-05-11 17:38:49 -06:00
Wyatt Johnson 08e7a90d69 lint fixes 2017-05-11 17:34:47 -06:00
Wyatt Johnson 547ba126be cleanup to mutators 2017-05-11 17:31:22 -06:00
Wyatt Johnson d497ad3369 initial pagination rewrite, cleanups to comment edit 2017-05-11 17:27:02 -06:00
Riley Davis 14b483d089 create roles helper 2017-05-11 14:23:02 -06:00
Kim Gardner 5c5d014e23 Merge pull request #563 from gobengo/edit-comment
Edit comment
2017-05-11 14:28:33 -04:00
Benjamin Goering 923e20f867 Merge branch 'master' into edit-comment 2017-05-11 10:42:41 -07:00
Benjamin Goering cd7b1f2b2b editComment mutation doesn't refetch streamQuery. Instead a reducer updates streamQuery client-side 2017-05-11 10:38:48 -07:00
Kim Gardner c44eb373c3 Merge pull request #559 from coralproject/config-embed
Config via Embed - Add external config passed into the script tag
2017-05-11 12:56:47 -04:00
Kim Gardner 49d0c9bd10 Merge branch 'master' into config-embed 2017-05-11 12:40:11 -04:00
Benjamin Goering cb90802896 Fix bad merge when CommentBox calls setCommentCountCache 2017-05-11 09:34:33 -07:00
Kim Gardner 30bb954e44 Merge pull request #582 from coralproject/losowsky-patch-1
removed the beta tag
2017-05-11 12:17:41 -04:00
Kim Gardner 4cd2a15f35 Merge branch 'master' into losowsky-patch-1 2017-05-11 12:05:32 -04:00
Kim Gardner a1f3f550db Merge pull request #580 from coralproject/dont-unreport
remove ability to un-flag a comment
2017-05-11 11:42:15 -04:00
Andrew Losowsky 604894e43c removed the beta tag
we left beta a while ago!
2017-05-11 11:11:37 -04:00
Riley Davis ebf4f7ecc9 Merge branch 'master' into dont-unreport 2017-05-11 09:00:57 -06:00
riley 964b934efb Merge branch 'master' into multiple-roles 2017-05-11 08:51:06 -06:00
Wyatt Johnson db94158426 Merge pull request #581 from coralproject/license-update
Update LICENSE
2017-05-11 07:59:29 -06:00
Kim Gardner ef502bf7a8 Update LICENSE 2017-05-11 09:46:04 -04:00
Belén Curcio 06b90041af Merge branch 'master' into config-embed 2017-05-11 09:46:58 -03:00
Riley Davis c3048e599d remove ability to un-flag a comment 2017-05-10 20:57:20 -06:00
Kim Gardner 1314f1972a Merge branch 'master' into edit-comment 2017-05-10 15:25:27 -04:00
Belén Curcio 0c07e39cc0 Merge pull request #571 from coralproject/coral-reaction
Coral Reactions and Coral Plugin Love
2017-05-10 16:12:32 -03:00
Belen Curcio 57d2b81b05 missing _ 2017-05-10 16:05:52 -03:00
Belen Curcio a75070cf2e Merge branch 'config-embed' of github.com:coralproject/talk into config-embed 2017-05-10 16:05:16 -03:00
Belén Curcio 2d6c26887a Merge branch 'master' into config-embed 2017-05-10 16:05:04 -03:00
Belén Curcio 7626330ee1 Merge branch 'master' into coral-reaction 2017-05-10 16:02:01 -03:00
Belen Curcio 2fc54ac529 Merge branch 'master' of github.com:coralproject/talk into config-embed 2017-05-10 15:59:09 -03:00
Belen Curcio 10e8a32c21 Changes 2017-05-10 15:57:21 -03:00
Belen Curcio 0a2b26b599 Exports 2017-05-10 15:53:33 -03:00
David Erwin 4f74d28602 Merge pull request #579 from coralproject/action-fix
Patched bug with adding action
2017-05-10 14:39:25 -04:00
Belen Curcio c3dff79e6e Updated 2017-05-10 15:35:32 -03:00
riley 602c3d0be2 use a slot instead of trying to use facebook directly 2017-05-10 12:28:33 -06:00
Wyatt Johnson a3a8d23797 patched bug with adding action 2017-05-10 12:18:42 -06:00
Belen Curcio 7313aaa473 Merge branch 'config-embed' of github.com:coralproject/talk into config-embed 2017-05-10 15:09:19 -03:00
Belen Curcio 74ffc00c20 Adding changes 2017-05-10 15:02:43 -03:00
Belen Curcio 2673de5059 lintin 2017-05-10 14:59:10 -03:00
Belen Curcio 644d00d5d8 Adding coral-plugin-api 2017-05-10 14:57:24 -03:00
Riley Davis 75fb7b6dc2 Merge branch 'master' into user-detail-ui 2017-05-10 11:44:36 -06:00
Belén Curcio 07778844dc Merge branch 'master' into config-embed 2017-05-10 14:17:23 -03:00
Belen Curcio cb45e33b41 merge 2017-05-10 14:15:24 -03:00
Belen Curcio 26e5631f52 Changing path for hocs 2017-05-10 14:13:32 -03:00
Belen Curcio 3e1b7591e0 Adds displayName for WrappedReactions and alreadyReacted 2017-05-10 14:07:58 -03:00
Riley Davis 40ff265ab1 merge master 2017-05-10 11:07:48 -06:00
Belen Curcio 19bf7f6cf8 Adds displayName for WrappedReactions 2017-05-10 13:58:05 -03:00
Belén Curcio 67c202d89f Merge pull request #577 from coralproject/admin-slots
Admin Mod Queue Slots and Coral Plugin Mod example
2017-05-10 13:53:20 -03:00
Belen Curcio ad24ff78d2 Merge branch 'master' of github.com:coralproject/talk into coral-reaction 2017-05-10 13:46:11 -03:00
Wyatt Johnson f8b207e563 Return a meaningful error 2017-05-10 10:29:20 -06:00
Riley Davis fe279587b5 add staff to dropdown 2017-05-10 09:25:39 -06:00
Belén Curcio ca51821d7b Merge branch 'master' into config-embed 2017-05-10 12:01:29 -03:00
Belen Curcio 250a578dd7 lint, and merge conficts 2017-05-10 11:05:21 -03:00
Wyatt Johnson 17b9e43c14 moved webpack config 2017-05-09 16:53:30 -06:00
Wyatt Johnson 785e5a4ac8 Collected config 2017-05-09 16:49:11 -06:00
Benjamin Goering 650e8e205e Merge branch 'edit-comment' of github.com:gobengo/talk into edit-comment 2017-05-09 15:03:19 -07:00
Benjamin Goering f37a210479 Only use errors from errors.js for EditWindowHasEnded 2017-05-09 15:02:26 -07:00
Wyatt Johnson 76d0fdfd67 Added docs 2017-05-09 15:32:19 -06:00
Wyatt Johnson 3649605629 Merge branch 'master' into auth-tokens 2017-05-09 15:21:38 -06:00
Belén Curcio af89ef45f2 Merge branch 'master' into coral-reaction 2017-05-09 15:54:36 -03:00
Belen Curcio 8c219c1c4d Adding config with initial 2017-05-09 15:53:57 -03:00
Kim Gardner b81b789afa Merge branch 'master' into query-mutation-api 2017-05-09 14:29:27 -04:00
Kim Gardner fa8d96b6be Merge branch 'master' into edit-comment 2017-05-09 14:19:50 -04:00
Kim Gardner d8e7b25d0e Merge pull request #576 from coralproject/FE_Plugin_DOCS
FE Plugin Docs cleanup
2017-05-09 13:49:49 -04:00
Belen Curcio 52b0819e78 Adding example for plugins within the Admin Comment Queue 2017-05-09 14:02:35 -03:00
Benjamin Goering 994b5006b6 Merge branch 'master' into edit-comment 2017-05-09 09:12:14 -07:00
riley a187472d5c merge master 2017-05-09 10:11:04 -06:00
Kim Gardner a20bacd78d Merge branch 'master' into FE_Plugin_DOCS 2017-05-09 12:07:27 -04:00
Riley Davis 084f6b28ed merge master 2017-05-09 10:06:41 -06:00
Kim Gardner 87bc3e0a72 Merge pull request #565 from coralproject/approve-reject-noop
Don't redo actions on comments
2017-05-09 12:06:21 -04:00
Belen Curcio 3c06ee9360 Adds slots to admin stream 2017-05-09 12:21:21 -03:00
Belén Curcio 5b22bfbac2 Merge branch 'master' into coral-reaction 2017-05-09 11:40:30 -03:00
Belén Curcio c020008fad Merge branch 'master' into config-embed 2017-05-09 10:54:52 -03:00
Kiwi 4eb20108b1 Merge branch 'master' into approve-reject-noop 2017-05-09 20:06:11 +07:00
Kim Gardner 42db896d33 Merge branch 'master' into FE_Plugin_DOCS 2017-05-09 08:09:36 -04:00
Kim Gardner c0b526e98d Fix typo 2017-05-09 08:03:18 -04:00
Belen Curcio 743580fd08 Merge branch 'coral-reaction' of github.com:coralproject/talk into coral-reaction 2017-05-09 09:02:32 -03:00
Belen Curcio 788610a5f6 Like button params 2017-05-09 09:02:04 -03:00
Kim Gardner 3129828f83 Merge pull request #575 from coralproject/suspect-partial
do not highlight partial word matches in admin since this is not how the backend works
2017-05-09 07:53:11 -04:00
Belén Curcio 58ce5d5055 Merge branch 'master' into coral-reaction 2017-05-09 08:52:04 -03:00
Kiwi f9259ead50 Merge branch 'master' into query-mutation-api 2017-05-09 17:49:50 +07:00
Kiwi 6fb227eb72 Merge branch 'master' into approve-reject-noop 2017-05-09 17:49:30 +07:00
Benjamin Goering 8af7168f4f Merge branch 'master' into edit-comment 2017-05-08 19:26:38 -07:00
Riley Davis d77fcfe9de Merge branch 'master' into suspect-partial 2017-05-08 17:13:50 -06:00
Riley Davis 2490e65981 merge master 2017-05-08 17:09:23 -06:00
Riley Davis 242f3ea5ea do not highlight partial word matches in admin since this is not how the backend works 2017-05-08 17:01:36 -06:00
Wyatt Johnson e30bdd1cdc Merge branch 'master' into auth-tokens 2017-05-08 16:11:27 -06:00
Kim Gardner 5625f5b4b8 Merge pull request #566 from coralproject/context-from-comment
open the comment in context when clicked
2017-05-08 17:38:42 -04:00
Kim Gardner b2c8e3b254 Merge branch 'master' into context-from-comment 2017-05-08 17:34:55 -04:00
Kim Gardner 3dff07de40 Merge pull request #572 from coralproject/mod-is-main-tab
default view is moderate > all
2017-05-08 17:34:43 -04:00
Kim Gardner 19d7a3323e Merge branch 'master' into mod-is-main-tab 2017-05-08 17:30:56 -04:00
Wyatt Johnson dc86b87a62 Merge pull request #574 from coralproject/sub-fix
Fix for incorrect options passed to pubsub
2017-05-08 15:29:56 -06:00
Kim Gardner 6dd01b4334 Merge branch 'master' into mod-is-main-tab 2017-05-08 17:26:55 -04:00
Wyatt Johnson b6e3796afe Merge branch 'master' into sub-fix 2017-05-08 15:25:57 -06:00
Kim Gardner 34ee1c56ea Merge pull request #573 from coralproject/move-queues
move approved tab to be next to rejected tab
2017-05-08 17:25:47 -04:00
Wyatt Johnson 85d634c221 Merge branch 'master' into sub-fix 2017-05-08 15:21:43 -06:00
Belén Curcio 513a841aac Merge branch 'master' into coral-reaction 2017-05-08 18:19:05 -03:00
Belen Curcio 88861754f1 Adding full example with translations 2017-05-08 18:05:54 -03:00
Belen Curcio fe58a5076e Adding coral-plugin-love 2017-05-08 18:05:39 -03:00
Riley Davis 3e258b1787 move approved tab 2017-05-08 14:59:23 -06:00
Belen Curcio b82cab0655 Lint 2017-05-08 17:50:15 -03:00
Belen Curcio b547ad44f1 Better and more friendly API 2017-05-08 17:46:56 -03:00
Kim Gardner e04bf7f9bc Merge branch 'master' into approve-reject-noop 2017-05-08 16:22:42 -04:00
David Erwin 50d832c2f8 Remove pseudocode 2017-05-08 16:21:24 -04:00
Kim Gardner 0b06c233a8 Merge pull request #523 from coralproject/FE_Plugin_DOCS
Frontend Plugins DOCS
2017-05-08 16:20:27 -04:00
Kim Gardner 7a3b36433a Merge branch 'master' into FE_Plugin_DOCS 2017-05-08 16:16:34 -04:00
Belén Curcio d478910a81 Merge pull request #570 from coralproject/add-supported-browsers-docs
Supported Browsers (docs)
2017-05-08 17:15:16 -03:00
Chi Vinh Le 028e13761f Complete graphql code refactor in framework 2017-05-09 03:05:19 +07:00
Chi Vinh Le 57e7396997 Use correct fragment name 2017-05-09 02:58:50 +07:00
Chi Vinh Le 80d08bbe2e Port StopIgnoringUser Mutation 2017-05-09 02:50:52 +07:00
Chi Vinh Le 5c44922d2b Port IgnoreUser mutation 2017-05-09 02:46:42 +07:00
Chi Vinh Le 7077300dbc Fix refetchQueries 2017-05-09 02:45:28 +07:00
Riley Davis c00e6c1ece default view is moderate > all 2017-05-08 13:40:24 -06:00
Wyatt Johnson bc3c73a992 Fix for incorrect options passed to pubsub 2017-05-08 13:26:07 -06:00
Chi Vinh Le 774a6f68ac Port RemoveCommentTag Mutation 2017-05-09 02:22:44 +07:00
Chi Vinh Le b87c36078a Port AddCommentTag Mutation 2017-05-09 02:19:55 +07:00
Belen Curcio 6934263f71 Ádding uuid v4 2017-05-08 15:59:52 -03:00
Belen Curcio cd18a7e76f Fully working. replaced typename by typereaction. Thanks kiwi 😂 2017-05-08 15:51:17 -03:00
Belen Curcio b7de01b0ad no console 2017-05-08 15:47:08 -03:00
Belen Curcio 011e5da26f Almost fully working 2017-05-08 15:44:11 -03:00
Chi Vinh Le 0e931d023d Port DeleteAction Mutation 2017-05-09 01:15:07 +07:00
Benjamin Goering 295de61f36 Merge branch 'master' into edit-comment 2017-05-08 10:00:58 -07:00
Benjamin Goering 096025b72a Add asset_id to editComment mutation to allow mongo query happy path to be atomic 2017-05-08 09:57:52 -07:00
David Erwin e2ed21e113 Adding building block documentation 2017-05-08 12:00:22 -04:00
Chi Vinh Le 83ef41000c Port PostDontAgree mutation 2017-05-08 22:56:01 +07:00
Chi Vinh Le 10796ed4b3 Port PostFlag mutation 2017-05-08 22:35:59 +07:00
Chi Vinh Le 7f0b3c2ddd Merge branch 'master' into query-mutation-api
Conflicts:
	client/coral-embed-stream/src/components/Comment.js
	client/coral-embed-stream/src/containers/Stream.js
2017-05-08 21:44:35 +07:00
David Erwin b019c5e180 Separate supported and experimental plugin guildes 2017-05-08 10:36:36 -04:00
Belen Curcio a5f6e1f343 Adding coral-reaction 2017-05-08 11:32:46 -03:00
Chi Vinh Le 4c17cec4fd Lazily resolve fragments in queries and mutations 2017-05-08 21:30:25 +07:00
Chi Vinh Le ce38b9da64 Rename argument name 2017-05-08 19:41:14 +07:00
Belén Curcio c920c7e9bc Merge branch 'master' into FE_Plugin_DOCS 2017-05-08 08:27:04 -03:00
Benjamin Goering 81879c9bb9 Move getEditableUntilDate into util file 2017-05-08 00:05:14 -07:00
Benjamin Goering 9fb76d35c8 shorten some syntax that could be shorter 2017-05-07 23:54:51 -07:00
Benjamin Goering a7099295d6 CountdownSeconds component has its own file 2017-05-07 23:47:59 -07:00
Benjamin Goering 3d8c521928 CommentForm component is in its own file 2017-05-07 23:44:41 -07:00
Wyatt Johnson 95f9bac254 Initial migration to JWT 2017-05-05 15:42:34 -06:00
Kim Gardner 70bb6f6632 Merge branch 'master' into approve-reject-noop 2017-05-05 16:53:14 -04:00
Kim Gardner 19d62a8c63 Merge branch 'master' into add-supported-browsers-docs 2017-05-05 16:46:21 -04:00
Kim Gardner b15a2af38c Update formatting of License info 2017-05-05 16:45:15 -04:00
Riley Davis 4ff42385e3 remove dead code 2017-05-05 14:41:19 -06:00
Kim Gardner dc81a59908 Supported Browsers 2017-05-05 16:40:17 -04:00
Kim Gardner fe5ddbe323 Merge pull request #569 from coralproject/ui-overlaps
Apply button overlapped text
2017-05-05 16:16:06 -04:00
Riley Davis f8e8eef989 float: right; in css is just fine. 2017-05-05 14:05:52 -06:00
Riley Davis 9c4b636645 Merge branch 'master' into user-detail-ui 2017-05-05 13:56:45 -06:00
Riley Davis 8212a7e11b Merge branch 'master' into approve-reject-noop 2017-05-05 13:56:38 -06:00
Riley Davis 3fc19df240 Merge branch 'master' into context-from-comment 2017-05-05 13:56:28 -06:00
Chi Vinh Le 13e2382cd7 More code comments 2017-05-06 02:33:23 +07:00
Wyatt Johnson d0cca476e6 Merge pull request #568 from coralproject/user-fix
Added privacy fix
2017-05-05 13:19:59 -06:00
Wyatt Johnson 7ccf9552c2 Added privacy fix 2017-05-05 13:14:34 -06:00
Riley Davis 519ca7beb5 en español 2017-05-05 11:55:28 -06:00
Riley Davis 5db48e8c0f make it a blue link instead of the whole thing 2017-05-05 11:35:18 -06:00
Chi Vinh Le 1b7eedfd8d Implement extendable GraphQL framework 2017-05-05 22:51:19 +07:00
Chi Vinh Le 8b3f69d964 Use getDisplayName from recompose 2017-05-05 22:40:54 +07:00
Riley Davis 5f9240f201 open the comment in context when clicked 2017-05-04 16:32:07 -06:00
Riley Davis f3f0b9c858 change mouse cursor for disabled buttons 2017-05-04 15:55:46 -06:00
Riley Davis 4507059a04 same thing for BanUserDialog and the keyboard shortcuts 2017-05-04 15:43:23 -06:00
Riley Davis 67b17160cb don't accept/reject when a comment is already accepted/rejected respectively 2017-05-04 14:42:43 -06:00
riley 57b0b5ec67 add a drawer for the user details 2017-05-04 13:58:43 -06:00
Riley Davis d255ea84b1 merge master 2017-05-04 11:19:01 -06:00
Belén Curcio 04812797a0 Merge branch 'master' into config-embed 2017-05-04 13:40:39 -03:00
Benjamin Goering 636d29af9f When max char count is disabled, allow posting long comments in CommentBox 2017-05-03 19:41:29 -07:00
Benjamin Goering 9b94406763 Remove old comments 2017-05-03 19:32:42 -07:00
Benjamin Goering 9b659b024e Add spanish translations for 2 new error strings 2017-05-03 19:27:32 -07:00
Benjamin Goering b97fa228b1 merge with master after huge coral-embed-stream refactor 2017-05-03 19:19:03 -07:00
Benjamin Goering 0ba973fb44 editComment returns status of comment after edit. UI shows appropriate notifications if PREMOD or REJECTED 2017-05-03 18:05:06 -07:00
Benjamin Goering 3ac8f09619 Notify on unexpected error when saving edit changes 2017-05-03 17:31:36 -07:00
Benjamin Goering ab9c2a89b7 Edit Window countdown is bold when < 10 seconds left 2017-05-03 17:27:36 -07:00
Benjamin Goering e31961716e Remove done todo comments 2017-05-03 17:14:36 -07:00
Benjamin Goering b83381719c Style editCommentWindow messages and i18n 2017-05-03 15:37:02 -07:00
Kim Gardner 5168f563ef Merge pull request #557 from coralproject/admin-queue-polish
load more comments to moderate if available
2017-05-03 18:02:35 -04:00
Kim Gardner 7d0d4aa397 Merge branch 'master' into admin-queue-polish 2017-05-03 17:57:34 -04:00
Kim Gardner 4f2126ef74 Merge pull request #562 from coralproject/flag-queue-loadmore
Flag queue Load More fix
2017-05-03 17:35:53 -04:00
Benjamin Goering 92fd146a8c Add cancel button to editing CommentForm 2017-05-03 14:22:15 -07:00
Benjamin Goering c3f3ed8a50 Comment edit is no longer extended one ach edit 2017-05-03 14:17:36 -07:00
Benjamin Goering 3cb84222b2 Add new padding, color, border-color to CommentBox textarea 2017-05-03 14:14:23 -07:00
Benjamin Goering 818c09a9b8 Edit window is now 30s instead of 5min 2017-05-03 14:06:07 -07:00
Benjamin Goering c8e7ec6cbf streamQuery reduces editComment mutations to reflect edits without contacting server 2017-05-03 13:35:51 -07:00
riley d02c82d0cd Merge branch 'master' into admin-queue-polish 2017-05-03 14:25:46 -06:00
riley 26ebdde244 Merge branch 'master' into user-detail-ui 2017-05-03 14:25:40 -06:00
Riley Davis 1cecad4d32 action_type must be passed to the load more query 2017-05-03 14:13:58 -06:00
Belén Curcio 64c79cf5f2 Merge branch 'master' into config-embed 2017-05-03 16:52:30 -03:00
Riley Davis 6b4505fa3d Merge branch 'master' into flag-queue-loadmore 2017-05-03 13:42:25 -06:00
Riley Davis f03c87c26d Merge pull request #561 from coralproject/like-fix
Removed LikeAction from core typeName interface resolver
2017-05-03 13:42:01 -06:00
riley c15c312c2c typo 2017-05-03 13:35:33 -06:00
Benjamin Goering ae3cbe990b Edit Comment UI reflects expired edit comment window 2017-05-03 12:34:22 -07:00
Wyatt Johnson d9a4653484 Removed LikeAction from core typeName interface resolver 2017-05-03 13:31:57 -06:00
Riley Davis e993328dc4 load only flagged comments in mod queue load more 2017-05-03 13:15:29 -06:00
riley 4a42436b7b load user via HOC UserDetail 2017-05-03 12:54:02 -06:00
Belén Curcio 15afce516c Merge pull request #556 from coralproject/coral-plugin-like
Coral Plugin Like
2017-05-03 15:50:09 -03:00
riley 3c6391e741 count should never be less than 0 2017-05-03 12:49:22 -06:00
riley 39bd0eca6f remove pointless change 2017-05-03 12:45:49 -06:00
Belen Curcio 99b3d0021a Linting 2017-05-03 15:45:31 -03:00
Belén Curcio 6716362b08 Merge branch 'master' into config-embed 2017-05-03 15:40:27 -03:00
Belén Curcio 68ea670ecd Merge branch 'master' into FE_Plugin_DOCS 2017-05-03 15:40:14 -03:00
Belen Curcio 13a6ee67e9 Merge branch 'coral-plugin-like' of github.com:coralproject/talk into coral-plugin-like 2017-05-03 15:39:41 -03:00
Belen Curcio 32fb0714ce updated tests 2017-05-03 15:37:56 -03:00
Belén Curcio 7cc8176cc4 Merge branch 'master' into coral-plugin-like 2017-05-03 15:31:04 -03:00
riley 354b51b4fe use a reduce instead 2017-05-03 11:14:21 -06:00
Riley Davis ae52ec983e Merge remote-tracking branch 'origin/admin-queue-polish' into admin-queue-polish 2017-05-03 10:55:43 -06:00
Riley Davis c78e00711a remove if statements 2017-05-03 10:54:52 -06:00
Riley Davis b8ddade518 Merge branch 'master' into admin-queue-polish 2017-05-03 10:28:52 -06:00
Riley Davis 324e82c070 not pretty, but it works 2017-05-03 10:20:03 -06:00
Kim Gardner e6651086d5 Merge pull request #560 from coralproject/bump-version-1-6-0
Bump version to 1.6.0
2017-05-03 12:17:51 -04:00
Kim Gardner 28ea498df2 Bump version to 1.6.0 2017-05-03 12:12:25 -04:00
riley 9e1b470cb5 saving my place 2017-05-03 09:32:05 -06:00
Benjamin Goering b9a3466e2e Edit UI sends editComment mutation, handles errors 2017-05-02 22:07:23 -05:00
riley e60bcb9caf make sure load more on approve/reject queues doesn't cause a bug 2017-05-02 15:58:57 -06:00
Benjamin Goering aa18a433f8 Can't edit comment outside of edit window 2017-05-02 16:47:00 -05:00
riley b7d3113677 apparently you can't make a fragment on RootQuery 2017-05-02 15:18:48 -06:00
Benjamin Goering 756b5f8b45 editComment only allows editing real comments authored by the requesting user 2017-05-02 16:09:09 -05:00
Benjamin Goering 860030a57c Test some editComment state transitions 2017-05-02 15:47:32 -05:00
Benjamin Goering a46773887f Initial working editComment mutation 2017-05-02 14:27:16 -05:00
Belén Curcio ea7888809e Merge branch 'master' into config-embed 2017-05-02 15:47:23 -03:00
Belen Curcio cc0a23c295 Deconstruct plugin config 2017-05-02 15:40:37 -03:00
Belen Curcio b444316e1c Adding debug plugins 2017-05-02 15:31:58 -03:00
Belen Curcio 17128e9a47 Adds redux to handle external config data 2017-05-02 15:18:04 -03:00
Belen Curcio f0c846493a Passing data from script tag, into Talk 2017-05-02 14:51:09 -03:00
Kim Gardner f6e2caf65f Merge branch 'master' into admin-queue-polish 2017-05-02 12:38:48 -04:00
Kim Gardner cdc1835f72 Merge branch 'master' into coral-plugin-like 2017-05-02 12:37:55 -04:00
Kim Gardner 9d2b47f02b Merge pull request #549 from coralproject/count-abbr
format mod queue counts
2017-05-02 12:37:34 -04:00
Benjamin Goering 00a2a65d31 Edit Comment UI 2017-05-01 16:58:24 -05:00
Riley Davis da5e8cda99 Merge branch 'master' into admin-queue-polish 2017-05-01 11:13:13 -06:00
Riley Davis 32dd554331 load more comments if the mod has moderated them all and there are more availabile 2017-05-01 10:30:18 -06:00
Riley Davis 8293408c52 pull some custom code out of ActionButton 2017-05-01 10:22:11 -06:00
Belen Curcio 23a5b236a2 Linting 2017-05-01 12:05:45 -03:00
Belen Curcio b02088e329 Like color 2017-05-01 12:04:14 -03:00
Belen Curcio 1a50b6cc85 Ádding like as a reaction 2017-05-01 12:00:44 -03:00
Belen Curcio f1bebaa627 Merge 2017-05-01 11:54:00 -03:00
Belen Curcio 1250054fb7 Prettier 2017-05-01 11:45:23 -03:00
Belen Curcio f3980a03b2 Removing like from core action summary 2017-05-01 11:44:24 -03:00
Belen Curcio a90802a046 Coral-Plugin-Like 2017-05-01 11:43:59 -03:00
Belen Curcio 400615cc58 Adding translations 2017-05-01 11:43:40 -03:00
David Erwin a94f6220f9 Merge branch 'master' into count-abbr 2017-05-01 10:40:20 -04:00
Belén Curcio c65467fedf Merge branch 'master' into FE_Plugin_DOCS 2017-05-01 11:28:15 -03:00
Belén Curcio f509d29a94 Merge pull request #540 from coralproject/slots-b
Slots
2017-05-01 11:28:02 -03:00
Belen Curcio 8bed64373e Removing LIKE from talk 2017-05-01 11:15:53 -03:00
Belen Curcio 322434f751 Removing LIKE from talk 2017-05-01 11:13:35 -03:00
Belen Curcio 9ee749edb2 Removing LIKE from the core graph 2017-05-01 11:00:37 -03:00
Belen Curcio 5c14ac3f67 Ádding LIKE to the plugin graph 2017-05-01 10:58:29 -03:00
Belen Curcio e1929f148a Removing likes from the core graph 2017-05-01 10:58:14 -03:00
Belen Curcio 717ebf40b7 Adding like plugin to the ignore 2017-05-01 10:56:10 -03:00
Belen Curcio ef40174be9 Adding Coral-Plugin-Like 2017-05-01 10:55:50 -03:00
Belen Curcio 3b819144e0 Merge branch 'slots-b' of github.com:coralproject/talk into slots-b 2017-05-01 10:48:04 -03:00
Belen Curcio c58813cf48 merge 2017-05-01 10:47:56 -03:00
Belen Curcio d14b965f54 Adding fragment slots 2017-05-01 10:46:37 -03:00
Belén Curcio ed88a59ef1 Merge branch 'master' into slots-b 2017-05-01 10:41:21 -03:00
Kim Gardner ffcb1b464f Merge pull request #548 from coralproject/action-count-fix
don't double count no-ops
2017-04-28 15:13:37 -04:00
Kim Gardner fd11cc5249 Merge branch 'master' into action-count-fix 2017-04-28 15:09:44 -04:00
Kim Gardner 9b155833a1 Merge branch 'master' into count-abbr 2017-04-28 15:07:18 -04:00
Kim Gardner 2484141ecf Merge pull request #553 from coralproject/load-more-accepted
enable load more on accepted queue
2017-04-28 15:07:06 -04:00
Kim Gardner 78ea42f34f Merge branch 'master' into load-more-accepted 2017-04-28 15:03:02 -04:00
Kim Gardner 3a1e78579a Merge pull request #554 from coralproject/yarn-fix
Updated yarn.lock
2017-04-28 15:02:48 -04:00
Riley Davis c85596a9bf Merge branch 'master' into load-more-accepted 2017-04-28 12:59:01 -06:00
David Erwin 211a6fd868 Merge branch 'master' into yarn-fix 2017-04-28 14:58:02 -04:00
David Erwin 59b048002b Merge pull request #555 from coralproject/fix-more-reply-count
Fix reply count polling
2017-04-28 14:57:50 -04:00
Kim Gardner 5506481182 Merge branch 'master' into load-more-accepted 2017-04-28 14:57:07 -04:00
Kim Gardner 12e6501d4d Merge branch 'master' into fix-more-reply-count 2017-04-28 14:51:01 -04:00
Kim Gardner 7a53cab2f5 Merge branch 'master' into action-count-fix 2017-04-28 14:44:28 -04:00
Kim Gardner cfbff62d1c Merge pull request #550 from coralproject/losowsky-patch-1
tiny typo that was bugging me
2017-04-28 14:43:41 -04:00
Wyatt Johnson 5d4c813800 removed empty gif 2017-04-28 12:03:29 -06:00
Chi Vinh Le e486d48556 postComment should increase replyCount 2017-04-29 01:02:59 +07:00
Chi Vinh Le 8663aa0cb5 Fix polling reply count 2017-04-29 01:02:11 +07:00
Wyatt Johnson f1e70102d0 Updated yarn.lock 2017-04-28 11:55:28 -06:00
Riley Davis 41d4bd1012 counts update 2017-04-28 11:52:07 -06:00
riley ace3e2398b enable load more on accepted queue 2017-04-28 11:41:03 -06:00
riley 521082eb3e add new query for counts 2017-04-28 11:39:37 -06:00
Riley Davis a6dd03beef Merge branch 'master' into action-count-fix 2017-04-28 10:45:34 -06:00
Riley Davis 7246adc467 Merge branch 'master' into count-abbr 2017-04-28 10:45:27 -06:00
Kim Gardner 4d0b5b7149 Merge branch 'master' into losowsky-patch-1 2017-04-28 11:24:27 -04:00
Kiwi bfb2eb30e2 Merge pull request #552 from coralproject/fix-empty-slot-fragments
Ret empty string when encountering undefined fragment
2017-04-28 22:04:44 +07:00
Chi Vinh Le 5d26f8e3de Ret empty string when encountering undefined fragment 2017-04-28 21:59:02 +07:00
Andrew Losowsky a46c8528e1 tiny typo that was bugging me 2017-04-28 07:19:44 -04:00
Kim Gardner e3d5850af6 Merge branch 'master' into FE_Plugin_DOCS 2017-04-27 18:05:59 -04:00
riley cd9648b251 format mod queue counts 2017-04-27 15:01:59 -06:00
riley 59ddde1856 semi 2017-04-27 14:07:01 -06:00
riley 862355b0cd don't double count no-ops 2017-04-27 14:02:29 -06:00
Kim Gardner 78878b4295 Merge pull request #539 from coralproject/approved-comments-queue
Approved comments queue
2017-04-27 15:28:00 -04:00
Riley Davis b1d4fff929 Merge branch 'master' into approved-comments-queue 2017-04-27 13:12:40 -06:00
riley 8cf11d83c8 Merge branch 'approved-comments-queue' of github.com:coralproject/talk into approved-comments-queue 2017-04-27 13:10:50 -06:00
riley a3ec23c273 mange counts and such for all queue 2017-04-27 13:09:00 -06:00
Kim Gardner af9f143b31 Merge pull request #547 from coralproject/lock-fix
Updated yarn.lock
2017-04-27 15:05:52 -04:00
Wyatt Johnson 48bffdbb3b Updated yarn.lock 2017-04-27 13:00:30 -06:00
Kiwi 8f1448823c Merge pull request #543 from coralproject/no-logic-user-box
Remove embed stream logic from UserBox
2017-04-28 01:07:56 +07:00
Kim Gardner 6a46fb822d Merge branch 'master' into approved-comments-queue 2017-04-27 13:56:15 -04:00
Kiwi b186a74199 Merge branch 'master' into no-logic-user-box 2017-04-28 00:55:50 +07:00
David Erwin e96c5bbc8f Merge pull request #542 from coralproject/optimise-initial-query
Only query once during initial render
2017-04-27 13:53:56 -04:00
David Erwin 06378209a2 Merge branch 'master' into optimise-initial-query 2017-04-27 13:49:12 -04:00
riley f6cede3fda add accepted to propTypes 2017-04-27 11:23:27 -06:00
riley d5b800f590 add accepted rejected aciton buttons 2017-04-27 11:17:53 -06:00
Riley Davis 8cade04431 Merge branch 'master' into approved-comments-queue 2017-04-27 11:07:43 -06:00
David Erwin efda925efc Merge pull request #546 from coralproject/sub-fix
Moved subscription dep to devDeps
2017-04-27 12:53:04 -04:00
Wyatt Johnson ec915208e5 Moved dep to devDeps 2017-04-27 10:48:03 -06:00
Kim Gardner d069cf9d8e Merge branch 'master' into FE_Plugin_DOCS 2017-04-27 11:59:39 -04:00
Kim Gardner af80c4d8ad Merge branch 'master' into approved-comments-queue 2017-04-27 11:59:30 -04:00
Kim Gardner 1de3614fe8 Merge branch 'master' into slots-b 2017-04-27 11:59:23 -04:00
Kim Gardner 663bce2875 Merge branch 'master' into no-logic-user-box 2017-04-27 11:59:08 -04:00
Kim Gardner d0090283c0 Merge pull request #535 from coralproject/report-popup-fix
position the reporting popup in a more reliable way
2017-04-27 11:58:43 -04:00
Chi Vinh Le 8c53bc2d22 Postpone first query until checked login 2017-04-27 22:55:36 +07:00
Kim Gardner f47bd269e5 Merge branch 'master' into report-popup-fix 2017-04-27 11:48:17 -04:00
Kim Gardner 7a99c9358a Merge pull request #533 from coralproject/143530761-remove-loading-when-searching
Only shows the Loading component when there is no search value
2017-04-27 11:47:50 -04:00
Riley Davis 6330d69649 Merge branch 'master' into approved-comments-queue 2017-04-27 09:42:28 -06:00
Kim Gardner 6e98d176e7 Merge branch 'master' into 143530761-remove-loading-when-searching 2017-04-27 11:41:51 -04:00
Kim Gardner 7b84b3daf1 Merge pull request #537 from coralproject/profanity-verbiage
update bad username copy
2017-04-27 11:41:24 -04:00
Riley Davis 40b7669ed8 Merge branch 'master' into approved-comments-queue 2017-04-27 09:40:30 -06:00
Kim Gardner 3bb994c562 Merge branch 'master' into profanity-verbiage 2017-04-27 11:35:47 -04:00
Belén Curcio fab14d896b Merge branch 'master' into FE_Plugin_DOCS 2017-04-27 12:32:20 -03:00
Belén Curcio 9c97d85859 Merge branch 'master' into slots-b 2017-04-27 12:31:07 -03:00
Riley Davis ea8e469c1c Merge branch 'master' into report-popup-fix 2017-04-27 09:28:00 -06:00
David Erwin 6e3159c7e1 Merge pull request #545 from coralproject/docker-fix
Fixed Dockerfile for regression with yarn
2017-04-27 10:34:26 -04:00
Wyatt Johnson 5559940967 Fixed dockerfile temp, updated yarn.lock 2017-04-27 08:29:16 -06:00
David Erwin 64eb16c3d6 Merge pull request #519 from coralproject/more-fragment-matcher
more matching on the fragment
2017-04-27 10:17:58 -04:00
Belén Curcio f779c5fe01 Merge branch 'master' into no-logic-user-box 2017-04-27 11:12:55 -03:00
David Erwin 6972b87b0b Merge branch 'master' into more-fragment-matcher 2017-04-27 10:10:19 -04:00
Belén Curcio 8c320d09da Merge branch 'master' into optimise-initial-query 2017-04-27 11:04:11 -03:00
David Erwin 279ff0655f Merge pull request #544 from coralproject/login-ios-safari
More reliable login support including IOS browsers
2017-04-27 10:03:29 -04:00
Chi Vinh Le 794cc0fc6c Support login on all IOS browsers 2017-04-27 19:28:54 +07:00
Chi Vinh Le 7b16a71e9b Remove embed stream logic from UserBox 2017-04-27 18:20:55 +07:00
Chi Vinh Le 20b4c4e2d0 Only query once during initial render 2017-04-27 17:30:43 +07:00
Belen Curcio 37e093f60a Linting 2017-04-26 23:15:40 -03:00
Belen Curcio de6492a26d Ádding reactions, commentContent and updating current Slot 2017-04-26 23:06:09 -03:00
Belen Curcio c60db41047 Updating slot name 2017-04-26 23:04:22 -03:00
Belen Curcio 00fe4b7c83 Úpdating slot names 2017-04-26 23:03:51 -03:00
Belen Curcio e0b177d955 Updated slot name 2017-04-26 23:02:09 -03:00
Belen Curcio 29e398c51d Rename Slot to commentInputDetailArea in Coral Plugin Offtopic 2017-04-26 22:37:37 -03:00
Belen Curcio 3dd2da96e8 Adding Slots to QuestionBox, ref 2017-04-26 22:35:30 -03:00
Belen Curcio 1fea1405ef Adding Slots, CommentBox ref 2017-04-26 22:34:38 -03:00
Belen Curcio 9fa3e361b5 Ref Stream 2017-04-26 22:34:17 -03:00
Belen Curcio 06ba5a53c1 Stateless Slot 2017-04-26 22:33:49 -03:00
David Erwin bb532b5eb2 Merge pull request #531 from coralproject/shortcuts-in-cog
shortcuts list should be accessible from the cog
2017-04-26 17:41:04 -04:00
David Erwin 5eb26946cb Merge branch 'master' into profanity-verbiage 2017-04-26 17:21:34 -04:00
David Erwin 81680442f0 Merge branch 'master' into shortcuts-in-cog 2017-04-26 17:20:37 -04:00
David Erwin c86f9877f3 Merge pull request #534 from coralproject/143202319-mod-queue-shortcuts
Recover the scroll.
2017-04-26 17:20:17 -04:00
David Erwin 1b3868edec Merge branch 'master' into 143202319-mod-queue-shortcuts 2017-04-26 17:13:46 -04:00
David Erwin 4936003f5f Merge pull request #536 from coralproject/fix-case
make Comment comment
2017-04-26 17:13:28 -04:00
David Erwin 5c4696ed61 Merge branch 'master' into fix-case 2017-04-26 17:08:56 -04:00
David Erwin bf898fbaec Merge pull request #460 from coralproject/subscriptions
Subscriptions
2017-04-26 17:05:58 -04:00
David Erwin 41065ba24a Merge branch 'master' into subscriptions 2017-04-26 17:01:27 -04:00
David Erwin 0e77c93ae5 Merge pull request #521 from coralproject/user-metadata
Added user metadata
2017-04-26 17:01:15 -04:00
David Erwin 2756b794ee Merge branch 'master' into more-fragment-matcher 2017-04-26 16:57:37 -04:00
David Erwin 626f5cd1e1 Merge branch 'master' into user-metadata 2017-04-26 16:56:50 -04:00
David Erwin 077daeb793 Merge branch 'master' into subscriptions 2017-04-26 16:55:32 -04:00
Riley Davis fc567e8a92 Merge branch 'master' into shortcuts-in-cog 2017-04-26 13:34:13 -06:00
gaba 60d2735fa5 Remove isfetching all together. 2017-04-26 14:26:22 -05:00
gaba 6f58754617 Removing the Loading all together. 2017-04-26 14:26:21 -05:00
Gabriela Rodríguez Berón e935fff8ac Merge branch 'master' into 143530761-remove-loading-when-searching 2017-04-26 12:52:30 -05:00
Gabriela Rodríguez Berón 3ad8d9ffec Merge branch 'master' into 143202319-mod-queue-shortcuts 2017-04-26 12:51:30 -05:00
Riley Davis 0df40c7e35 make the popup not full width like before 2017-04-26 11:50:17 -06:00
Riley Davis a793d63f65 Merge branch 'master' into profanity-verbiage 2017-04-26 11:45:20 -06:00
Riley Davis ccc512cddd if there is no available translation, do not show error.whatever in the UI 2017-04-26 11:43:34 -06:00
Benjamin Goering 84016e362d Delete Comment UI 2017-04-26 12:37:33 -05:00
David Erwin e20b6e1b25 Merge branch 'master' into fix-case 2017-04-26 13:19:50 -04:00
Riley Davis 4ff37d9063 Merge branch 'master' into approved-comments-queue 2017-04-26 11:01:41 -06:00
David Erwin c02890af00 Merge pull request #526 from coralproject/query-composition
Query composition
2017-04-26 12:58:51 -04:00
Benjamin Goering add7bfc170 Remove commented out css from TopRightMenu.css 2017-04-26 10:33:24 -05:00
Riley Davis cea0b4942a prevent text overflow 2017-04-26 09:32:00 -06:00
Riley Davis 4ddaca7655 español 2017-04-26 09:20:23 -06:00
Riley Davis 03e0c62b72 accepted => approved 2017-04-26 08:45:37 -06:00
Riley Davis a21d73dcad accepted queue 2017-04-26 08:26:29 -06:00
Kiwi b20d8c18a3 Merge branch 'master' into report-popup-fix 2017-04-26 21:22:23 +07:00
David Erwin 85674b3ef5 Merge branch 'master' into query-composition 2017-04-26 10:09:14 -04:00
David Erwin 80f695e23d Merge branch 'master' into profanity-verbiage 2017-04-26 09:53:19 -04:00
David Erwin 8b4c2876c7 Merge pull request #538 from coralproject/ios-safari-full-width
Safari IOS ignores width=100% in iframes
2017-04-26 09:44:01 -04:00
Chi Vinh Le d56c35ea64 Merge branch 'master' into query-composition 2017-04-26 19:28:46 +07:00
Chi Vinh Le 9ff2ccf1ed Safari IOS ignores width=100% in iframes 2017-04-26 18:52:30 +07:00
riley 0807663965 add z-index to popup 2017-04-25 16:31:33 -06:00
riley f296fe2abd update bad username copy 2017-04-25 16:22:31 -06:00
Kim Gardner 2654e3d452 Merge branch 'master' into user-metadata 2017-04-25 18:21:45 -04:00
Kim Gardner 2c2c2dc604 Merge pull request #502 from coralproject/all-comments-queue
All comments queue
2017-04-25 18:20:36 -04:00
riley aad66ec7e0 fix casing 2017-04-25 16:18:21 -06:00
Kim Gardner 94e429e431 Merge branch 'master' into all-comments-queue 2017-04-25 18:16:01 -04:00
Riley Davis 7db5e4d68b Merge branch 'master' into report-popup-fix 2017-04-25 16:14:56 -06:00
riley f3822d09b2 position the reporting popup in a more reliable way 2017-04-25 15:54:18 -06:00
Gabriela Rodríguez Berón 9e35b7b39f Merge branch 'master' into 143202319-mod-queue-shortcuts 2017-04-25 16:10:23 -05:00
gaba 33d71df049 Recover the scroll. 2017-04-25 15:52:52 -05:00
David Erwin 7d0aac5d4a Merge pull request #532 from coralproject/fix-view-more
Fix: View More working with asset_id
2017-04-25 16:01:38 -04:00
gaba 10cd53b40b Only shows the Loading component when there is no search value 2017-04-25 14:28:37 -05:00
David Erwin d4c4b5a3e9 Merge branch 'master' into fix-view-more 2017-04-25 15:26:20 -04:00
Riley Davis b88cbe30de have all the comments show up in All Queue with proper statuses 2017-04-25 13:23:39 -06:00
Belen Curcio 9c448d5143 View More working with asset_id 2017-04-25 16:17:10 -03:00
Chi Vinh Le cb5228a423 Document getSlotFragments 2017-04-26 01:58:07 +07:00
Kim Gardner bd91f898f5 Merge branch 'master' into FE_Plugin_DOCS 2017-04-25 14:54:29 -04:00
Kim Gardner 4f70a24a32 Merge branch 'master' into subscriptions 2017-04-25 14:54:07 -04:00
Kim Gardner 62835acc76 Merge branch 'master' into user-metadata 2017-04-25 14:53:30 -04:00
riley 67301e6e85 Merge branch 'comment-status-display' into all-comments-queue 2017-04-25 12:52:32 -06:00
riley ad4386b235 Merge branch 'all-comments-queue' into comment-status-display 2017-04-25 12:51:20 -06:00
Riley Davis a94e6a0ada have default...just in case 2017-04-25 12:40:28 -06:00
Riley Davis 89f0663723 shortcuts list should be accessible from the cog 2017-04-25 12:38:00 -06:00
Chi Vinh Le eb9846ba40 Merge branch 'master' into query-composition
Conflicts:
	client/coral-embed-stream/src/Embed.js
	client/coral-embed-stream/src/Stream.js
2017-04-26 01:22:14 +07:00
Chi Vinh Le 9d7524644c Merge branch 'master' into query-composition 2017-04-26 01:11:07 +07:00
Chi Vinh Le f142f3a27f Introduce separateDataAndRoot 2017-04-26 01:10:16 +07:00
Chi Vinh Le df2c33ac35 Introduce getDefinitionName 2017-04-26 01:07:07 +07:00
Riley Davis 5666c4ac90 condense some var definitions 2017-04-25 11:57:36 -06:00
Riley Davis 37ac5157dd Merge branch 'master' into comment-status-display 2017-04-25 11:45:27 -06:00
Riley Davis 5db9ea6520 Merge pull request #520 from coralproject/shortcut-hint
add a little keyboard shortcut hint widget
2017-04-25 11:44:18 -06:00
Chi Vinh Le f79af56468 Better API for slot fragments 2017-04-26 00:44:02 +07:00
Belén Curcio a4b9f86e29 Merge branch 'master' into shortcut-hint 2017-04-25 14:38:47 -03:00
Riley Davis cee1c5bc1c have active state on action buttons depending on comment status 2017-04-25 11:21:28 -06:00
Chi Vinh Le 15d410ba12 Workaround weird Apollo bug 2017-04-25 23:20:23 +07:00
Chi Vinh Le 3a7b17750e Use unique name for fragment 2017-04-25 23:00:11 +07:00
Chi Vinh Le 4bacc2b0c2 Update Apollo 2017-04-25 22:31:59 +07:00
Chi Vinh Le 3d10534da1 RespectPlugin use fat query 2017-04-25 19:54:54 +07:00
Chi Vinh Le 54ea71bedf Support fragments in plugins 2017-04-25 18:24:25 +07:00
riley 19a83a902a Merge branch 'master' into all-comments-queue 2017-04-24 15:15:10 -06:00
Belén Curcio 7d56404e84 Merge branch 'master' into subscriptions 2017-04-24 18:09:34 -03:00
Riley Davis b71b7cc883 move the shortcuts note logic into redux state 2017-04-24 14:04:57 -06:00
Riley Davis aaa5d17ca3 Merge branch 'master' into shortcut-hint 2017-04-24 12:34:44 -06:00
Kim Gardner 860f434335 Merge branch 'master' into all-comments-queue 2017-04-24 13:45:02 -04:00
Chi Vinh Le 027358913d Use container 2017-04-24 23:35:49 +07:00
Chi Vinh Le a91db35f91 Add comments for filter 2017-04-24 23:34:53 +07:00
Chi Vinh Le c261cdc0c6 Compose comment fragments 2017-04-24 22:49:52 +07:00
Chi Vinh Le 11fb5421b5 Implement Stream Container 2017-04-24 19:27:54 +07:00
Chi Vinh Le 4dc3b0c2f4 Merge branch 'master' into query-composition-2
Conflicts:
	client/coral-embed-stream/src/Embed.js
	client/coral-embed-stream/src/components/Comment.js
	client/coral-embed-stream/src/index.js
	client/coral-framework/services/store.js
	client/coral-settings/containers/ProfileContainer.js
	graph/resolvers/root_query.js
2017-04-24 15:24:34 +07:00
Belen Curcio f18191793b fe plugins 2017-04-23 13:09:52 -03:00
Riley Davis 2743f6faf6 return all the known types 2017-04-21 16:44:42 -06:00
Gabriela Rodríguez Berón a41108a53c Merge branch 'master' into shortcut-hint 2017-04-21 15:10:42 -07:00
Gabriela Rodríguez Berón e337a0d18b Merge branch 'master' into subscriptions 2017-04-21 14:35:21 -07:00
Gabriela Rodríguez Berón e9c81f0639 Merge branch 'master' into all-comments-queue 2017-04-21 14:31:16 -07:00
Wyatt Johnson 441ea12a65 Added user metadata 2017-04-21 15:22:52 -06:00
Riley Davis d1ab279f74 add a little keyboard shortcut widget 2017-04-21 13:26:32 -06:00
Riley Davis 387e52b510 Merge branch 'master' into more-fragment-matcher 2017-04-21 12:56:47 -06:00
Riley Davis 2a2a069cea more matching on the fragment 2017-04-21 12:50:29 -06:00
Riley Davis f20f0c5d10 Merge branch 'master' into all-comments-queue 2017-04-21 09:05:38 -06:00
Wyatt Johnson b559b81198 Removed some demo code 2017-04-20 15:20:07 -06:00
Wyatt Johnson 1f562c8fad Merge branch 'master' into subscriptions 2017-04-20 15:17:44 -06:00
Chi Vinh Le 3cd9e802dd WIP 2017-04-20 00:37:45 +07:00
Chi Vinh Le 34fee9439f Move active tab to redux 2017-04-19 23:26:26 +07:00
Chi Vinh Le 0e6b0c01fd Fix issue with ignoredUsers 2017-04-19 21:06:57 +07:00
Chi Vinh Le 6fb2a95bf2 Move query logic to coral-embed-stream 2017-04-19 20:28:14 +07:00
Chi Vinh Le d5d424bfc5 Merge branch 'master' into embed-refactor 2017-04-19 16:16:14 +07:00
Wyatt Johnson ecbb66e270 Fix linting 2017-04-18 15:53:15 -06:00
Wyatt Johnson 45094fd176 Optimized sub model 2017-04-18 15:48:33 -06:00
Wyatt Johnson e1c8e75405 Merge branch 'master' into subscriptions 2017-04-18 15:43:25 -06:00
Chi Vinh Le e9331e90d3 Add missing check 2017-04-19 01:07:49 +07:00
Chi Vinh Le d3b81dff35 Merge branch 'master' into embed-refactor
Conflicts:
	client/coral-plugin-commentbox/CommentBox.js
2017-04-19 00:18:50 +07:00
Chi Vinh Le af5be0469f Refactor embed 2017-04-18 23:20:39 +07:00
Chi Vinh Le b40eed4751 Move to components folder 2017-04-18 23:14:28 +07:00
Riley Davis 673bbb61b1 Merge branch 'master' into all-comments-queue 2017-04-17 15:41:30 -06:00
riley 449a6f67f1 add some tab icons 2017-04-17 15:37:00 -06:00
Riley Davis df859dc782 add all tab 2017-04-17 14:12:45 -06:00
Wyatt Johnson 7fac8ef073 Merge branch 'master' into subscriptions 2017-04-17 08:03:14 -06:00
Wyatt Johnson 9f8c9c7eb1 Merge branch 'master' into subscriptions 2017-04-12 10:37:15 -06:00
David Erwin ab74ebdc52 Merge branch 'master' into subscriptions 2017-04-11 11:58:55 -04:00
Wyatt Johnson 97e3d7220e Removed test code 2017-04-10 14:39:50 -06:00
Wyatt Johnson 7acaaadcd6 Merge branch 'master' into subscriptions 2017-04-10 14:33:08 -06:00
Wyatt Johnson c12660be3b Merge branch 'master' into subscriptions 2017-04-06 15:39:24 -06:00
Wyatt Johnson ffb0ee20e8 Cleanup of subscriptions 2017-04-05 16:15:22 -06:00
Wyatt Johnson e6dba11984 Merge branch 'master' into subscriptions 2017-04-05 15:31:08 -06:00
David Erwin b335cb3493 Document subscription plugins 2017-04-05 17:26:12 -04:00
David Erwin 6735296c3c Abstract setupFunctions 2017-04-05 17:07:14 -04:00
David Erwin 350d9e74f2 Get plugin setupFuncitons for subscriptions 2017-04-05 16:56:32 -04:00
Wyatt Johnson b3582025b7 Fixed import 2017-04-05 13:37:32 -06:00
Wyatt Johnson 3c91b9cda1 Merge branch 'master' into subscriptions 2017-04-05 13:36:57 -06:00
Wyatt Johnson 1fecd4589b Removed unused file 2017-04-05 09:55:15 -06:00
David Erwin 48b7a67665 Add comments 2017-04-05 11:24:52 -04:00
Wyatt Johnson 45c7cbad9e Merge branch 'master' into subscriptions 2017-04-05 08:08:56 -06:00
Wyatt Johnson 75c88df8f1 Fix for build 2017-04-04 10:12:25 -06:00
Wyatt Johnson 07d1f9d3cf Merge branch 'master' into subscriptions 2017-04-04 09:58:50 -06:00
Wyatt Johnson dd3d1e9fe7 fix for session service 2017-03-29 12:45:57 -06:00
Wyatt Johnson ffbe976760 working websockets 2017-03-29 09:35:56 -06:00
Wyatt Johnson 0e0c3f19aa Fixed tests 2017-03-23 11:11:57 -06:00
Wyatt Johnson 63ef49d595 Initial subscription impl 2017-03-23 11:09:14 -06:00
385 changed files with 11899 additions and 5247 deletions
+5
View File
@@ -3,5 +3,10 @@ client/lib
**/*.html
plugins/*
!plugins/coral-plugin-facebook-auth
!plugins/coral-plugin-auth
!plugins/coral-plugin-respect
!plugins/coral-plugin-offtopic
!plugins/coral-plugin-like
!plugins/coral-plugin-mod
!plugins/coral-plugin-love
node_modules
+7
View File
@@ -20,6 +20,8 @@
"no-template-curly-in-string": [1],
"no-unsafe-negation": [1],
"array-callback-return": [1],
"arrow-parens": ["warn", "always"],
"template-curly-spacing": "warn",
"eqeqeq": [2, "smart"],
"no-eval": [2],
"no-global-assign": [2],
@@ -36,6 +38,11 @@
"no-unneeded-ternary": [1],
"object-curly-spacing": [1],
"space-infix-ops": ["error"],
"space-in-parens": ["error", "never"],
"space-unary-ops": ["error", {
"words": true,
"nonwords": false
}],
"no-const-assign": [2],
"no-duplicate-imports": [2],
"prefer-template": [1],
+4
View File
@@ -17,7 +17,11 @@ coverage/
plugins.json
plugins/*
!plugins/coral-plugin-facebook-auth
!plugins/coral-plugin-auth
!plugins/coral-plugin-respect
!plugins/coral-plugin-offtopic
!plugins/coral-plugin-like
!plugins/coral-plugin-mod
!plugins/coral-plugin-love
**/node_modules/*
+2 -1
View File
@@ -13,7 +13,8 @@ EXPOSE 5000
COPY . /usr/src/app
# Install app dependencies and build static assets.
RUN yarn install --frozen-lockfile && \
RUN yarn global add node-gyp && \
yarn install --frozen-lockfile && \
cli plugins reconcile && \
yarn build && \
yarn install --production && \
+1 -1
View File
@@ -243,7 +243,7 @@ file under the `scripts` key including:
# Setup
Once you've installed Talk (either via Docker or source), you still need to
setup the application. If you are unfamiliar with any terminoligy used in the
setup the application. If you are unfamiliar with any terminology used in the
setup process, refer to the `TERMINOLOGY.md` document.
## Via Web
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2016 Mozilla Foundation
Copyright 2017 Mozilla Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+32
View File
@@ -145,6 +145,10 @@ type RootMutation {
type RootQuery {
people: [Person!]
}
type Subscription {
leader: Person
}
```
Thanks to [gql-merge](https://www.npmjs.com/package/gql-merge) the contents of
@@ -259,6 +263,23 @@ If your post function accepts four parameters, then it can modify the field
result. It is *required* that the function resolves a promise (or returns) with
the modified value or simply the original if you didn't modify it.
#### Field: `setupFunctions`
```js
setupFunctions: {
leader: (options, args) => ({
leader: {
filter: (person) => person.place === 1
},
}),
}
```
Setup functions allow you to create filters that control which pubsub.publish() events
send data to the client. If the type in question contains args, clients may subscribe using those arguments to further filter their subscription.
For more information, see the [Apollo Docs](https://github.com/apollographql/graphql-subscriptions).
#### Field: `router`
```js
@@ -375,6 +396,10 @@ module.exports = {
type RootQuery {
people: [Person!]
}
type Subscription {
leader: Person
}
`,
context: {
Slack: () => ({
@@ -430,6 +455,13 @@ module.exports = {
}
}
}
},
setupFunctions: {
leader: (options, args) => ({
leader: {
filter: (person) => person.place === 1
}
}
}
};
+44 -5
View File
@@ -1,6 +1,6 @@
# Talk [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk)
Talk is currently in Beta! [Read more about Talk here.](https://coralproject.net/products/talk.html)
Online comments are broken. Our open-source Talk tool rethinks how moderation, comment display, and conversation function, creating the opportunity for safer, smarter discussions around your work. [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.
@@ -24,10 +24,13 @@ The Talk application looks for the following configuration values either as envi
- `TALK_MONGO_URL` (*required*) - the database connection string for the MongoDB database.
- `TALK_REDIS_URL` (*required*) - the database connection string for the Redis database.
- `TALK_SESSION_SECRET` (*required*) - a random string which will be used to
secure cookies.
- `TALK_ROOT_URL` (*required*) - root url of the installed application externally
available in the format: `<scheme>://<host>` without the path.
- `TALK_JWT_SECRET` (*required*) - a long and cryptographical secure random string which will be used to
sign and verify tokens via a `HS256` algorithm.
- `TALK_JWT_EXPIRY` (_optional_) - the expiry duration (`exp`) for the tokens issued for logged in sessions (Default `1 day`)
- `TALK_JWT_ISSUER` (_optional_) - the issuer (`iss`) claim for login JWT tokens (Default `process.env.TALK_ROOT_URL`)
- `TALK_JWT_AUDIENCE` (_optional_) - the audience (`aud`) claim for login JWT tokens (Default `talk`)
- `TALK_SMTP_EMAIL` (*required for email*) - the address to send emails from using the
SMTP provider.
- `TALK_SMTP_USERNAME` (*required for email*) - username of the SMTP provider you are using.
@@ -42,9 +45,45 @@ available in the format: `<scheme>://<host>` without the path.
Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for
alternative methods of loading configuration during development.
### License
## Supported Browsers
Copyright 2016 Mozilla Foundation
### Web
- Chrome: latest 2 versions
- Firefox: latest 2 versions, and most recent extended support version, if any
- Safari: latest 2 versions
- Internet Explorer: IE Edge, 11
### iOS Devices
- iPad
- iPad Pro
- iPhone 7 Plus
- iPhone 7
- iPhone 6 Plus
- iPhone 6
- iPhone 5
### iOS Browsers
- Chrome for iOS: latest version
- Firefox for iOS: latest version
- Safari for iOS: latest version
### Android Devices
- Galaxy S5
- Nexus 5X
- Nexus 6P
### Android Browsers
- Chrome for Android: latest version
- Firefox for Android: latest version
## License
Copyright 2017 Mozilla Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+43 -61
View File
@@ -3,16 +3,16 @@ const bodyParser = require('body-parser');
const morgan = require('morgan');
const path = require('path');
const helmet = require('helmet');
const authentication = require('./middleware/authentication');
const {passport} = require('./services/passport');
const plugins = require('./services/plugins');
const session = require('express-session');
const enabled = require('debug').enabled;
const RedisStore = require('connect-redis')(session);
const redis = require('./services/redis');
const csrf = require('csurf');
const errors = require('./errors');
const graph = require('./graph');
const {createGraphOptions} = require('./graph');
const apollo = require('graphql-server-express');
const accepts = require('accepts');
const compression = require('compression');
const cookieParser = require('cookie-parser');
const app = express();
@@ -33,44 +33,44 @@ app.set('trust proxy', 1);
app.use(helmet({
frameguard: false
}));
app.use(compression());
app.use(cookieParser());
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');
//==============================================================================
// SESSION MIDDLEWARE
// STATIC FILES
//==============================================================================
const session_opts = {
secret: process.env.TALK_SESSION_SECRET,
httpOnly: true,
rolling: true,
saveUninitialized: true,
resave: true,
unset: 'destroy',
name: 'talk.sid',
cookie: {
secure: false,
maxAge: 8.64e+7, // 24 hours for session token expiry
},
store: new RedisStore({
client: redis.createClient(),
})
};
// If the application is in production mode, then add gzip rewriting for the
// content.
if (process.env.NODE_ENV === 'production') {
app.get('*.js', (req, res, next) => {
const accept = accepts(req);
if (accept.encoding(['gzip']) === 'gzip') {
if (app.get('env') === 'production') {
// Adjsut the headers on the request by adding a content type header
// because express won't be able to detect the mime-type with the .gz
// extension and we need to decalre support for the gzip encoding.
res.set('Content-Type', 'application/javascript');
res.set('Content-Encoding', 'gzip');
// Enable the secure cookie when we are in production mode.
session_opts.cookie.secure = true;
} else if (app.get('env') === 'test') {
// Rewrite the url so that the gzip version will be served instead.
req.url = `${req.url}.gz`;
}
// Add in the secret during tests.
session_opts.secret = 'keyboard cat';
next();
});
}
app.use(session(session_opts));
app.use('/client', express.static(path.join(__dirname, 'dist')));
app.use('/public', express.static(path.join(__dirname, 'public')));
//==============================================================================
// VIEW CONFIGURATION
//==============================================================================
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
//==============================================================================
// PASSPORT MIDDLEWARE
@@ -89,22 +89,27 @@ plugins.get('server', 'passport').forEach((plugin) => {
// Setup the PassportJS Middleware.
app.use(passport.initialize());
app.use(passport.session());
// Attach the authentication middleware, this will be responsible for decoding
// (if present) the JWT on the request.
app.use('/api', authentication);
//==============================================================================
// GraphQL Router
//==============================================================================
// GraphQL endpoint.
app.use('/api/v1/graph/ql', apollo.graphqlExpress(graph.createGraphOptions));
app.use('/api/v1/graph/ql', apollo.graphqlExpress(createGraphOptions));
// Only include the graphiql tool if we aren't in production mode.
if (app.get('env') !== 'production') {
// Interactive graphiql interface.
app.use('/api/v1/graph/iql', apollo.graphiqlExpress({
endpointURL: '/api/v1/graph/ql'
}));
app.use('/api/v1/graph/iql', (req, res) => {
res.render('graphiql', {
endpointURL: '/api/v1/graph/ql'
});
});
// GraphQL documention.
app.get('/admin/docs', (req, res) => {
@@ -113,29 +118,6 @@ if (app.get('env') !== 'production') {
}
//==============================================================================
// CSRF MIDDLEWARE
//==============================================================================
if (process.env.TEST_MODE === 'unit') {
// Add this fake test token in the event we are in unit test mode, and don't
// include the CSRF protection.
app.locals.csrfToken = 'UNIT_TESTS';
} else {
// Setup route middlewares for CSRF protection.
// Default ignore methods are GET, HEAD, OPTIONS
app.use(csrf({}));
app.use((req, res, next) => {
res.locals.csrfToken = req.csrfToken();
next();
});
}
//==============================================================================
// ROUTES
//==============================================================================
+24 -11
View File
@@ -1,26 +1,29 @@
#!/usr/bin/env node
const app = require('../app');
const program = require('./commander');
const http = require('http');
const app = require('../app');
const {createServer} = require('http');
const scraper = require('../services/scraper');
const mailer = require('../services/mailer');
const kue = require('../services/kue');
const mongoose = require('../services/mongoose');
const util = require('./util');
const {createSubscriptionManager} = require('../graph/subscriptions');
const {
PORT
} = require('../config');
/**
* Get port from environment and store in Express.
*/
const port = normalizePort(process.env.TALK_PORT || '3000');
const port = normalizePort(PORT);
app.set('port', port);
/**
* Create HTTP server.
*/
const server = http.createServer(app);
const server = createServer(app);
/**
* Event listener for HTTP server "error" event.
@@ -76,20 +79,29 @@ function normalizePort(val) {
function onListening() {
let addr = server.address();
let bind = typeof addr === 'string'
? `pipe ${ addr}`
: `port ${ addr.port}`;
console.log(`Listening on ${ bind}`);
? `pipe ${addr}`
: `port ${addr.port}`;
console.log(`API Server Listening on ${bind}`);
}
/**
* Start the app.
*/
function startApp() {
function startApp(program) {
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.listen(port, () => {
// Mount the websocket server if requested.
if (program.websockets) {
console.log(`Websocket Server Listening on ${port}`);
// Mount the subscriptions server on the application server.
createSubscriptionManager(server);
}
});
server.on('error', onError);
server.on('listening', onListening);
}
@@ -100,10 +112,11 @@ function startApp() {
program
.option('-j, --jobs', 'enable job processing on this thread')
.option('-w, --websockets', 'enable the websocket (subscriptions) handler on this thread')
.parse(process.argv);
// Start the application serving.
startApp();
startApp(program);
// Enable job processing on the thread if enabled.
if (program.jobs) {
-5
View File
@@ -3,11 +3,6 @@ const dotenv = require('dotenv');
const fs = require('fs');
const program = require('commander');
// Perform rewrites to the runtime environment variables based on the contents
// of the process.env.REWRITE_ENV if it exists. This is done here as it is the
// entrypoint for the entire application.
require('env-rewrite').rewrite();
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
+8 -2
View File
@@ -1,5 +1,5 @@
import React from 'react';
import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-router';
import {Router, Route, IndexRedirect, browserHistory} from 'react-router';
import Stories from 'containers/Stories/Stories';
import Configure from 'containers/Configure/Configure';
@@ -18,7 +18,7 @@ const routes = (
<div>
<Route exact path="/admin/install" component={InstallContainer}/>
<Route path='/admin' component={LayoutContainer}>
<IndexRoute component={Dashboard} />
<IndexRedirect to='/admin/moderate/all' />
<Route path='community' component={CommunityContainer} />
<Route path='configure' component={Configure} />
<Route path='stories' component={Stories} />
@@ -39,6 +39,12 @@ const routes = (
{/* Moderation Routes */}
<Route path='moderate' component={ModerationLayout}>
<Route path='all' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
</Route>
<Route path='accepted' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
</Route>
<Route path='premod' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
</Route>
+4 -4
View File
@@ -8,7 +8,7 @@ import {
UPDATE_ASSETS
} from '../constants/assets';
import coralApi from '../../../coral-framework/helpers/response';
import coralApi from '../../../coral-framework/helpers/request';
/**
* Action disptacher related to assets
@@ -24,7 +24,7 @@ export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filte
assets: result,
count
}))
.catch(error => dispatch({type: FETCH_ASSETS_FAILURE, error}));
.catch((error) => dispatch({type: FETCH_ASSETS_FAILURE, error}));
};
// Update an asset state
@@ -34,9 +34,9 @@ export const updateAssetState = (id, closedAt) => (dispatch) => {
return coralApi(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}})
.then(() =>
dispatch({type: UPDATE_ASSET_STATE_SUCCESS}))
.catch(error => dispatch({type: UPDATE_ASSET_STATE_FAILURE, error}));
.catch((error) => dispatch({type: UPDATE_ASSET_STATE_FAILURE, error}));
};
export const updateAssets = assets => dispatch => {
export const updateAssets = (assets) => (dispatch) => {
dispatch({type: UPDATE_ASSETS, assets});
};
+77 -38
View File
@@ -1,75 +1,114 @@
import bowser from 'bowser';
import * as actions from '../constants/auth';
import coralApi from 'coral-framework/helpers/response';
import coralApi from 'coral-framework/helpers/request';
import * as Storage from 'coral-framework/helpers/storage';
import {handleAuthToken} from 'coral-framework/actions/auth';
// Log In.
export const handleLogin = (email, password, recaptchaResponse) => dispatch => {
//==============================================================================
// SIGN IN
//==============================================================================
export const handleLogin = (email, password, recaptchaResponse) => (dispatch) => {
dispatch({type: actions.LOGIN_REQUEST});
const params = {method: 'POST', body: {email, password}};
const params = {
method: 'POST',
body: {
email,
password
}
};
if (recaptchaResponse) {
params.headers = {'X-Recaptcha-Response': recaptchaResponse};
params.headers = {
'X-Recaptcha-Response': recaptchaResponse
};
}
return coralApi('/auth/local', params)
.then(({user}) => {
.then(({user, token}) => {
if (!user) {
if (!bowser.safari && !bowser.ios) {
Storage.removeItem('token');
}
return dispatch(checkLoginFailure('not logged in'));
}
const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length;
dispatch(checkLoginSuccess(user, isAdmin));
dispatch(handleAuthToken(token));
dispatch(checkLoginSuccess(user));
})
.catch(error => {
.catch((error) => {
if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') {
dispatch({type: actions.LOGIN_MAXIMUM_EXCEEDED, message: error.translation_key});
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});
//==============================================================================
// FORGOT PASSWORD
//==============================================================================
export const requestPasswordReset = email => dispatch => {
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}})
const redirectUri = location.href;
return coralApi('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}})
.then(() => dispatch(forgotPassowordSuccess()))
.catch(error => dispatch(forgotPassowordFailure(error)));
.catch((error) => dispatch(forgotPassowordFailure(error)));
};
// Check Login
//==============================================================================
// CHECK LOGIN
//==============================================================================
const checkLoginRequest = () => ({type: actions.CHECK_LOGIN_REQUEST});
const checkLoginSuccess = (user, isAdmin) => ({type: actions.CHECK_LOGIN_SUCCESS, user, isAdmin});
const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
const checkLoginRequest = () => ({
type: actions.CHECK_LOGIN_REQUEST
});
export const checkLogin = () => dispatch => {
const checkLoginSuccess = (user, isAdmin) => ({
type: actions.CHECK_LOGIN_SUCCESS,
user,
isAdmin
});
const checkLoginFailure = (error) => ({
type: actions.CHECK_LOGIN_FAILURE,
error
});
export const checkLogin = () => (dispatch) => {
dispatch(checkLoginRequest());
return coralApi('/auth')
.then(({user}) => {
if (!user) {
if (!bowser.safari && !bowser.ios) {
Storage.removeItem('token');
}
return dispatch(checkLoginFailure('not logged in'));
}
const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length;
dispatch(checkLoginSuccess(user, isAdmin));
dispatch(checkLoginSuccess(user));
})
.catch(error => {
.catch((error) => {
console.error(error);
dispatch(checkLoginFailure(`${error.translation_key}`));
});
};
// LogOut Actions
const logOutRequest = () => ({type: actions.LOGOUT_REQUEST});
const logOutSuccess = () => ({type: actions.LOGOUT_SUCCESS});
const logOutFailure = () => ({type: actions.LOGOUT_FAILURE});
export const logout = () => dispatch => {
dispatch(logOutRequest());
return coralApi('/auth', {method: 'DELETE'})
.then(() => dispatch(logOutSuccess()))
.catch(error => dispatch(logOutFailure(error)));
};
+4 -4
View File
@@ -14,9 +14,9 @@ import {
HIDE_SUSPENDUSER_DIALOG
} from '../constants/community';
import coralApi from '../../../coral-framework/helpers/response';
import coralApi from '../../../coral-framework/helpers/request';
export const fetchAccounts = (query = {}) => dispatch => {
export const fetchAccounts = (query = {}) => (dispatch) => {
dispatch(requestFetchAccounts());
coralApi(`/users?${qs.stringify(query)}`)
@@ -30,14 +30,14 @@ export const fetchAccounts = (query = {}) => dispatch => {
totalPages
});
})
.catch(error => dispatch({type: FETCH_COMMENTERS_FAILURE, error}));
.catch((error) => dispatch({type: FETCH_COMMENTERS_FAILURE, error}));
};
const requestFetchAccounts = () => ({
type: FETCH_COMMENTERS_REQUEST
});
export const updateSorting = sort => ({
export const updateSorting = (sort) => ({
type: SORT_UPDATE,
sort
});
+1 -1
View File
@@ -1,6 +1,6 @@
export const CONFIG_UPDATED = 'CONFIG_UPDATED';
export const fetchConfig = () => dispatch => {
export const fetchConfig = () => (dispatch) => {
let json = document.getElementById('data');
let data = JSON.parse(json.textContent);
dispatch({type: CONFIG_UPDATED, data});
+12 -12
View File
@@ -1,18 +1,18 @@
import coralApi from 'coral-framework/helpers/response';
import coralApi from 'coral-framework/helpers/request';
import * as actions from '../constants/install';
import validate from 'coral-framework/helpers/validate';
import errorMsj from 'coral-framework/helpers/error';
export const nextStep = () => ({type: actions.NEXT_STEP});
export const previousStep = () => ({type: actions.PREVIOUS_STEP});
export const goToStep = step => ({type: actions.GO_TO_STEP, step});
export const goToStep = (step) => ({type: actions.GO_TO_STEP, step});
const installRequest = () => ({type: actions.INSTALL_REQUEST});
const installSuccess = () => ({type: actions.INSTALL_SUCCESS});
const installFailure = error => ({type: actions.INSTALL_FAILURE, error});
const installFailure = (error) => ({type: actions.INSTALL_FAILURE, error});
const addError = (name, error) => ({type: actions.ADD_ERROR, name, error});
const hasError = error => ({type: actions.HAS_ERROR, error});
const hasError = (error) => ({type: actions.HAS_ERROR, error});
const clearErrors = () => ({type: actions.CLEAR_ERRORS});
const validation = (formData, dispatch, next) => {
@@ -21,11 +21,11 @@ const validation = (formData, dispatch, next) => {
}
const validKeys = Object.keys(formData)
.filter(name => name !== 'domains');
.filter((name) => name !== 'domains');
// Required Validation
const empty = validKeys
.filter(name => {
.filter((name) => {
const cond = !formData[name].length;
if (cond) {
@@ -45,7 +45,7 @@ const validation = (formData, dispatch, next) => {
// RegExp Validation
const validation = validKeys
.filter(name => {
.filter((name) => {
const cond = !validate[name](formData[name]);
if (cond) {
@@ -88,7 +88,7 @@ export const finishInstall = () => (dispatch, getState) => {
dispatch(installSuccess());
dispatch(nextStep());
})
.catch(error => {
.catch((error) => {
console.error(error);
dispatch(installFailure(`${error.translation_key}`));
});
@@ -99,10 +99,10 @@ export const updateUserFormData = (name, value) => ({type: actions.UPDATE_FORMDA
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});
const checkInstallFailure = error => ({type: actions.CHECK_INSTALL_FAILURE, error});
const checkInstallSuccess = (installed) => ({type: actions.CHECK_INSTALL_SUCCESS, installed});
const checkInstallFailure = (error) => ({type: actions.CHECK_INSTALL_FAILURE, error});
export const checkInstall = next => dispatch => {
export const checkInstall = (next) => (dispatch) => {
dispatch(checkInstallRequest());
coralApi('/setup')
.then(({installed}) => {
@@ -111,7 +111,7 @@ export const checkInstall = next => dispatch => {
next();
}
})
.catch(error => {
.catch((error) => {
console.error(error);
dispatch(checkInstallFailure(`${error.translation_key}`));
});
+23 -2
View File
@@ -1,8 +1,29 @@
import * as actions from 'constants/moderation';
export const toggleModal = open => ({type: actions.TOGGLE_MODAL, open});
export const toggleModal = (open) => ({type: actions.TOGGLE_MODAL, open});
export const singleView = () => ({type: actions.SINGLE_VIEW});
// Ban User Dialog
export const showBanUserDialog = (user, commentId, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, showRejectedNote});
export const showBanUserDialog = (user, commentId, commentStatus, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, commentStatus, showRejectedNote});
export const hideBanUserDialog = (showDialog) => ({type: actions.HIDE_BANUSER_DIALOG, showDialog});
// Suspend User Dialog
export const showSuspendUserDialog = (userId, username, commentId, commentStatus) =>
({type: actions.SHOW_SUSPEND_USER_DIALOG, userId, username, commentId, commentStatus});
export const hideSuspendUserDialog = () => ({type: actions.HIDE_SUSPEND_USER_DIALOG});
// hide shortcuts note
export const hideShortcutsNote = () => {
try {
window.localStorage.setItem('coral:shortcutsNote', 'hide');
} catch (e) {
// above will fail in Safari private mode
}
return {type: actions.HIDE_SHORTCUTS_NOTE};
};
export const viewUserDetail = (userId) => ({type: actions.VIEW_USER_DETAIL, userId});
export const hideUserDetail = () => ({type: actions.HIDE_USER_DETAIL});
+6 -6
View File
@@ -1,4 +1,4 @@
import coralApi from '../../../coral-framework/helpers/response';
import coralApi from '../../../coral-framework/helpers/request';
export const SETTINGS_LOADING = 'SETTINGS_LOADING';
export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED';
@@ -13,19 +13,19 @@ export const SAVE_SETTINGS_FAILED = 'SAVE_SETTINGS_FAILED';
export const WORDLIST_UPDATED = 'WORDLIST_UPDATED';
export const DOMAINLIST_UPDATED = 'DOMAINLIST_UPDATED';
export const fetchSettings = () => dispatch => {
export const fetchSettings = () => (dispatch) => {
dispatch({type: SETTINGS_LOADING});
coralApi('/settings')
.then(settings => {
.then((settings) => {
dispatch({type: SETTINGS_RECEIVED, settings});
})
.catch(error => {
.catch((error) => {
dispatch({type: SETTINGS_FETCH_ERROR, error});
});
};
// for updating top-level settings
export const updateSettings = settings => {
export const updateSettings = (settings) => {
return {type: SETTINGS_UPDATED, settings};
};
@@ -48,7 +48,7 @@ export const saveSettingsToServer = () => (dispatch, getState) => {
.then(() => {
dispatch({type: SAVE_SETTINGS_SUCCESS, settings});
})
.catch(error => {
.catch((error) => {
dispatch({type: SAVE_SETTINGS_FAILED, error});
});
};
+5 -5
View File
@@ -1,4 +1,4 @@
import coralApi from '../../../coral-framework/helpers/response';
import coralApi from '../../../coral-framework/helpers/request';
import * as userTypes from '../constants/users';
/**
@@ -9,8 +9,8 @@ export const userStatusUpdate = (status, userId, commentId) => {
return (dispatch) => {
dispatch({type: userTypes.UPDATE_STATUS_REQUEST});
return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
.then(res => dispatch({type: userTypes.UPDATE_STATUS_SUCCESS, res}))
.catch(error => dispatch({type: userTypes.UPDATE_STATUS_FAILURE, error}));
.then((res) => dispatch({type: userTypes.UPDATE_STATUS_SUCCESS, res}))
.catch((error) => dispatch({type: userTypes.UPDATE_STATUS_FAILURE, error}));
};
};
@@ -18,7 +18,7 @@ export const userStatusUpdate = (status, userId, commentId) => {
export const sendNotificationEmail = (userId, subject, body) => {
return (dispatch) => {
return coralApi(`/users/${userId}/email`, {method: 'POST', body: {subject, body}})
.catch(error => dispatch({type: userTypes.USER_EMAIL_FAILURE, error}));
.catch((error) => dispatch({type: userTypes.USER_EMAIL_FAILURE, error}));
};
};
@@ -26,6 +26,6 @@ export const sendNotificationEmail = (userId, subject, body) => {
export const enableUsernameEdit = (userId) => {
return (dispatch) => {
return coralApi(`/users/${userId}/username-enable`, {method: 'POST'})
.catch(error => dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error}));
.catch((error) => dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error}));
};
};
@@ -1,17 +1,30 @@
import React from 'react';
import React, {PropTypes} from 'react';
import styles from './ModerationList.css';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap';
const ActionButton = ({type = '', ...props}) => {
const ActionButton = ({type = '', active, ...props}) => {
const typeName = type.toLowerCase();
let text = menuActionsMap[type].text;
if (text === 'Approve' && active) {
text = 'Approved';
} else if (text === 'Reject' && active) {
text = 'Rejected';
}
return (
<Button
className={`${type.toLowerCase()} ${styles.actionButton}`}
cStyle={type.toLowerCase()}
className={`${typeName} ${styles.actionButton} ${active ? styles[`${typeName}__active`] : ''}`}
cStyle={typeName}
icon={menuActionsMap[type].icon}
onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment}
>{menuActionsMap[type].text}</Button>
>{text}</Button>
);
};
ActionButton.propTypes = {
active: PropTypes.bool
};
export default ActionButton;
@@ -0,0 +1,53 @@
.button {
-webkit-transform: scale(.8);
transform: scale(.8);
margin: 0;
}
.root {
color: black;
> :global(.mdl-menu__container) {
margin-left: 10px;
> :global(.mdl-menu__outline) {
box-shadow: none;
}
}
}
.buttonOpen {
box-shadow: none;
color: white;
background-color: #616161;
}
.arrowIcon {
margin-left: 6px;
margin-right: 0;
vertical-align: middle;
margin-right: 0;
font-size: 14px;
}
.menu {
padding: 0;
}
.menuItem {
background-color: #2a2a2a;
color: white;
&:first-child {
margin-bottom: 1px;
border-radius: 2px 2px 0px 0px;
}
&:last-child {
border-radius: 0px 0px 2px 2px;
}
&:hover, &:active, &:focus {
background-color: #767676;
}
&[disabled], &[disabled]:hover, &[disabled]:focus, &[disabled]:active {
background-color: #262626;
color: rgba(255, 255, 255, 0.5);
}
}
@@ -0,0 +1,64 @@
import React, {PropTypes} from 'react';
import {Button, Icon} from 'coral-ui';
import {Menu} from 'react-mdl';
import cn from 'classnames';
import {findDOMNode} from 'react-dom';
import styles from './ActionsMenu.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
let count = 0;
class ActionsMenu extends React.Component {
id = `actions-dropdown-${count++}`;
menu = null;
state = {open: false};
timeout = null;
componentWillUnmount() {
clearTimeout(this.timeout);
}
handleRef = (ref) => {
this.menu = ref ? findDOMNode(ref).parentNode : null;
}
syncOpenState = () => {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.setState({open: this.menu.className.indexOf('is-visible') >= 0});
}, 150);
};
render() {
return (
<div className={styles.root} onBlur={this.syncOpenState} >
<Button
cStyle='actions'
className={cn(styles.button, {[styles.buttonOpen]: this.state.open})}
disabled={false}
id={this.id}
onClick={this.syncOpenState}
icon={this.props.icon}
raised>
{lang.t('modqueue.actions')}
<Icon
name={this.state.open ? 'keyboard_arrow_up' : 'keyboard_arrow_down'}
className={styles.arrowIcon}
/>
</Button>
<Menu target={this.id} className={styles.menu} ref={this.handleRef}>
{this.props.children}
</Menu>
</div>
);
}
}
ActionsMenu.propTypes = {
icon: PropTypes.string,
};
export default ActionsMenu;
@@ -0,0 +1,9 @@
import React from 'react';
import cn from 'classnames';
import {MenuItem} from 'react-mdl';
import styles from './ActionsMenu.css';
const ActionsMenuItem = (props) =>
<MenuItem className={cn(styles.menuItem, props.className)} {...props} />;
export default ActionsMenuItem;
@@ -14,7 +14,7 @@ class AdminLogin extends React.Component {
this.state = {email: '', password: '', requestPassword: false};
}
handleSignIn = e => {
handleSignIn = (e) => {
e.preventDefault();
this.props.handleLogin(this.state.email, this.state.password);
}
@@ -28,7 +28,7 @@ class AdminLogin extends React.Component {
this.props.handleLogin(this.state.email, this.state.password, recaptchaResponse);
}
handleRequestPassword = e => {
handleRequestPassword = (e) => {
e.preventDefault();
this.props.requestPasswordReset(this.state.email);
}
@@ -41,11 +41,11 @@ class AdminLogin extends React.Component {
<TextField
label='Email Address'
value={this.state.email}
onChange={e => this.setState({email: e.target.value})} />
onChange={(e) => this.setState({email: e.target.value})} />
<TextField
label='Password'
value={this.state.password}
onChange={e => this.setState({password: e.target.value})}
onChange={(e) => this.setState({password: e.target.value})}
type='password' />
<div style={{height: 10}}></div>
<Button
@@ -54,7 +54,7 @@ class AdminLogin extends React.Component {
full
onClick={this.handleSignIn}>Sign In</Button>
<p className={styles.forgotPasswordCTA}>
Forgot your password? <a href="#" className={styles.forgotPasswordLink} onClick={e => {
Forgot your password? <a href="#" className={styles.forgotPasswordLink} onClick={(e) => {
e.preventDefault();
this.setState({requestPassword: true});
}}>Request a new one.</a>
@@ -82,7 +82,7 @@ class AdminLogin extends React.Component {
<TextField
label='Email Address'
value={this.state.email}
onChange={e => this.setState({email: e.target.value})} />
onChange={(e) => this.setState({email: e.target.value})} />
<Button
type='submit'
cStyle='black'
+5 -5
View File
@@ -1,16 +1,16 @@
import React from 'react';
import {Provider} from 'react-redux';
import ToastContainer from './ToastContainer';
import 'material-design-lite';
import store from 'services/store';
import AppRouter from '../AppRouter';
export default class App extends React.Component {
render () {
return (
<Provider store={store}>
<AppRouter store={store} />
</Provider>
<div>
<ToastContainer />
<AppRouter />
</div>
);
}
}
@@ -1,7 +1,7 @@
import React, {PropTypes} from 'react';
import {Card} from 'coral-ui';
const EmptyCard = props => (
const EmptyCard = (props) => (
<Card style={{textAlign: 'center', maxWidth: 400, margin: '0 auto'}}>
{props.children}
</Card>
@@ -18,3 +18,47 @@
border-radius: 4px;
border: 1px solid #999;
}
.callToAction {
position: fixed;
left: 10px;
bottom: 10px;
width: 280px;
height: 200px;
background: white;
padding: 15px;
box-sizing: border-box;
box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.15);
.ctaHeader {
font-size: 16px;
margin-bottom: 5px;
}
ul {
padding-left: 0;
list-style: none;
margin: 0 0 10px 0;
}
p, li {
font-size: 12px;
margin-bottom: 5px;
}
li span:first-child, p:last-child span:first-child {
display: inline-block;
width: 120px;
}
}
.closeButton {
float: right;
font-size: 20px;
cursor: pointer;
}
.smallKey {
padding: 5px;
border: 1px solid #ccc;
}
@@ -1,9 +1,11 @@
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
import React from 'react';
import React, {PropTypes} from 'react';
import Modal from 'components/Modal';
import styles from './ModerationKeysModal.css';
const lang = new I18n(translations);
const shortcuts = [
{
title: 'modqueue.navigation',
@@ -23,29 +25,52 @@ const shortcuts = [
}
];
export default ({open, onClose}) => (
<Modal open={open} onClose={onClose}>
<h3>{lang.t('modqueue.shortcuts')}</h3>
<div className={styles.container}>
{shortcuts.map((shortcut, i) => (
<table className={styles.table} key={i}>
<thead>
<tr>
<th>{lang.t(shortcut.title)}</th>
</tr>
</thead>
<tbody>
{Object.keys(shortcut.shortcuts).map(key => (
<tr key={`${key }tr`}>
<td className={styles.shortcut}><span className={styles.key}>{key}</span></td>
<td>{lang.t(shortcut.shortcuts[key])}</td>
</tr>
))}
</tbody>
</table>
))}
</div>
</Modal>
);
export default class ModerationKeysModal extends React.Component {
const lang = new I18n(translations);
static propTypes = {
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
hideShortcutsNote: PropTypes.func.isRequired,
shortcutsNoteVisible: PropTypes.string.isRequired
}
render () {
const {open, onClose, hideShortcutsNote, shortcutsNoteVisible} = this.props;
return (
<div>
<div className={styles.callToAction} style={{display: shortcutsNoteVisible === 'show' ? 'block' : 'none'}}>
<div onClick={hideShortcutsNote} className={styles.closeButton}>×</div>
<p className={styles.ctaHeader}>{lang.t('modqueue.mod-faster')}</p>
<p><strong>{lang.t('modqueue.try-these')}:</strong></p>
<ul>
<li><span>{lang.t('modqueue.approve')}</span> <span className={styles.smallKey}>t</span></li>
<li><span>{lang.t('modqueue.reject')}</span> <span className={styles.smallKey}>r</span></li>
</ul>
<p><span>{lang.t('modqueue.view-more-shortcuts')}</span> <span className={styles.smallKey}>{lang.t('modqueue.shift-key')}</span> + <span className={styles.smallKey}>/</span></p>
</div>
<Modal open={open} onClose={onClose}>
<h3>{lang.t('modqueue.shortcuts')}</h3>
<div className={styles.container}>
{shortcuts.map((shortcut, i) => (
<table className={styles.table} key={i}>
<thead>
<tr>
<th>{lang.t(shortcut.title)}</th>
</tr>
</thead>
<tbody>
{Object.keys(shortcut.shortcuts).map((key) => (
<tr key={`${key}tr`}>
<td className={styles.shortcut}><span className={styles.key}>{key}</span></td>
<td>{lang.t(shortcut.shortcuts[key])}</td>
</tr>
))}
</tbody>
</table>
))}
</div>
</Modal>
</div>
);
}
}
@@ -188,3 +188,17 @@
margin: 0;
width: 140px;
}
.approve__active {
box-shadow: none;
color: white;
background-color: #519954;
cursor: not-allowed;
}
.reject__active, .rejected__active {
color: white;
background-color: #D03235;
box-shadow: none;
cursor: not-allowed;
}
@@ -72,7 +72,7 @@ export default class ModerationList extends React.Component {
// Add key handlers. Each action has one and added j/k for moving around
bindKeyHandlers () {
const {modActions, isActive} = this.props;
modActions.filter(action => menuOptionsMap[action].key).forEach(action => {
modActions.filter((action) => menuOptionsMap[action].key).forEach((action) => {
key(menuOptionsMap[action].key, 'moderationList', () => isActive && this.actionKeyHandler(menuOptionsMap[action].status));
});
key('j', 'moderationList', () => isActive && this.moveKeyHandler('down'));
@@ -0,0 +1,226 @@
@keyframes :global(bounceInRight) {
from, 60%, 75%, 90%, to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); }
from {
opacity: 0;
transform: translate3d(3000px, 0, 0); }
60% {
opacity: 1;
transform: translate3d(-25px, 0, 0); }
75% {
transform: translate3d(10px, 0, 0); }
90% {
transform: translate3d(-5px, 0, 0); }
to {
transform: none; } }
@keyframes :global(bounceOutRight) {
20% {
opacity: 1;
transform: translate3d(-20px, 0, 0); }
to {
opacity: 0;
transform: translate3d(2000px, 0, 0); } }
@keyframes :global(bounceInLeft) {
from, 60%, 75%, 90%, to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); }
0% {
opacity: 0;
transform: translate3d(-3000px, 0, 0); }
60% {
opacity: 1;
transform: translate3d(25px, 0, 0); }
75% {
transform: translate3d(-10px, 0, 0); }
90% {
transform: translate3d(5px, 0, 0); }
to {
transform: none; } }
@keyframes :global(bounceOutLeft) {
20% {
opacity: 1;
transform: translate3d(20px, 0, 0); }
to {
opacity: 0;
transform: translate3d(-2000px, 0, 0); } }
@keyframes :global(bounceInUp) {
from, 60%, 75%, 90%, to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); }
from {
opacity: 0;
transform: translate3d(0, 3000px, 0); }
60% {
opacity: 1;
transform: translate3d(0, -20px, 0); }
75% {
transform: translate3d(0, 10px, 0); }
90% {
transform: translate3d(0, -5px, 0); }
to {
transform: translate3d(0, 0, 0); } }
@keyframes :global(bounceOutUp) {
20% {
transform: translate3d(0, -10px, 0); }
40%, 45% {
opacity: 1;
transform: translate3d(0, 20px, 0); }
to {
opacity: 0;
transform: translate3d(0, -2000px, 0); } }
@keyframes :global(bounceInDown) {
from, 60%, 75%, 90%, to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); }
0% {
opacity: 0;
transform: translate3d(0, -3000px, 0); }
60% {
opacity: 1;
transform: translate3d(0, 25px, 0); }
75% {
transform: translate3d(0, -10px, 0); }
90% {
transform: translate3d(0, 5px, 0); }
to {
transform: none; } }
@keyframes :global(bounceOutDown) {
20% {
transform: translate3d(0, 10px, 0); }
40%, 45% {
opacity: 1;
transform: translate3d(0, -20px, 0); }
to {
opacity: 0;
transform: translate3d(0, 2000px, 0); } }
@keyframes :global(track-progress) {
0% {
width: 100%; }
100% {
width: 0; } }
:global {
.bounceOutRight, .toast-exit--top-right, .toast-exit--bottom-right {
animation-name: bounceOutRight; }
.bounceInRight, .toast-enter--top-right, .toast-enter--bottom-right {
animation-name: bounceInRight; }
.bounceInLeft, .toast-enter--top-left, .toast-enter--bottom-left {
animation-name: bounceInLeft; }
.bounceOutLeft, .toast-exit--top-left, .toast-exit--bottom-left {
animation-name: bounceOutLeft; }
.bounceInUp, .toast-enter--bottom-center {
animation-name: bounceInUp; }
.bounceOutUp, .toast-exit--top-center {
animation-name: bounceOutUp; }
.bounceInDown, .toast-enter--top-center {
animation-name: bounceInDown; }
.bounceOutDown, .toast-exit--bottom-center {
animation-name: bounceOutDown; }
.animated {
animation-duration: 0.75s;
animation-fill-mode: both; }
.toastify {
z-index: 999;
position: fixed;
padding: 4px;
width: 350px;
max-width: 98%;
color: #999;
box-sizing: border-box; }
.toastify--top-left {
top: 1em;
left: 1em; }
.toastify--top-center {
top: 1em;
left: 50%;
margin-left: -175px; }
.toastify--top-right {
top: 1em;
right: 2em; }
.toastify--bottom-left {
bottom: 1em;
left: 1em; }
.toastify--bottom-center {
bottom: 1em;
left: 50%;
margin-left: -175px; }
.toastify--bottom-right {
bottom: 1em;
right: 2em; }
.toastify__img {
float: left;
margin-right: 8px;
vertical-align: middle; }
.toastify__close {
position: absolute;
top: 18px;
left: 12px;
width: 20px;
height: 16px;
padding: 0;
text-align: center;
text-decoration: none;
color: white;
font-weight: bold;
font-size: 14px;
background: transparent;
outline: none;
border: none;
cursor: pointer;
opacity: 0.8;
transition: .3s ease; }
.toastify__close:hover, .toastify__close:focus {
opacity: 1;
}
.toastify-content {
position: relative;
width: 100%;
margin-bottom: 12px;
padding: 18px 24px 20px 48px;
box-sizing: border-box;
background: #404040;
border-radius: 2px;
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1), 0 3px 20px 0 rgba(0, 0, 0, 0.05); }
.toastify-content--info {
background: #2488cb; }
.toastify-content--success {
background: #008577; }
.toastify-content--warning {
background: #ef6c2b; }
.toastify-content--error {
background: #ef342b; }
.toastify__body {
color: white;
font-size: 15px;
font-weight: 400;
}
.toastify__progress {
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 4px;
z-index: 999;
opacity: 0.8;
border-radius: 2px;
animation: track-progress linear 1;
background-color: white;
}
}
@@ -0,0 +1,7 @@
import './ToastContainer.css';
import {defaultProps} from 'recompose';
import {ToastContainer} from 'react-toastify';
export default defaultProps({
autoClose: 5000,
})(ToastContainer);
+28 -18
View File
@@ -4,10 +4,11 @@ import {IndexLink, Link} from 'react-router';
import styles from './Drawer.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import {can} from 'coral-framework/services/perms';
const CoralDrawer = ({handleLogout, restricted = false}) => (
const CoralDrawer = ({handleLogout, auth}) => (
<Drawer className={styles.header}>
{ !restricted ?
{ auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
<div>
<Navigation className={styles.nav}>
<IndexLink
@@ -16,28 +17,37 @@ const CoralDrawer = ({handleLogout, restricted = false}) => (
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"
{
can(auth.user, 'MODERATE_COMMENTS') && (
<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}>
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>
{
can(auth.user, 'UPDATE_CONFIG') &&
(
<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>
+61 -46
View File
@@ -5,57 +5,74 @@ import styles from './Header.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import {Logo} from './Logo';
import {can} from 'coral-framework/services/perms';
const CoralHeader = ({handleLogout, restricted = false}) => (
const CoralHeader = ({
handleLogout,
showShortcuts = () => {},
auth
}) => (
<Header className={styles.header}>
<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
id='streamsNav'
className={styles.navLink}
to="/admin/stories"
activeClassName={styles.active}>
{lang.t('configure.stories')}
</Link>
<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}>
{lang.t('configure.configure')}
</Link>
</Navigation>
{
auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
<Navigation className={styles.nav}>
<IndexLink
id='dashboardNav'
className={styles.navLink}
to="/admin/dashboard"
activeClassName={styles.active}>
{lang.t('configure.dashboard')}
</IndexLink>
{
can(auth.user, 'MODERATE_COMMENTS') && (
<Link
id='moderateNav'
className={styles.navLink}
to="/admin/moderate"
activeClassName={styles.active}>
{lang.t('configure.moderate')}
</Link>
)
}
<Link
id='streamsNav'
className={styles.navLink}
to="/admin/stories"
activeClassName={styles.active}>
{lang.t('configure.stories')}
</Link>
<Link
id='communityNav'
className={styles.navLink}
to="/admin/community"
activeClassName={styles.active}>
{lang.t('configure.community')}
</Link>
{
can(auth.user, 'UPDATE_CONFIG') && (
<Link
id='configureNav'
className={styles.navLink}
to="/admin/configure"
activeClassName={styles.active}>
{lang.t('configure.configure')}
</Link>
)
}
</Navigation>
:
null
}
<div className={styles.rightPanel}>
<ul>
<li className={styles.settings}>
<div>
<IconButton name="settings" id="menu-settings"/>
<Menu target="menu-settings" align="right">
<MenuItem onClick={handleLogout}>Sign Out</MenuItem>
<MenuItem onClick={() => showShortcuts(true)}>{lang.t('configure.shortcuts')}</MenuItem>
<MenuItem onClick={handleLogout}>{lang.t('configure.sign-out')}</MenuItem>
</Menu>
</div>
</li>
@@ -65,15 +82,13 @@ const CoralHeader = ({handleLogout, restricted = false}) => (
</ul>
</div>
</div>
:
null
}
</Header>
);
CoralHeader.propTypes = {
handleLogout: PropTypes.func.isRequired,
restricted: PropTypes.bool // hide elemnts from a user that's logged out
auth: PropTypes.object,
showShortcuts: PropTypes.func,
handleLogout: PropTypes.func.isRequired
};
const lang = new I18n(translations);
+11 -2
View File
@@ -4,9 +4,17 @@ import Header from './Header';
import Drawer from './Drawer';
import styles from './Layout.css';
const Layout = ({children, handleLogout = () => {}, restricted = false, ...props}) => (
const Layout = ({
children,
handleLogout = () => {},
toggleShortcutModal,
restricted = false,
...props}) => (
<LayoutMDL fixedDrawer>
<Header handleLogout={handleLogout} restricted={restricted} {...props} />
<Header
handleLogout={handleLogout}
showShortcuts={toggleShortcutModal}
{...props} />
<Drawer handleLogout={handleLogout} restricted={restricted} {...props} />
<div className={styles.layout}>
{children}
@@ -16,6 +24,7 @@ const Layout = ({children, handleLogout = () => {}, restricted = false, ...props
Layout.propTypes = {
handleLogout: PropTypes.func,
toggleShortcutModal: PropTypes.func,
restricted: PropTypes.bool // hide elements from a user that's logged out
};
+1 -5
View File
@@ -2,11 +2,7 @@ export const CHECK_LOGIN_REQUEST = 'CHECK_LOGIN_REQUEST';
export const CHECK_LOGIN_SUCCESS = 'CHECK_LOGIN_SUCCESS';
export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE';
export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN';
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
export const LOGOUT = 'LOGOUT';
export const LOGIN_REQUEST = 'LOGIN_REQUEST';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
@@ -2,3 +2,8 @@ export const TOGGLE_MODAL = 'TOGGLE_MODAL';
export const SINGLE_VIEW = 'SINGLE_VIEW';
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
export const HIDE_SHORTCUTS_NOTE = 'HIDE_SHORTCUTS_NOTE';
export const SHOW_SUSPEND_USER_DIALOG = 'SHOW_SUSPEND_USER_DIALOG';
export const HIDE_SUSPEND_USER_DIALOG = 'HIDE_SUSPEND_USER_DIALOG';
export const VIEW_USER_DETAIL = 'VIEW_USER_DETAIL';
export const HIDE_USER_DETAIL = 'HIDE_USER_DETAIL';
@@ -3,7 +3,7 @@ import {connect} from 'react-redux';
import {compose} from 'react-apollo';
import {modUserFlaggedQuery} from 'coral-admin/src/graphql/queries';
import {banUser, setUserStatus, suspendUser} from 'coral-admin/src/graphql/mutations';
import {banUser, setUserStatus, rejectUsername} from 'coral-admin/src/graphql/mutations';
import {
fetchAccounts,
@@ -113,7 +113,7 @@ class CommunityContainer extends Component {
error={data.error}
showBanUserDialog={props.showBanUserDialog}
approveUser={props.approveUser}
suspendUser={props.suspendUser}
rejectUsername={props.rejectUsername}
showSuspendUserDialog={props.showSuspendUserDialog}
/>
<BanUserDialog
@@ -126,7 +126,7 @@ class CommunityContainer extends Component {
open={community.suspendDialog}
handleClose={props.hideSuspendUserDialog}
user={community.user}
suspendUser={props.suspendUser}
rejectUsername={props.rejectUsername}
/>
</div>
);
@@ -148,12 +148,12 @@ class CommunityContainer extends Component {
}
}
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
community: state.community.toJS()
});
const mapDispatchToProps = dispatch => ({
fetchAccounts: query => dispatch(fetchAccounts(query)),
const mapDispatchToProps = (dispatch) => ({
fetchAccounts: (query) => dispatch(fetchAccounts(query)),
showBanUserDialog: (user) => dispatch(showBanUserDialog(user)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
showSuspendUserDialog: (user) => dispatch(showSuspendUserDialog(user)),
@@ -165,5 +165,5 @@ export default compose(
modUserFlaggedQuery,
banUser,
setUserStatus,
suspendUser
rejectUsername
)(CommunityContainer);
@@ -1,6 +1,6 @@
import React from 'react';
const CommunityLayout = props => (
const CommunityLayout = (props) => (
<div>
{props.children}
</div>
@@ -4,7 +4,6 @@ import translations from 'coral-admin/src/translations.json';
import styles from './Community.css';
import Table from './Table';
import Loading from './Loading';
import {Pager, Icon} from 'coral-ui';
import EmptyCard from '../../components/EmptyCard';
@@ -29,8 +28,8 @@ const tableHeaders = [
}
];
const People = ({isFetching, commenters, searchValue, onSearchChange, ...props}) => {
const hasResults = !isFetching && !!commenters.length;
const People = ({commenters, searchValue, onSearchChange, ...props}) => {
const hasResults = !!commenters.length;
return (
<div className={styles.container}>
<div className={styles.leftColumn}>
@@ -47,7 +46,6 @@ const People = ({isFetching, commenters, searchValue, onSearchChange, ...props})
</div>
</div>
<div className={styles.mainContent}>
{ isFetching && <Loading /> }
{
hasResults
? <Table
@@ -54,7 +54,7 @@ class Table extends Component {
<SelectField label={'Select me'} value={row.status || ''}
className={styles.selectField}
label={lang.t('community.status')}
onChange={status => this.onCommenterStatusChange(row.id, status)}>
onChange={(status) => this.onCommenterStatusChange(row.id, status)}>
<Option value={'ACTIVE'}>{lang.t('community.active')}</Option>
<Option value={'BANNED'}>{lang.t('community.banned')}</Option>
</SelectField>
@@ -63,8 +63,9 @@ class Table extends Component {
<SelectField label={'Select me'} value={row.roles[0] || ''}
className={styles.selectField}
label={lang.t('community.role')}
onChange={role => this.onRoleChange(row.id, role)}>
onChange={(role) => this.onRoleChange(row.id, role)}>
<Option value={''}>.</Option>
<Option value={'STAFF'}>{lang.t('community.staff')}</Option>
<Option value={'MODERATOR'}>{lang.t('community.moderator')}</Option>
<Option value={'ADMIN'}>{lang.t('community.admin')}</Option>
</SelectField>
@@ -77,4 +78,4 @@ class Table extends Component {
}
}
export default connect(state => ({commenters: state.community.get('accounts')}))(Table);
export default connect((state) => ({commenters: state.community.get('accounts')}))(Table);
@@ -1,6 +1,6 @@
import React from 'react';
import styles from '../Community.css';
import BanUserButton from '../../../components/BanUserButton';
import BanUserButton from './BanUserButton';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../../../containers/ModerationQueue/helpers/moderationQueueActionsMap';
@@ -10,16 +10,16 @@ const lang = new I18n(translations);
const stages = [
{
title: 'suspenduser.title_0',
description: 'suspenduser.description_0',
title: 'suspenduser.title_reject',
description: 'suspenduser.description_reject',
options: {
'j': 'suspenduser.no_cancel',
'k': 'suspenduser.yes_suspend'
}
},
{
title: 'suspenduser.title_1',
description: 'suspenduser.description_1',
title: 'suspenduser.title_notify',
description: 'suspenduser.description_notify',
options: {
'j': 'bandialog.cancel',
'k': 'suspenduser.send'
@@ -34,11 +34,11 @@ class SuspendUserDialog extends Component {
static propTypes = {
stage: PropTypes.number,
handleClose: PropTypes.func.isRequired,
suspendUser: PropTypes.func.isRequired
rejectUsername: PropTypes.func.isRequired
}
componentDidMount() {
this.setState({email: lang.t('suspenduser.email'), about: lang.t('suspenduser.username')});
this.setState({email: lang.t('suspenduser.email_message_reject'), about: lang.t('suspenduser.username')});
}
/*
@@ -46,13 +46,13 @@ class SuspendUserDialog extends Component {
* handles the possible actions for that dialog.
*/
onActionClick = (stage, menuOption) => () => {
const {suspendUser, user} = this.props;
const {rejectUsername, user} = this.props;
const {stage} = this.state;
const cancel = this.props.handleClose;
const next = () => this.setState({stage: stage + 1});
const suspend = () => {
suspendUser({userId: user.user.id, message: this.state.email})
rejectUsername({id: user.user.id, message: this.state.email})
.then(() => {
this.props.handleClose();
});
@@ -79,7 +79,7 @@ class SuspendUserDialog extends Component {
open={open}
onClose={handleClose}
onCancel={handleClose}
title={lang.t('suspenduser.title')}>
title={lang.t('suspenduser.suspend_user')}>
<div className={styles.title}>
{lang.t(stages[stage].title, lang.t('suspenduser.username'))}
</div>
@@ -9,7 +9,7 @@ import translations from '../../../translations.json';
const lang = new I18n(translations);
// Render a single user for the list
const User = props => {
const User = (props) => {
const {user, modActionButtons} = props;
let userStatus = user.status;
@@ -37,7 +37,7 @@ const User = props => {
<div className={styles.flaggedByCount}>
<i className="material-icons">flag</i><span className={styles.flaggedByLabel}>{lang.t('community.flags')}({ user.actions.length })</span>:
{ user.action_summaries.map(
(action, i ) => {
(action, i) => {
return <span className={styles.flaggedBy} key={i}>
{lang.t(`community.${action.reason}`)} ({action.count})
</span>;
@@ -46,7 +46,7 @@ const User = props => {
</div>
<div className={styles.flaggedReasons}>
{ user.action_summaries.map(
(action_sum, i ) => {
(action_sum, i) => {
return <div key={i}>
<span className={styles.flaggedByLabel}>
{lang.t(`community.${action_sum.reason}`)} ({action_sum.count})
@@ -96,24 +96,27 @@
}
}
.inlineTextfield {
border-color: #ccc;
border-style: solid;
border-width: 0px 0px 1px 0px;
text-align: center;
font-size: inherit;
}
.inlineTextfield:focus {
outline: none;
}
.charCountTexfield {
width: 4em;
padding: 0px;
border-color: #ccc;
border-style: solid;
border-width: 0px 0px 1px 0px;
font-size: 14px;
text-align: center;
}
.charCountTexfieldEnabled {
border-color: #00796b;
}
.charCountTexfield:focus {
outline: none;
}
.changedSave {
background-color: #00796B;
color: white;
@@ -15,6 +15,7 @@ import translations from 'coral-admin/src/translations.json';
import StreamSettings from './StreamSettings';
import ModerationSettings from './ModerationSettings';
import TechSettings from './TechSettings';
import {can} from 'coral-framework/services/perms';
class Configure extends Component {
constructor (props) {
@@ -118,6 +119,11 @@ class Configure extends Component {
render () {
const {activeSection} = this.state;
const section = this.getSection(activeSection);
const {auth: {user}} = this.props;
if (!can(user, 'UPDATE_CONFIG')) {
return <p>You must be an administrator to access config settings. Please find the nearest Admin and ask them to level you up!</p>;
}
const showSave = Object.keys(this.state.errors).reduce(
(bool, error) => this.state.errors[error] ? false : bool, this.state.changed);
@@ -171,7 +177,8 @@ class Configure extends Component {
}
}
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
auth: state.auth.toJS(),
settings: state.settings.toJS()
});
export default connect(mapStateToProps)(Configure);
@@ -18,8 +18,8 @@ const Domainlist = ({domains, onChangeDomainlist}) => {
value={domains}
inputProps={{placeholder: 'URL'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => onChangeDomainlist('whitelist', tags)}
pasteSplit={(data) => data.split(',').map((d) => d.trim())}
onChange={(tags) => onChangeDomainlist('whitelist', tags)}
/>
</div>
</div>
@@ -30,7 +30,7 @@ class EmbedLink extends Component {
location.protocol,
'//',
location.hostname,
location.port ? (`:${ window.location.port}`) : ''
location.port ? (`:${window.location.port}`) : ''
].join('');
const coralJsUrl = [talkBaseUrl, '/embed.js'].join('');
const nonce = String(Math.random()).slice(2);
@@ -27,6 +27,12 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
const on = styles.enabledSetting;
const off = styles.disabledSetting;
const onChangeEditCommentWindowLength = (e) => {
const value = e.target.value;
const valueAsNumber = parseFloat(value);
const milliseconds = (!isNaN(valueAsNumber)) && (valueAsNumber * 1000);
updateSettings({editCommentWindowLength: milliseconds || value});
};
return (
<div className={styles.Configure}>
<Card className={`${styles.configSetting} ${settings.requireEmailConfirmation ? on : off}`}>
@@ -72,6 +78,27 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
bannedWords={settings.wordlist.banned}
suspectWords={settings.wordlist.suspect}
onChangeWordlist={onChangeWordlist} />
{/* Edit Comment Timeframe */}
<Card className={styles.configSetting}>
<div className={styles.settingsHeader}>{lang.t('configure.edit-comment-timeframe-heading')}</div>
<p>
{lang.t('configure.edit-comment-timeframe-text-pre')}
&nbsp;
<input
style={{width: '3em'}}
className={styles.inlineTextfield}
type="number"
min="0"
onChange={onChangeEditCommentWindowLength}
placeholder="30"
defaultValue={(settings.editCommentWindowLength / 1000) /* saved as ms, rendered as seconds */}
pattern='[0-9]+([\.][0-9]*)?'
/>
&nbsp;
{lang.t('configure.edit-comment-timeframe-text-post')}
</p>
</Card>
</div>
);
};
@@ -81,7 +81,7 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
<p className={settings.charCountEnable ? '' : styles.disabledSettingText}>
<span>{lang.t('configure.comment-count-text-pre')}</span>
<input type='text'
className={`${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
className={`${styles.inlineTextfield} ${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
htmlFor='charCount'
onChange={updateCharCount(updateSettings, settingsError)}
value={settings.charCount}
@@ -170,7 +170,7 @@ 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
const getTimeoutMeasure = ts => {
const getTimeoutMeasure = (ts) => {
if (ts % TIMESTAMPS['weeks'] === 0) {
return 'weeks';
} else if (ts % TIMESTAMPS['days'] === 0) {
@@ -182,6 +182,6 @@ const getTimeoutMeasure = ts => {
// Dividing the amount by it's measure (hours, days, weeks) we
// obtain the amount of time
const getTimeoutAmount = ts => ts / TIMESTAMPS[getTimeoutMeasure(ts)];
const getTimeoutAmount = (ts) => ts / TIMESTAMPS[getTimeoutMeasure(ts)];
const lang = new I18n(translations);
@@ -15,8 +15,8 @@ const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
value={bannedWords}
inputProps={{placeholder: 'word or phrase'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => onChangeWordlist('banned', tags)}
pasteSplit={(data) => data.split(',').map((d) => d.trim())}
onChange={(tags) => onChangeWordlist('banned', tags)}
/>
</div>
</Card>
@@ -28,8 +28,8 @@ const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
value={suspectWords}
inputProps={{placeholder: 'word or phrase'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => onChangeWordlist('suspect', tags)} />
pasteSplit={(data) => data.split(',').map((d) => d.trim())}
onChange={(tags) => onChangeWordlist('suspect', tags)} />
</div>
</Card>
</div>
@@ -17,7 +17,7 @@ const ActivityWidget = ({assets}) => {
<div className={styles.widgetTable}>
{
assets.length
? assets.map(asset => {
? assets.map((asset) => {
return (
<div className={styles.rowLinkify} key={asset.id}>
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
@@ -6,7 +6,6 @@ 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';
@@ -36,19 +35,14 @@ class Dashboard extends React.Component {
}
}
const mapStateToProps = state => {
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(
connect(mapStateToProps, mapDispatchToProps),
connect(mapStateToProps),
getMetrics
)(Dashboard);
@@ -18,10 +18,10 @@ const FlagWidget = ({assets}) => {
<div className={styles.widgetTable}>
{
assets.length
? assets.map(asset => {
? assets.map((asset) => {
let flagSummary = null;
if (asset.action_summaries) {
flagSummary = asset.action_summaries.find(s => s.type === 'FlagAssetActionSummary');
flagSummary = asset.action_summaries.find((s) => s.__typename === 'FlagAssetActionSummary');
}
return (
@@ -18,8 +18,8 @@ const LikeWidget = ({assets}) => {
<div className={styles.widgetTable}>
{
assets.length
? assets.map(asset => {
const likeSummary = asset.action_summaries.find(s => s.type === 'LikeAssetActionSummary');
? 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>
@@ -7,7 +7,7 @@ import BanUserDialog from 'coral-admin/src/components/BanUserDialog';
const lang = new I18n(translations);
const MostLikedCommentsWidget = props => {
const MostLikedCommentsWidget = (props) => {
const {
comments,
moderation,
@@ -64,32 +64,32 @@ InstallContainer.contextTypes = {
router: React.PropTypes.object
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
install: state.install.toJS()
});
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch) => ({
nextStep: () => dispatch(nextStep()),
goToStep: step => dispatch(goToStep(step)),
goToStep: (step) => dispatch(goToStep(step)),
previousStep: () => dispatch(previousStep()),
finishInstall: () => dispatch(finishInstall()),
checkInstall: next => dispatch(checkInstall(next)),
handleDomainsChange: value => {
checkInstall: (next) => dispatch(checkInstall(next)),
handleDomainsChange: (value) => {
dispatch(updatePermittedDomains(value));
},
handleSettingsChange: e => {
handleSettingsChange: (e) => {
const {name, value} = e.currentTarget;
dispatch(updateSettingsFormData(name, value));
},
handleUserChange: e => {
handleUserChange: (e) => {
const {name, value} = e.currentTarget;
dispatch(updateUserFormData(name, value));
},
handleSettingsSubmit: e => {
handleSettingsSubmit: (e) => {
e.preventDefault();
dispatch(submitSettings());
},
handleUserSubmit: e => {
handleUserSubmit: (e) => {
e.preventDefault();
dispatch(submitUser());
}
@@ -6,7 +6,7 @@ const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const AddOrganizationName = props => {
const AddOrganizationName = (props) => {
const {handleSettingsChange, handleSettingsSubmit, install} = props;
return (
<div className={styles.step}>
@@ -6,7 +6,7 @@ const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const InitialStep = props => {
const InitialStep = (props) => {
const {handleUserChange, handleUserSubmit, install} = props;
return (
<div className={styles.step}>
@@ -6,7 +6,7 @@ const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const InitialStep = props => {
const InitialStep = (props) => {
const {nextStep} = props;
return (
<div className={styles.step}>
@@ -2,7 +2,7 @@ import React from 'react';
import styles from './style.css';
import {Button, Select, Option, TextField} from 'coral-ui';
const InviteTeamMembers = props => {
const InviteTeamMembers = (props) => {
const {nextStep} = props;
return (
<div className={styles.step}>
@@ -7,7 +7,7 @@ const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const PermittedDomainsStep = props => {
const PermittedDomainsStep = (props) => {
const {finishInstall, install, handleDomainsChange} = props;
const domains = install.data.settings.domains.whitelist;
return (
@@ -19,8 +19,8 @@ const PermittedDomainsStep = props => {
value={domains}
inputProps={{placeholder: 'URL'}}
addOnPaste={true}
pasteSplit={data => data.split(',').map(d => d.trim())}
onChange={tags => handleDomainsChange(tags)}
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>
@@ -1,21 +1,24 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import Layout from '../components/ui/Layout';
import {checkLogin, handleLogin, logout, requestPasswordReset} from '../actions/auth';
import {fetchConfig} from '../actions/config';
import {FullLoading} from '../components/FullLoading';
import AdminLogin from '../components/AdminLogin';
import {logout} from 'coral-framework/actions/auth';
import {FullLoading} from '../components/FullLoading';
import {toggleModal as toggleShortcutModal} from '../actions/moderation';
import {checkLogin, handleLogin, requestPasswordReset} from '../actions/auth';
import {can} from 'coral-framework/services/perms';
class LayoutContainer extends Component {
componentWillMount () {
componentWillMount() {
const {checkLogin, fetchConfig} = this.props;
checkLogin();
fetchConfig();
}
render () {
render() {
const {
isAdmin,
user,
loggedIn,
loadingUser,
loginError,
@@ -23,36 +26,60 @@ class LayoutContainer extends Component {
passwordRequestSuccess
} = this.props.auth;
const {handleLogout, TALK_RECAPTCHA_PUBLIC} = this.props;
if (loadingUser) { return <FullLoading />; }
if (!isAdmin) {
return <AdminLogin
loginMaxExceeded={loginMaxExceeded}
handleLogin={this.props.handleLogin}
requestPasswordReset={this.props.requestPasswordReset}
passwordRequestSuccess={passwordRequestSuccess}
recaptchaPublic={TALK_RECAPTCHA_PUBLIC}
errorMessage={loginError} />;
const {
handleLogout,
toggleShortcutModal,
TALK_RECAPTCHA_PUBLIC
} = this.props;
if (loadingUser) {
return <FullLoading />;
}
if (!loggedIn) {
return (
<AdminLogin
loginMaxExceeded={loginMaxExceeded}
handleLogin={this.props.handleLogin}
requestPasswordReset={this.props.requestPasswordReset}
passwordRequestSuccess={passwordRequestSuccess}
recaptchaPublic={TALK_RECAPTCHA_PUBLIC}
errorMessage={loginError}
/>
);
}
if (can(user, 'ACCESS_ADMIN') && loggedIn) {
return (
<Layout
handleLogout={handleLogout}
toggleShortcutModal={toggleShortcutModal}
{...this.props}
/>
);
} else if (loggedIn) {
return (
<Layout {...this.props}>
<p>This page is for team use only. Please contact an administrator if you want to join this team.</p>
</Layout>
);
}
if (isAdmin && loggedIn) { return <Layout handleLogout={handleLogout} {...this.props} />; }
return <FullLoading />;
}
}
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
auth: state.auth.toJS(),
TALK_RECAPTCHA_PUBLIC: state.config.get('data').get('TALK_RECAPTCHA_PUBLIC', null)
TALK_RECAPTCHA_PUBLIC: state.config
.get('data')
.get('TALK_RECAPTCHA_PUBLIC', null)
});
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch) => ({
checkLogin: () => dispatch(checkLogin()),
fetchConfig: () => dispatch(fetchConfig()),
handleLogin: (username, password, recaptchaResponse) => dispatch(handleLogin(username, password, recaptchaResponse)),
requestPasswordReset: email => dispatch(requestPasswordReset(email)),
handleLogin: (username, password, recaptchaResponse) =>
dispatch(handleLogin(username, password, recaptchaResponse)),
requestPasswordReset: (email) => dispatch(requestPasswordReset(email)),
toggleShortcutModal: (toggle) => dispatch(toggleShortcutModal(toggle)),
handleLogout: () => dispatch(logout())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(LayoutContainer);
export default connect(mapStateToProps, mapDispatchToProps)(LayoutContainer);
@@ -1,24 +1,42 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import * as notification from 'coral-admin/src/services/notification';
import key from 'keymaster';
import isEqual from 'lodash/isEqual';
import styles from './components/styles.css';
import translations from 'coral-admin/src/translations';
import I18n from 'coral-framework/modules/i18n/i18n';
import {modQueueQuery} from '../../graphql/queries';
import {banUser, setCommentStatus} from '../../graphql/mutations';
import {modQueueQuery, getQueueCounts} from '../../graphql/queries';
import {banUser, setCommentStatus, suspendUser} from '../../graphql/mutations';
import {fetchSettings} from 'actions/settings';
import {updateAssets} from 'actions/assets';
import {toggleModal, singleView, showBanUserDialog, hideBanUserDialog} from 'actions/moderation';
import {
toggleModal,
singleView,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog,
hideShortcutsNote,
viewUserDetail,
hideUserDetail
} from 'actions/moderation';
import {Spinner} from 'coral-ui';
import BanUserDialog from '../../components/BanUserDialog';
import BanUserDialog from './components/BanUserDialog';
import SuspendUserDialog from './components/SuspendUserDialog';
import ModerationQueue from './ModerationQueue';
import ModerationMenu from './components/ModerationMenu';
import ModerationHeader from './components/ModerationHeader';
import NotFoundAsset from './components/NotFoundAsset';
import ModerationKeysModal from '../../components/ModerationKeysModal';
import UserDetail from './UserDetail';
const lang = new I18n(translations);
class ModerationContainer extends Component {
state = {
@@ -43,12 +61,13 @@ class ModerationContainer extends Component {
const {acceptComment, rejectComment} = this.props;
const {selectedIndex} = this.state;
const comments = this.getComments();
const commentId = {commentId: comments[selectedIndex].id};
const comment = comments[selectedIndex];
const commentId = {commentId: comment.id};
if (accept) {
acceptComment(commentId);
comment.status !== 'ACCEPTED' && acceptComment(commentId);
} else {
rejectComment(commentId);
comment.status !== 'REJECTED' && rejectComment(commentId);
}
}
@@ -60,14 +79,14 @@ class ModerationContainer extends Component {
select = (next) => () => {
if (next) {
this.setState(prevState =>
this.setState((prevState) =>
({
...prevState,
selectedIndex: prevState.selectedIndex < this.getComments().length - 1
? prevState.selectedIndex + 1 : prevState.selectedIndex
}));
} else {
this.setState(prevState =>
this.setState((prevState) =>
({
...prevState,
selectedIndex: prevState.selectedIndex > 0 ?
@@ -81,6 +100,33 @@ class ModerationContainer extends Component {
this.props.modQueueResort(sort);
}
suspendUser = async (args) => {
this.props.hideSuspendUserDialog();
try {
const result = await this.props.suspendUser(args);
if (result.data.suspendUser.errors) {
throw result.data.suspendUser.errors;
}
notification.success(
lang.t('suspenduser.notify_suspend_until',
this.props.moderation.suspendUserDialog.username,
lang.timeago(args.until)),
);
const {commentStatus, commentId} = this.props.moderation.suspendUserDialog;
if (commentStatus !== 'REJECTED') {
return this.props.rejectComment({commentId})
.then((result) => {
if (result.data.setCommentStatus.errors) {
throw result.data.setCommentStatus.errors;
}
});
}
}
catch(err) {
notification.showMutationErrors(err);
}
};
componentWillUnmount() {
key.unbind('s');
key.unbind('shift+/');
@@ -110,7 +156,7 @@ class ModerationContainer extends Component {
}
render () {
const {data, moderation, settings, assets, onClose, ...props} = this.props;
const {data, moderation, settings, assets, onClose, viewUserDetail, hideUserDetail, ...props} = this.props;
const providedAssetId = this.props.params.id;
const activeTab = this.props.route.path === ':id' ? 'premod' : this.props.route.path;
@@ -125,7 +171,7 @@ class ModerationContainer extends Component {
}
if (providedAssetId) {
asset = assets.find(asset => asset.id === this.props.params.id);
asset = assets.find((asset) => asset.id === this.props.params.id);
if (!asset) {
return <NotFoundAsset assetId={providedAssetId} />;
@@ -135,6 +181,12 @@ class ModerationContainer extends Component {
const comments = data[activeTab];
let activeTabCount;
switch(activeTab) {
case 'all':
activeTabCount = data.allCount;
break;
case 'accepted':
activeTabCount = data.acceptedCount;
break;
case 'premod':
activeTabCount = data.premodCount;
break;
@@ -151,6 +203,8 @@ class ModerationContainer extends Component {
<ModerationHeader asset={asset} />
<ModerationMenu
asset={asset}
allCount={data.allCount}
acceptedCount={data.acceptedCount}
premodCount={data.premodCount}
rejectedCount={data.rejectedCount}
flaggedCount={data.flaggedCount}
@@ -166,49 +220,79 @@ class ModerationContainer extends Component {
bannedWords={settings.wordlist.banned}
suspectWords={settings.wordlist.suspect}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
loadMore={props.loadMore}
assetId={providedAssetId}
sort={this.state.sort}
commentCount={activeTabCount}
currentUserId={this.props.auth.user.id}
viewUserDetail={viewUserDetail}
hideUserDetail={hideUserDetail}
/>
<BanUserDialog
open={moderation.banDialog}
user={moderation.user}
commentId={moderation.commentId}
commentStatus={moderation.commentStatus}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
showRejectedNote={moderation.showRejectedNote}
rejectComment={props.rejectComment}
/>
<ModerationKeysModal
<SuspendUserDialog
open={moderation.suspendUserDialog.show}
username={moderation.suspendUserDialog.username}
userId={moderation.suspendUserDialog.userId}
organizationName={data.settings.organizationName}
onCancel={props.hideSuspendUserDialog}
onPerform={this.suspendUser}
/>
<ModerationKeysModal
hideShortcutsNote={props.hideShortcutsNote}
shortcutsNoteVisible={moderation.shortcutsNoteVisible}
open={moderation.modalOpen}
onClose={onClose}/>
{moderation.userDetailId && (
<UserDetail
id={moderation.userDetailId}
hideUserDetail={hideUserDetail} />
)}
</div>
);
}
}
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
moderation: state.moderation.toJS(),
settings: state.settings.toJS(),
auth: state.auth.toJS(),
assets: state.assets.get('assets')
});
const mapDispatchToProps = dispatch => ({
toggleModal: toggle => dispatch(toggleModal(toggle)),
const mapDispatchToProps = (dispatch) => ({
onClose: () => dispatch(toggleModal(false)),
singleView: () => dispatch(singleView()),
updateAssets: assets => dispatch(updateAssets(assets)),
fetchSettings: () => dispatch(fetchSettings()),
showBanUserDialog: (user, commentId, showRejectedNote) => dispatch(showBanUserDialog(user, commentId, showRejectedNote)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
...bindActionCreators({
toggleModal,
singleView,
updateAssets,
fetchSettings,
showBanUserDialog,
hideShortcutsNote,
showSuspendUserDialog,
hideSuspendUserDialog,
viewUserDetail,
hideUserDetail,
}, dispatch),
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
setCommentStatus,
getQueueCounts,
banUser,
suspendUser,
modQueueQuery,
banUser
)(ModerationContainer);
@@ -1,6 +1,6 @@
import React from 'react';
const ModerationLayout = props => (
const ModerationLayout = (props) => (
<div>
{props.children}
</div>
@@ -9,52 +9,82 @@ import translations from 'coral-admin/src/translations';
import LoadMore from './components/LoadMore';
const lang = new I18n(translations);
const ModerationQueue = ({comments, selectedIndex, commentCount, singleView, loadMore, activeTab, sort, ...props}) => {
return (
<div id="moderationList" className={`${styles.list} ${singleView ? styles.singleView : ''}`}>
<ul style={{paddingLeft: 0}}>
{
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}
rejectComment={props.rejectComment}
currentAsset={props.currentAsset}
/>;
})
: <EmptyCard>{lang.t('modqueue.emptyqueue')}</EmptyCard>
}
</ul>
<LoadMore
comments={comments}
loadMore={loadMore}
sort={sort}
tab={activeTab}
showLoadMore={comments.length < commentCount}
assetId={props.assetId}
/>
</div>
);
};
class ModerationQueue extends React.Component {
ModerationQueue.propTypes = {
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
showBanUserDialog: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
comments: PropTypes.array.isRequired
};
static propTypes = {
viewUserDetail: PropTypes.func.isRequired,
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
showBanUserDialog: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
comments: PropTypes.array.isRequired
}
componentDidUpdate (prev) {
const {loadMore, comments, commentCount, sort, activeTab: tab, assetId: asset_id} = this.props;
// if the user just moderated the last (visible) comment
// AND there are more comments available on the server,
// go ahead and load more comments
if (prev.comments.length > 0 && comments.length === 0 && commentCount > 0) {
loadMore({sort, tab, asset_id});
}
}
render () {
const {
comments,
selectedIndex,
commentCount,
singleView,
loadMore,
activeTab,
sort,
viewUserDetail,
...props
} = this.props;
return (
<div id="moderationList" className={`${styles.list} ${singleView ? styles.singleView : ''}`}>
<ul style={{paddingLeft: 0}}>
{
comments.length
? comments.map((comment, i) => {
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
return <Comment
key={i}
index={i}
comment={comment}
selected={i === selectedIndex}
suspectWords={props.suspectWords}
bannedWords={props.bannedWords}
viewUserDetail={viewUserDetail}
actions={actionsMap[status]}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
currentAsset={props.currentAsset}
currentUserId={this.props.currentUserId}
/>;
})
: <EmptyCard>{lang.t('modqueue.emptyqueue')}</EmptyCard>
}
</ul>
<LoadMore
comments={comments}
loadMore={loadMore}
sort={sort}
tab={activeTab}
showLoadMore={comments.length < commentCount}
assetId={props.assetId}
/>
</div>
);
}
}
export default ModerationQueue;
@@ -0,0 +1,41 @@
.copyButton {
float: right;
top: -10px;
}
.memberSince {
clear: both;
}
.small {
color: #aaa;
}
.stats {
display: flex;
.stat {
margin: 0 4px 12px;
}
.stat:last-child {
margin-right: 0;
}
p {
margin: 0;
}
.stat p:first-child {
font-weight: bold;
}
}
.profileEmail {
border: none;
background-color: transparent;
font-size: 16px;
position: absolute;
width: 100%;
outline: none;
}
@@ -0,0 +1,74 @@
import React, {PropTypes} from 'react';
import {Button, Drawer} from 'coral-ui';
import styles from './UserDetail.css';
import {compose} from 'react-apollo';
import {getUserDetail} from 'coral-admin/src/graphql/queries';
import Slot from 'coral-framework/components/Slot';
class UserDetail extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
hideUserDetail: PropTypes.func.isRequired
}
copyPermalink = () => {
this.profile.select();
try {
document.execCommand('copy');
} catch (e) {
/* nothing */
}
}
render () {
const {data, hideUserDetail} = this.props;
if (!('user' in data)) {
return null;
}
const {user, totalComments, rejectedComments} = data;
const localProfile = user.profiles.find((p) => p.provider === 'local');
let profile;
if (localProfile) {
profile = localProfile.id;
}
let rejectedPercent = rejectedComments / totalComments;
if (rejectedPercent === Infinity || isNaN(rejectedPercent)) {
// if totalComments is 0, you're dividing by zero, which is naughty
rejectedPercent = 0;
}
return (
<Drawer handleClickOutside={hideUserDetail}>
<h3>{user.username}</h3>
<Button className={styles.copyButton} onClick={this.copyPermalink}>Copy</Button>
{profile && <input className={styles.profileEmail} readOnly type="text" ref={(ref) => this.profile = ref} value={profile} />}
<Slot fill="userProfile" user={user} />
<p className={styles.memberSince}><strong>Member since</strong> {new Date(user.created_at).toLocaleString()}</p>
<hr/>
<p>
<strong>Account summary</strong>
<br/><small className={styles.small}>Data represents the last six months of activity</small>
</p>
<div className={styles.stats}>
<div className={styles.stat}>
<p>Total Comments</p>
<p>{totalComments}</p>
</div>
<div className={styles.stat}>
<p>Reject Rate</p>
<p>{`${(rejectedPercent).toFixed(1)}%`}</p>
</div>
</div>
</Drawer>
);
}
}
export default compose(
getUserDetail
)(UserDetail);
@@ -152,13 +152,14 @@ input.error{
.cancel {
margin-right: 10px;
width: 47%;
width: 48%;
}
.ban {
width: 47%;
width: 48%;
}
.buttons {
margin: 20px 0;
margin: 20px;
text-align: center;
}
@@ -5,17 +5,17 @@ import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
import translations from '../../../translations';
const lang = new I18n(translations);
const onBanClick = (userId, commentId, handleBanUser, rejectComment, handleClose) => (e) => {
const onBanClick = (userId, commentId, commentStatus, handleBanUser, rejectComment, handleClose) => (e) => {
e.preventDefault();
handleBanUser({userId})
.then(handleClose)
.then(() => rejectComment({commentId}));
.then(() => commentStatus === 'REJECTED' ? null : rejectComment({commentId}));
};
const BanUserDialog = ({open, handleClose, handleBanUser, rejectComment, user, commentId, showRejectedNote}) => (
const BanUserDialog = ({open, handleClose, handleBanUser, rejectComment, user, commentId, commentStatus, showRejectedNote}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
@@ -36,7 +36,7 @@ const BanUserDialog = ({open, handleClose, handleBanUser, rejectComment, user, c
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{lang.t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={onBanClick(user.id, commentId, handleBanUser, rejectComment, handleClose)} raised>
<Button cStyle="black" className={styles.ban} onClick={onBanClick(user.id, commentId, commentStatus, handleBanUser, rejectComment, handleClose)} raised>
{lang.t('bandialog.yes_ban_user')}
</Button>
</div>
@@ -1,16 +1,18 @@
import React, {PropTypes} from 'react';
import timeago from 'timeago.js';
import Linkify from 'react-linkify';
import Highlighter from 'react-highlight-words';
import {Link} from 'react-router';
import Linkify from 'react-linkify';
import styles from './styles.css';
import {Icon} from 'coral-ui';
import FlagBox from './FlagBox';
import styles from './styles.css';
import CommentType from './CommentType';
import ActionButton from 'coral-admin/src/components/ActionButton';
import BanUserButton from 'coral-admin/src/components/BanUserButton';
import Highlighter from 'react-highlight-words';
import Slot from 'coral-framework/components/Slot';
import {getActionSummary} from 'coral-framework/utils';
import ActionButton from 'coral-admin/src/components/ActionButton';
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
const linkify = new Linkify();
@@ -18,85 +20,166 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
const Comment = ({actions = [], comment, ...props}) => {
const Comment = ({
actions = [],
comment,
viewUserDetail,
suspectWords,
bannedWords,
...props
}) => {
const links = linkify.getMatches(comment.body);
const linkText = links ? links.map(link => link.raw) : [];
const linkText = links ? links.map((link) => link.raw) : [];
const flagActionSummaries = getActionSummary('FlagActionSummary', comment);
const flagActions = comment.actions && comment.actions.filter(a => a.__typename === 'FlagAction');
const flagActions =
comment.actions &&
comment.actions.filter((a) => a.__typename === 'FlagAction');
let commentType = '';
if (comment.status === 'PREMOD') {
commentType = 'premod';
} else if (flagActions && flagActions.length) {
commentType = 'flagged';
}
// since words are checked against word boundaries on the backend,
// this should be the behavior on the front end as well.
// currently the highlighter plugin does not support this out of the box.
const searchWords = [...suspectWords, ...bannedWords]
.filter((w) => {
return new RegExp(`(^|\\s)${w}(\\s|$)`).test(comment.body);
})
.concat(linkText);
return (
<li tabIndex={props.index} className={`mdl-card ${props.selected ? 'mdl-shadow--8dp' : 'mdl-shadow--2dp'} ${styles.Comment} ${styles.listItem}`}>
<li
tabIndex={props.index}
className={`mdl-card ${props.selected ? 'mdl-shadow--16dp' : 'mdl-shadow--2dp'} ${styles.Comment} ${styles.listItem} ${props.selected ? styles.selected : ''}`}
>
<div className={styles.container}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span>
<span className={styles.username} onClick={() => viewUserDetail(comment.user.id)}>
{comment.user.name}
</span>
<span className={styles.created}>
{timeago().format(comment.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}
{timeago().format(
comment.created_at || Date.now() - props.index * 60 * 1000,
lang.getLocale().replace('-', '_')
)}
</span>
<BanUserButton user={comment.user} onClick={() => props.showBanUserDialog(comment.user, comment.id, comment.status !== 'REJECTED')} />
<CommentType type={props.commentType} />
{props.currentUserId !== comment.user.id &&
<ActionsMenu icon="not_interested">
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showSuspendUserDialog(comment.user.id, comment.user.name, comment.id, comment.status)}>
Suspend User</ActionsMenuItem>
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showBanUserDialog(comment.user, comment.id, comment.status, comment.status !== 'REJECTED')}>
Ban User
</ActionsMenuItem>
</ActionsMenu>
}
<CommentType type={commentType} />
</div>
{comment.user.status === 'banned' ?
<span className={styles.banned}>
<Icon name='error_outline'/>
{lang.t('comment.banned_user')}
</span>
{comment.user.status === 'banned'
? <span className={styles.banned}>
<Icon name="error_outline" />
{lang.t('comment.banned_user')}
</span>
: null}
<Slot fill="adminCommentInfoBar" comment={comment} />
</div>
<div className={styles.moderateArticle}>
Story: {comment.asset.title}
{!props.currentAsset && (
<Link to={`/admin/moderate/${comment.asset.id}`}>Moderate </Link>
)}
{!props.currentAsset &&
<Link to={`/admin/moderate/${comment.asset.id}`}>Moderate </Link>}
</div>
<div className={styles.itemBody}>
<p className={styles.body}>
<Highlighter
searchWords={[...props.suspectWords, ...props.bannedWords, ...linkText]}
textToHighlight={comment.body} />
searchWords={searchWords}
textToHighlight={comment.body}
/>
{' '}
<a
className={styles.external}
href={`${comment.asset.url}#${comment.id}`}
target="_blank"
>
<Icon name="open_in_new" /> {lang.t('comment.view_context')}
</a>
</p>
<Slot fill="adminCommentContent" comment={comment} />
<div className={styles.sideActions}>
{links ? <span className={styles.hasLinks}><Icon name='error_outline'/> Contains Link</span> : null}
{links
? <span className={styles.hasLinks}>
<Icon name="error_outline" /> Contains Link
</span>
: null}
<div className={`actions ${styles.actions}`}>
{actions.map((action, i) =>
<ActionButton key={i}
type={action}
user={comment.user}
acceptComment={() => props.acceptComment({commentId: comment.id})}
rejectComment={() => props.rejectComment({commentId: comment.id})}
/>
)}
{actions.map((action, i) => {
const active =
(action === 'REJECT' && comment.status === 'REJECTED') ||
(action === 'APPROVE' && comment.status === 'ACCEPTED');
return (
<ActionButton
key={i}
type={action}
user={comment.user}
status={comment.status}
active={active}
acceptComment={() =>
(comment.status === 'ACCEPTED'
? null
: props.acceptComment({commentId: comment.id}))}
rejectComment={() =>
(comment.status === 'REJECTED'
? null
: props.rejectComment({commentId: comment.id}))}
/>
);
})}
</div>
<Slot fill="adminSideActions" comment={comment} />
</div>
</div>
</div>
{
flagActions && flagActions.length
? <FlagBox actions={flagActions} actionSummaries={flagActionSummaries} />
: null
}
<div>
<Slot fill="adminCommentDetailArea" comment={comment} />
</div>
{flagActions && flagActions.length
? <FlagBox
actions={flagActions}
actionSummaries={flagActionSummaries}
/>
: null}
</li>
);
};
Comment.propTypes = {
viewUserDetail: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
showBanUserDialog: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func.isRequired,
currentUserId: PropTypes.string.isRequired,
comment: PropTypes.shape({
body: PropTypes.string.isRequired,
action_summaries: PropTypes.array,
actions: PropTypes.array,
created_at: PropTypes.string.isRequired,
user: PropTypes.shape({
id: PropTypes.string,
status: PropTypes.string
}),
}).isRequired,
asset: PropTypes.shape({
title: PropTypes.string,
url: PropTypes.string,
id: PropTypes.string
})
})
@@ -1,9 +1,25 @@
import React, {PropTypes} from 'react';
import styles from './CommentCount.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
const CommentCount = props => (
<span className={styles.count}>{props.count}</span>
);
const CommentCount = ({count}) => {
let number = count;
// shorten large counts to abbreviations
if (number / 1e9 > 1) {
number = `${(number / 1e9).toFixed(1)}${lang.t('modqueue.billion')}`;
} else if (number / 1e6 > 1) {
number = `${(number / 1e6).toFixed(1)}${lang.t('modqueue.million')}`;
} else if (number / 1e3 > 1) {
number = `${(number / 1e3).toFixed(1)}${lang.t('modqueue.thousand')}`;
}
return (
<span className={styles.count}>{number}</span>
);
};
CommentCount.propTypes = {
count: PropTypes.number.isRequired
@@ -2,7 +2,7 @@ import React, {PropTypes} from 'react';
import styles from './CommentType.css';
import {Icon} from 'coral-ui';
const CommentType = props => {
const CommentType = (props) => {
const typeData = getTypeData(props.type);
return (
@@ -12,7 +12,7 @@ const CommentType = props => {
);
};
const getTypeData = type => {
const getTypeData = (type) => {
switch (type) {
case 'premod':
return {icon: 'query_builder', text: 'Pre-Mod', className: 'premod'};
@@ -55,7 +55,7 @@ class FlagBox extends Component {
<ul>
{actionSummaries.map((summary, i) => {
const actionList = actions.filter(a => a.reason === summary.reason);
const actionList = actions.filter((a) => a.reason === summary.reason);
return (
<li key={i}>
@@ -7,13 +7,16 @@ const LoadMore = ({comments, loadMore, sort, tab, assetId, showLoadMore}) =>
{
showLoadMore && <Button
className={styles.loadMore}
onClick={() =>
loadMore({
cursor: comments[comments.length - 1].created_at,
onClick={() => {
const lastComment = comments[comments.length - 1];
const cursor = lastComment ? lastComment.created_at : null;
return loadMore({
cursor,
sort,
tab,
asset_id: assetId
})}>
});
}}>
Load More
</Button>
}
@@ -23,7 +26,7 @@ LoadMore.propTypes = {
comments: PropTypes.array.isRequired,
loadMore: PropTypes.func.isRequired,
sort: PropTypes.oneOf(['CHRONOLOGICAL', 'REVERSE_CHRONOLOGICAL']).isRequired,
tab: PropTypes.oneOf(['rejected', 'premod', 'flagged']).isRequired,
tab: PropTypes.oneOf(['rejected', 'premod', 'flagged', 'all', 'accepted']).isRequired,
assetId: PropTypes.string,
showLoadMore: PropTypes.bool.isRequired
};
@@ -3,7 +3,7 @@ import {Link} from 'react-router';
import {Icon} from 'coral-ui';
import styles from './styles.css';
const ModerationHeader = props => (
const ModerationHeader = (props) => (
<div className=''>
<div className={`mdl-tabs ${styles.header}`}>
{
@@ -4,22 +4,18 @@ 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 {Icon} from 'coral-ui';
import {Link} from 'react-router';
const lang = new I18n(translations);
const ModerationMenu = (
{asset, premodCount, rejectedCount, flaggedCount, selectSort, sort}
{asset, allCount, acceptedCount, 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';
function getPath (type) {
return asset ? `/admin/moderate/${type}/${asset.id}` : `/admin/moderate/${type}`;
}
return (
<div className="mdl-tabs">
@@ -27,29 +23,41 @@ const ModerationMenu = (
<div className={styles.tabBarPadding} />
<div>
<Link
to={premodPath}
to={getPath('all')}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.premod')} <CommentCount count={premodCount} />
<Icon name='question_answer' className={styles.tabIcon} /> {lang.t('modqueue.all')} <CommentCount count={allCount} />
</Link>
<Link
to={flagPath}
to={getPath('premod')}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.flagged')} <CommentCount count={flaggedCount} />
<Icon name='access_time' className={styles.tabIcon} /> {lang.t('modqueue.premod')} <CommentCount count={premodCount} />
</Link>
<Link
to={rejectPath}
to={getPath('flagged')}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.rejected')} <CommentCount count={rejectedCount} />
<Icon name='flag' className={styles.tabIcon} /> {lang.t('modqueue.flagged')} <CommentCount count={flaggedCount} />
</Link>
<Link
to={getPath('accepted')}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
<Icon name='check' className={styles.tabIcon} /> {lang.t('modqueue.approved')} <CommentCount count={acceptedCount} />
</Link>
<Link
to={getPath('rejected')}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
<Icon name='close' className={styles.tabIcon} /> {lang.t('modqueue.rejected')} <CommentCount count={rejectedCount} />
</Link>
</div>
<SelectField
className={styles.selectField}
label="Sort"
value={sort}
onChange={sort => selectSort(sort)}>
onChange={(sort) => selectSort(sort)}>
<Option value={'REVERSE_CHRONOLOGICAL'}>Newest First</Option>
<Option value={'CHRONOLOGICAL'}>Oldest First</Option>
</SelectField>
@@ -59,6 +67,7 @@ const ModerationMenu = (
};
ModerationMenu.propTypes = {
allCount: PropTypes.number.isRequired,
premodCount: PropTypes.number.isRequired,
rejectedCount: PropTypes.number.isRequired,
flaggedCount: PropTypes.number.isRequired,
@@ -2,7 +2,7 @@ import React from 'react';
import {Link} from 'react-router';
import styles from './styles.css';
const NotFound = props => (
const NotFound = (props) => (
<div className={`mdl-card mdl-shadow--2dp ${styles.notFound}`}>
<p>
The provided asset id <Link to={`/admin/moderate/${props.assetId}`}>{props.assetId}</Link> does not exist.
@@ -0,0 +1,90 @@
.dialog {
border: none;
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
width: 400px;
top: 50%;
transform: translateY(-50%);
padding: 20px;
border-radius: 4px;
}
.header {
color: black;
font-size: 1.5em;
font-weight: 500;
margin: 0 0 8px 0;
}
.close {
display: block;
position: absolute;
top: 24px;
right: 20px;
}
.closeButton {
userSelect: none;
outline: none;
border: none;
touchAction: manipulation;
&::-moz-focus-inner: {
border: 0;
}
background: 0;
padding: 0;
font-size: 24px;
line-height: 14px;
cursor: pointer;
color: #363636;
&:hover {
color: #6b6b6b;
}
}
.legend {
padding: 0;
font-weight: bold;
}
div.radioGroup {
margin-top: 6px;
}
label.radioGroup {
&:global(.is-checked) > :global(.mdl-radio__outer-circle),
> :global(.mdl-radio__outer-circle) {
border-color: #212121;
}
> :global(.mdl-radio__inner-circle) {
background: #212121;
}
> :global(.mdl-radio__label) {
font-size: 14px;
line-height: 20px;
}
}
.messageInput {
border-radius: 3px;
width: 100%;
padding: 10px;
font-size: 14px;
box-sizing: border-box;
}
.cancel {
margin-right: 5px;
}
.perform {
min-width: 90px;
}
.buttons {
margin-top: 8px;
margin-bottom: 6px;
text-align: right;
}
@@ -0,0 +1,165 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import {RadioGroup, Radio} from 'react-mdl';
import styles from './SuspendUserDialog.css';
import Button from 'coral-ui/components/Button';
import I18n from 'coral-framework/modules/i18n/i18n';
import {dateAdd} from 'coral-framework/utils';
import translations from '../../../translations';
const lang = new I18n(translations);
const initialState = {step: 0, duration: '3'};
function durationsToDate(hours) {
// Add 1 minute more to help `timeago.js` to display the correct duration.
return dateAdd(new Date(), 'minute', hours * 60 + 1);
}
class SuspendUserDialog extends React.Component {
state = initialState;
componentWillReceiveProps(next) {
if (this.props.open && !next.open) {
this.setState(initialState);
}
}
handleDurationChange = (event) => {
this.setState({duration: event.target.value});
}
handleMessageChange = (event) => {
this.setState({message: event.target.value});
}
goToStep1 = () => {
this.setState({
step: 1,
message: lang.t(
'suspenduser.email_message_suspend',
this.props.username,
this.props.organizationName,
lang.timeago(durationsToDate(this.state.duration)),
),
});
}
handlePerform = () => {
this.props.onPerform({
id: this.props.userId,
message: this.state.message,
// Add 1 minute more to help `timeago.js` to display the correct duration.
until: durationsToDate(this.state.duration),
});
};
renderStep0() {
const {onCancel, username} = this.props;
const {duration} = this.state;
return (
<section>
<h1 className={styles.header}>
{lang.t('suspenduser.title_suspend')}
</h1>
<p className={styles.description}>
{lang.t('suspenduser.description_suspend', username)}
</p>
<fieldset>
<legend className={styles.legend}>{lang.t('suspenduser.select_duration')}</legend>
<RadioGroup
name='status filter'
value={duration}
childContainer='div'
onChange={this.handleDurationChange}
className={styles.radioGroup}
>
<Radio value='1'>{lang.t('suspenduser.one_hour')}</Radio>
<Radio value='3'>{lang.t('suspenduser.hours', 3)}</Radio>
<Radio value='24'>{lang.t('suspenduser.hours', 24)}</Radio>
<Radio value='168'>{lang.t('suspenduser.days', 7)}</Radio>
</RadioGroup>
</fieldset>
<div className={styles.buttons}>
<Button cStyle="white" className={styles.cancel} onClick={onCancel} raised>
{lang.t('suspenduser.cancel')}
</Button>
<Button cStyle="black" className={styles.perform} onClick={this.goToStep1} raised>
{lang.t('suspenduser.suspend_user')}
</Button>
</div>
</section>
);
}
renderStep1() {
const {onCancel, username} = this.props;
const {message} = this.state;
return (
<section>
<h1 className={styles.header}>
{lang.t('suspenduser.title_notify')}
</h1>
<p className={styles.description}>
{lang.t('suspenduser.description_notify', username)}
</p>
<fieldset>
<legend className={styles.legend}>{lang.t('suspenduser.write_message')}</legend>
<textarea
rows={5}
className={styles.messageInput}
value={message}
onChange={this.handleMessageChange} />
</fieldset>
<div className={styles.buttons}>
<Button cStyle="white" className={styles.cancel} onClick={onCancel} raised>
{lang.t('suspenduser.cancel')}
</Button>
<Button
cStyle="black"
className={styles.perform}
onClick={this.handlePerform}
disabled={this.state.message.length === 0}
raised
>
{lang.t('suspenduser.send')}
</Button>
</div>
</section>
);
}
render() {
const {open, onCancel} = this.props;
const {step} = this.state;
return (
<Dialog
className={styles.dialog}
onCancel={onCancel}
open={open}
>
<div className={styles.close}>
<button aria-label="Close" onClick={onCancel} className={styles.closeButton}>×</button>
</div>
{step === 0 && this.renderStep0()}
{step === 1 && this.renderStep1()}
</Dialog>
);
}
}
SuspendUserDialog.propTypes = {
open: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onPerform: PropTypes.func.isRequired,
username: PropTypes.string,
userId: PropTypes.string,
organizationName: PropTypes.string,
};
export default SuspendUserDialog;
@@ -418,3 +418,40 @@ span {
.loadMore:hover {
background-color: #4399FF;
}
.tabIcon {
position: relative;
top: 7px;
}
.username {
color: blue;
text-decoration: underline;
padding: 5px;
cursor: pointer;
&:hover {
background-color: rgba(255, 0, 0, .1);
}
}
.external {
font-size: .7em;
text-decoration: none;
color: #063b9a;
cursor: pointer;
font-weight: normal;
margin-left: 10px;
white-space: nowrap;
&:hover {
text-decoration: underline;
opacity: .9;
}
i {
font-size: 12px;
top: 2px;
position: relative;
}
}
@@ -55,7 +55,7 @@ class Stories extends Component {
onStatusClick = (closeStream, id, statusMenuOpen) => () => {
if (statusMenuOpen) {
this.setState(prev => {
this.setState((prev) => {
prev.statusMenus[id] = false;
return prev;
});
@@ -65,7 +65,7 @@ class Stories extends Component {
this.props.fetchAssets(page, limit, search, sort, filter);
});
} else {
this.setState(prev => {
this.setState((prev) => {
prev.statusMenus[id] = true;
return prev;
});
@@ -54,7 +54,7 @@ class Stories extends Component {
onStatusClick = (closeStream, id, statusMenuOpen) => () => {
if (statusMenuOpen) {
this.setState(prev => {
this.setState((prev) => {
prev.statusMenus[id] = false;
return prev;
});
@@ -64,7 +64,7 @@ class Stories extends Component {
this.props.fetchAssets(page, limit, search, sort, filter);
});
} else {
this.setState(prev => {
this.setState((prev) => {
prev.statusMenus[id] = true;
return prev;
});
@@ -6,7 +6,6 @@ fragment metrics on Asset {
created_at
commentCount
action_summaries {
type: __typename
actionCount
actionableItemCount
}
@@ -11,6 +11,7 @@ fragment commentView on Comment {
asset {
id
title
url
}
action_summaries {
count
@@ -2,6 +2,7 @@ import {graphql} from 'react-apollo';
import SET_USER_STATUS from './setUserStatus.graphql';
import SET_COMMENT_STATUS from './setCommentStatus.graphql';
import SUSPEND_USER from './suspendUser.graphql';
import REJECT_USERNAME from './rejectUsername.graphql';
export const banUser = graphql(SET_USER_STATUS, {
props: ({mutate}) => ({
@@ -32,11 +33,22 @@ export const setUserStatus = graphql(SET_USER_STATUS, {
export const suspendUser = graphql(SUSPEND_USER, {
props: ({mutate}) => ({
suspendUser: ({userId, message}) => {
suspendUser: (input) => {
return mutate({
variables: {
userId,
message
input,
},
});
}
})
});
export const rejectUsername = graphql(REJECT_USERNAME, {
props: ({mutate}) => ({
rejectUsername: (input) => {
return mutate({
variables: {
input,
},
refetchQueries: ['Users']
});
@@ -44,6 +56,7 @@ export const suspendUser = graphql(SUSPEND_USER, {
})
});
const views = ['all', 'premod', 'flagged', 'accepted', 'rejected'];
export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
props: ({mutate}) => ({
acceptComment: ({commentId}) => {
@@ -54,20 +67,37 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
},
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 comment = views.reduce((comment, view) => {
return comment ? comment : oldData[view].find((c) => c.id === commentId);
}, null);
let accepted;
let acceptedCount = oldData.acceptedCount;
// if the comment was already in the Approved queue, don't re-add it
if (comment.status === 'ACCEPTED') {
accepted = [...oldData.accepted];
} else {
comment.status = 'ACCEPTED';
acceptedCount++;
accepted = [comment, ...oldData.accepted];
}
const premod = oldData.premod.filter((c) => c.id !== commentId);
const flagged = oldData.flagged.filter((c) => c.id !== commentId);
const rejected = oldData.rejected.filter((c) => c.id !== commentId);
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
const rejectedCount = rejected.length < oldData.rejected.length ? oldData.rejectedCount - 1 : oldData.rejectedCount;
return {
...oldData,
premodCount,
flaggedCount,
rejectedCount,
premodCount: Math.max(0, premodCount),
flaggedCount: Math.max(0, flaggedCount),
acceptedCount: Math.max(0, acceptedCount),
rejectedCount: Math.max(0, rejectedCount),
premod,
flagged,
accepted,
rejected,
};
}
@@ -82,21 +112,37 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
},
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 comment = views.reduce((comment, view) => {
return comment ? comment : oldData[view].find((c) => c.id === commentId);
}, null);
let rejected;
let rejectedCount = oldData.rejectedCount;
// if the item was already in the Rejected queue, don't put it in again
if (comment.status === 'REJECTED') {
rejected = oldData.rejected;
} else {
comment.status = 'REJECTED';
rejectedCount++;
rejected = [comment, ...oldData.rejected];
}
const premod = oldData.premod.filter((c) => c.id !== commentId);
const flagged = oldData.flagged.filter((c) => c.id !== commentId);
const accepted = oldData.accepted.filter((c) => c.id !== commentId);
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
const rejectedCount = oldData.rejectedCount + 1;
const acceptedCount = accepted.length < oldData.accepted.length ? oldData.acceptedCount - 1 : oldData.acceptedCount;
return {
...oldData,
premodCount,
flaggedCount,
rejectedCount,
premodCount: Math.max(0, premodCount),
flaggedCount: Math.max(0, flaggedCount),
acceptedCount: Math.max(0, acceptedCount),
rejectedCount: Math.max(0, rejectedCount),
premod,
flagged,
accepted,
rejected
};
}
@@ -0,0 +1,7 @@
mutation rejectUsername($input: RejectUsernameInput!) {
rejectUsername(input: $input) {
errors {
translation_key
}
}
}
@@ -1,5 +1,5 @@
mutation suspendUser($userId: ID!, $message: String) {
suspendUser(id: $userId, message: $message) {
mutation suspendUser($input: SuspendUserInput!) {
suspendUser(input: $input) {
errors {
translation_key
}
@@ -0,0 +1,22 @@
query Counts ($asset_id: ID) {
allCount: commentCount(query: {
asset_id: $asset_id
})
acceptedCount: commentCount(query: {
statuses: [ACCEPTED],
asset_id: $asset_id
})
premodCount: commentCount(query: {
statuses: [PREMOD],
asset_id: $asset_id
})
rejectedCount: commentCount(query: {
statuses: [REJECTED],
asset_id: $asset_id
})
flaggedCount: commentCount(query: {
action_type: FLAG,
asset_id: $asset_id,
statuses: [NONE, PREMOD]
})
}
+39 -12
View File
@@ -4,6 +4,8 @@ import MOD_QUEUE_QUERY from './modQueueQuery.graphql';
import MOD_QUEUE_LOAD_MORE from './loadMore.graphql';
import MOD_USER_FLAGGED_QUERY from './modUserFlaggedQuery.graphql';
import METRICS from './metricsQuery.graphql';
import USER_DETAIL from './userDetail.graphql';
import GET_QUEUE_COUNTS from './getQueueCounts.graphql';
export const modQueueQuery = graphql(MOD_QUEUE_QUERY, {
options: ({params: {id = null}}) => {
@@ -33,28 +35,34 @@ export const getMetrics = graphql(METRICS, {
}
});
export const loadMore = (fetchMore) => ({limit, cursor, sort, tab, asset_id}) => {
let statuses;
export const loadMore = (fetchMore) => ({limit = 10, cursor, sort, tab, asset_id}) => {
let variables = {
limit,
cursor,
sort,
asset_id
};
switch(tab) {
case 'all':
variables.statuses = null;
break;
case 'accepted':
variables.statuses = ['ACCEPTED'];
break;
case 'premod':
statuses = ['PREMOD'];
variables.statuses = ['PREMOD'];
break;
case 'flagged':
statuses = ['NONE', 'PREMOD'];
variables.statuses = ['NONE', 'PREMOD'];
variables.action_type = 'FLAG';
break;
case 'rejected':
statuses = ['REJECTED'];
variables.statuses = ['REJECTED'];
break;
}
return fetchMore({
query: MOD_QUEUE_LOAD_MORE,
variables: {
limit,
cursor,
sort,
statuses,
asset_id
},
variables,
updateQuery: (oldData, {fetchMoreResult:{comments}}) => {
return {
...oldData,
@@ -87,3 +95,22 @@ export const modQueueResort = (id, fetchMore) => (sort) => {
updateQuery: (oldData, {fetchMoreResult:{data}}) => data
});
};
export const getUserDetail = graphql(USER_DETAIL, {
options: ({id}) => {
return {
variables: {author_id: id}
};
}
});
export const getQueueCounts = graphql(GET_QUEUE_COUNTS, {
options: ({params: {id = null}}) => {
return {
pollInterval: 5000,
variables: {
asset_id: id
}
};
}
});
@@ -1,7 +1,7 @@
#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}) {
query LoadMoreModQueue($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) {
comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort, action_type: $action_type}) {
...commentView
action_summaries {
count
@@ -1,6 +1,20 @@
#import "../fragments/commentView.graphql"
query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
all: comments(query: {
statuses: [NONE, PREMOD, ACCEPTED, REJECTED],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
accepted: comments(query: {
statuses: [ACCEPTED],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
premod: comments(query: {
statuses: [PREMOD],
asset_id: $asset_id,
@@ -28,6 +42,13 @@ query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
title
url
}
allCount: commentCount(query: {
asset_id: $asset_id
})
acceptedCount: commentCount(query: {
statuses: [ACCEPTED],
asset_id: $asset_id
})
premodCount: commentCount(query: {
statuses: [PREMOD],
asset_id: $asset_id
@@ -41,4 +62,7 @@ query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
asset_id: $asset_id,
statuses: [NONE, PREMOD]
})
settings {
organizationName
}
}
@@ -0,0 +1,13 @@
query UserDetail ($author_id: ID!) {
user(id: $author_id) {
id
username
created_at
profiles {
id
provider
}
}
totalComments: commentCount(query: {author_id: $author_id})
rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]})
}
+1 -5
View File
@@ -4,7 +4,6 @@ import * as actions from '../constants/auth';
const initialState = Map({
loggedIn: false,
user: null,
isAdmin: false,
loginError: null,
loginMaxExceeded: false,
passwordRequestSuccess: null
@@ -24,12 +23,9 @@ export default function auth (state = initialState, action) {
return state
.set('loggedIn', true)
.set('loadingUser', false)
.set('isAdmin', action.isAdmin)
.set('user', action.user);
case actions.LOGOUT_SUCCESS:
case actions.LOGOUT:
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:
+4 -4
View File
@@ -53,17 +53,17 @@ export default function community (state = initialState, action) {
}
case SET_ROLE : {
const commenters = state.get('accounts');
const idx = commenters.findIndex(el => el.id === action.id);
const idx = commenters.findIndex((el) => el.id === action.id);
commenters[idx].roles[0] = action.role;
return state.set('accounts', commenters.map(id => id));
return state.set('accounts', commenters.map((id) => id));
}
case SET_COMMENTER_STATUS: {
const commenters = state.get('accounts');
const idx = commenters.findIndex(el => el.id === action.id);
const idx = commenters.findIndex((el) => el.id === action.id);
commenters[idx].status = action.status;
return state.set('accounts', commenters.map(id => id));
return state.set('accounts', commenters.map((id) => id));
}
case SORT_UPDATE :
+38 -5
View File
@@ -1,27 +1,53 @@
import {Map} from 'immutable';
import {fromJS, Map} from 'immutable';
import * as actions from '../constants/moderation';
const initialState = Map({
const initialState = fromJS({
singleView: false,
modalOpen: false,
user: Map({}),
user: {},
commentId: null,
banDialog: false
commentStatus: null,
userDetailId: null,
banDialog: false,
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
suspendUserDialog: {
show: false,
userId: null,
username: '',
commentId: null,
commentStatus: '',
},
});
export default function moderation (state = initialState, action) {
switch (action.type) {
case actions.HIDE_BANUSER_DIALOG:
return state
.set('banDialog', false);
.set('banDialog', false)
.set('commentStatus', null);
case actions.SHOW_BANUSER_DIALOG:
return state
.merge({
user: Map(action.user),
commentId: action.commentId,
commentStatus: action.commentStatus,
showRejectedNote: action.showRejectedNote,
banDialog: true
});
case actions.SHOW_SUSPEND_USER_DIALOG:
return state
.mergeDeep({
suspendUserDialog: {
show: true,
userId: action.userId,
username: action.username,
commentId: action.commentId,
commentStatus: action.commentStatus,
}
});
case actions.HIDE_SUSPEND_USER_DIALOG:
return state
.setIn(['suspendUserDialog', 'show'], false);
case actions.SET_ACTIVE_TAB:
return state
.set('activeTab', action.activeTab);
@@ -31,6 +57,13 @@ export default function moderation (state = initialState, action) {
case actions.SINGLE_VIEW:
return state
.set('singleView', !state.get('singleView'));
case actions.HIDE_SHORTCUTS_NOTE:
return state
.set('shortcutsNoteVisible', 'hide');
case actions.VIEW_USER_DETAIL:
return state.set('userDetailId', action.userId);
case actions.HIDE_USER_DETAIL:
return state.set('userDetailId', null);
default :
return state;
}
+2 -2
View File
@@ -1,5 +1,5 @@
import ApolloClient, {addTypename} from 'apollo-client';
import getNetworkInterface from './transport';
import {networkInterface} from 'coral-framework/services/transport';
import fragmentMatcher from './fragmentMatcher';
export const client = new ApolloClient({
@@ -12,5 +12,5 @@ export const client = new ApolloClient({
}
return null;
},
networkInterface: getNetworkInterface()
networkInterface
});

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