Compare commits

...
774 Commits
Author SHA1 Message Date
Kim Gardner 2564aa98bb Merge pull request #916 from coralproject/author-menu
Author menu, member since, and ignored users
2017-09-05 09:03:17 +01:00
Chi Vinh Le 612ed3a16d Merge branch 'author-menu' of ssh://github.com/coralproject/talk into author-menu 2017-09-05 02:24:55 +07:00
Chi Vinh Le a5acf8ba82 Add some global css classes 2017-09-05 02:22:10 +07:00
Kiwi 00b46fe0ea Merge branch 'master' into author-menu 2017-09-04 20:27:19 +02:00
Chi Vinh Le a722456359 reorder 2017-09-05 01:25:16 +07:00
Chi Vinh Le 943c9decba Add to default plugins 2017-09-05 01:18:14 +07:00
Chi Vinh Le 77396ccc31 Fix graphql tree bug 2017-09-05 01:17:05 +07:00
Chi Vinh Le e1a5a64b39 Pluginize stop ignoring user feature in profile 2017-09-05 01:09:43 +07:00
Chi Vinh Le 44e3d0b811 Add title 2017-09-05 00:28:07 +07:00
Chi Vinh Le 07f5f42870 Optimized render 2017-09-05 00:16:27 +07:00
Chi Vinh Le 123935c8bc Also notify on errors 2017-09-05 00:00:27 +07:00
Chi Vinh Le 2d4adb60be Show notification on success 2017-09-04 23:55:44 +07:00
Chi Vinh Le f8297e9652 Hide menu after action 2017-09-04 23:29:36 +07:00
Chi Vinh Le 74e8c28f45 Apply styling 2017-09-04 23:13:00 +07:00
Chi Vinh Le 89258638d4 Correct exclude if logic 2017-09-04 22:35:05 +07:00
Kim Gardner 902670cdac Merge pull request #914 from coralproject/ignored
Top-level comments by ignored user are tombstoned, but replies should be as well
2017-09-04 11:32:10 +01:00
Kim Gardner f11cd3c676 Merge branch 'master' into ignored 2017-09-04 11:19:48 +01:00
Kim Gardner f088b4d459 Merge pull request #915 from coralproject/fix-live-modqueue
Use correct moderation queues during updates
2017-09-04 11:18:29 +01:00
Chi Vinh Le c0252e160b Fix reply fragments 2017-09-04 16:44:53 +07:00
Chi Vinh Le 98cb34f554 Don't ignore myself 2017-09-04 16:25:19 +07:00
Chi Vinh Le b2ef7fa795 Fix tombstone bug 2017-09-04 15:51:04 +07:00
Chi Vinh Le ffef2f640d First implementation of ignore user plugin 2017-09-04 15:40:39 +07:00
Chi Vinh Le 26bae69454 Fix bug when determining moderation queues for live updates 2017-09-03 19:36:02 +07:00
Chi Vinh Le c07eaf0edb Add member since 2017-09-01 23:15:00 +07:00
Chi Vinh Le 9c2ea693b9 Add hover styles 2017-09-01 22:29:30 +07:00
Chi Vinh Le 7c339aa510 Render contentSlot if set 2017-09-01 22:25:26 +07:00
Belén Curcio 2ed6bb4067 Merge branch 'master' into ignored 2017-09-01 12:23:21 -03:00
Chi Vinh Le 692d92f645 Implement talk-plugin-author-menu 2017-09-01 22:20:49 +07:00
Belen Curcio 7bb4b03daa Retrieving me from Comment triggers a change 2017-09-01 12:17:48 -03:00
Kim Gardner e132f1d652 Merge pull request #913 from coralproject/redis-fix
Fixed Redis Connection Error
2017-09-01 15:53:10 +01:00
Wyatt Johnson 644788ea31 removed unused option 2017-09-01 08:47:08 -06:00
Wyatt Johnson e368c50687 ioredis changed config, fixes #901 2017-09-01 08:44:25 -06:00
Kim Gardner 4569c1bd4f Merge pull request #911 from coralproject/premodqueue
Create both a PreMod and a New Queue when stream is switched from one status to another
2017-09-01 10:30:13 +01:00
Kim Gardner d6c46cb6d1 Merge branch 'master' into premodqueue 2017-09-01 10:25:30 +01:00
Kim Gardner faeadeb61d Merge pull request #912 from coralproject/embed-fix
Fixed path joining for embed link
2017-09-01 10:01:34 +01:00
Wyatt Johnson d161733cdc fixed path joining 2017-08-31 13:59:00 -06:00
Wyatt Johnson 2fd6d370c8 Merge branch 'master' into premodqueue 2017-08-31 13:16:41 -06:00
Wyatt Johnson 824abcdfa3 Merge pull request #902 from coralproject/ioredis
Replace node_redis with ioredis
2017-08-31 11:57:38 -06:00
Belen Curcio 36c4c6a59f Ready 2017-08-31 14:22:59 -03:00
Belen Curcio 99235df566 No premod status to the New Queue 2017-08-31 13:17:34 -03:00
Belen Curcio af0e9bc880 New Queue should appear if there are comments with NONE status 2017-08-31 13:02:07 -03:00
Belen Curcio 23c8e2a0e6 Refetching settings :) 2017-08-31 12:53:47 -03:00
Wyatt Johnson c17fcfe5ef Merge branch 'master' into ioredis 2017-08-31 09:24:20 -06:00
Belen Curcio f81d5f8957 Grabbing settings from the store 2017-08-31 11:41:20 -03:00
Belen Curcio db55689f75 premod and new 2017-08-31 11:30:11 -03:00
Belen Curcio f140efe1bd Premod and New Queue Edge Case 2017-08-31 11:10:28 -03:00
Kim Gardner fe638a4c54 Merge pull request #910 from coralproject/edit-should-disappear
Make rendering `edit` pure
2017-08-31 13:13:25 +01:00
Chi Vinh Le b61afbc1d6 Make rendering edit pure 2017-08-31 19:04:54 +07:00
Kim Gardner 82e95dc4a1 Merge pull request #908 from coralproject/hide-empty-categories
Hide empty categories
2017-08-31 11:18:31 +01:00
Chi Vinh Le 3b7d006d1e Hide empty categories take 2 2017-08-31 16:59:04 +07:00
Chi Vinh Le b5daf5882b IfSlotIsEmpty and IfSlotIsNotEmpty now only renders a single child 2017-08-31 16:58:01 +07:00
Kim Gardner 11f5460604 Merge branch 'master' into hide-empty-categories 2017-08-31 10:45:52 +01:00
Kim Gardner 6e90dd6d62 Merge pull request #909 from coralproject/cleanup-plugins-default
Cleanup default plugins
2017-08-31 10:44:17 +01:00
Kiwi 635f6d7720 Merge branch 'master' into hide-empty-categories 2017-08-31 11:39:17 +02:00
Kiwi a4b40f2c46 Merge branch 'master' into cleanup-plugins-default 2017-08-31 11:39:13 +02:00
Kim Gardner 14541b48a1 Merge pull request #907 from coralproject/hide-on-click
Hide menu when clicking on a sort option
2017-08-31 10:38:56 +01:00
Chi Vinh Le c7c5167311 Cleanup default plugins 2017-08-31 16:36:08 +07:00
Chi Vinh Le e39c377e08 Hide empty categories 2017-08-31 16:33:51 +07:00
Chi Vinh Le 2887b20e38 Merge branch 'master' into hide-on-click 2017-08-31 16:32:46 +07:00
Chi Vinh Le 0a631cfcae Hide viewing options when changing sort 2017-08-31 16:12:28 +07:00
Kim Gardner 12c54c39db Merge branch 'master' into ioredis 2017-08-31 08:51:42 +01:00
Kim Gardner 5ad92972b1 Merge pull request #903 from coralproject/paginate-flagged-users
Paginate flagged users and more
2017-08-31 08:42:00 +01:00
Kim Gardner 201f6fd8ae Merge branch 'master' into paginate-flagged-users 2017-08-31 08:32:17 +01:00
Kim Gardner b0a6417848 Merge pull request #906 from gjtorikian/capitalize-the-h
Capitalize the H
2017-08-31 08:32:05 +01:00
Garen J. Torikian f80e4af683 Capitalize the H 2017-08-30 23:22:33 -04:00
Wyatt Johnson 7f9f864aeb Merge branch 'master' into paginate-flagged-users 2017-08-30 16:11:13 -06:00
Wyatt Johnson e9f5fcda2a Merge branch 'master' into ioredis 2017-08-30 16:06:36 -06:00
Wyatt Johnson b1c9d07ca7 Merge pull request #905 from coralproject/fix-query-event-names
Use correct event name in queries
2017-08-30 14:40:08 -06:00
Wyatt Johnson 53c32518b4 Merge branch 'master' into paginate-flagged-users 2017-08-30 14:14:45 -06:00
Kiwi 9d08dc2490 Merge branch 'master' into ioredis 2017-08-30 21:52:30 +02:00
Chi Vinh Le e5c385ae21 Use correct event name in queries 2017-08-31 02:05:00 +07:00
Belén Curcio 5e5e0028c9 Merge pull request #904 from coralproject/exclude-ignored-fc
Exclude Ignored users in Featured Comments Plugin
2017-08-30 16:01:36 -03:00
Belen Curcio cb7e7eae97 Using a variable 2017-08-30 15:45:25 -03:00
Belen Curcio e98432b17e Exclude Ignored 2017-08-30 15:35:52 -03:00
Chi Vinh Le 5434413308 Use fetchPolicy network-only 2017-08-30 23:47:48 +07:00
Chi Vinh Le 8b4f21ff4c Remove extra long transition timeout :-D 2017-08-30 23:27:48 +07:00
Chi Vinh Le 4072c39f05 Merge branch 'master' into paginate-flagged-users
Conflicts:
	client/coral-admin/src/routes/Community/components/FlaggedAccounts.js
	client/coral-admin/src/routes/Community/components/User.js
	client/coral-admin/src/routes/Community/containers/Community.js
	graph/loaders/users.js
2017-08-30 22:56:30 +07:00
Chi Vinh Le 9b9476d44f Add todos 2017-08-30 22:51:16 +07:00
Wyatt Johnson 2e72fec4d7 Merge branch 'master' into ioredis 2017-08-30 09:34:14 -06:00
Chi Vinh Le 1f489ff0e5 Always refetch when switching to FlaggedAccounts 2017-08-30 22:30:53 +07:00
Chi Vinh Le f582cc2534 Add transition effect 2017-08-30 22:25:03 +07:00
Wyatt Johnson a2536eb41b updated pubsub 2017-08-30 09:06:48 -06:00
Chi Vinh Le c66fb053e7 Implement mutation updators 2017-08-30 21:40:28 +07:00
Chi Vinh Le 797f0fd0e3 Docs in schema 2017-08-30 21:05:03 +07:00
Chi Vinh Le 02ec1f69ee More graphql fixes 2017-08-30 20:42:13 +07:00
Chi Vinh Le 37638cab86 Fix center styling 2017-08-30 20:41:49 +07:00
Chi Vinh Le 4ce6206516 Add deprecation and refactor notices to legacy styles 2017-08-30 19:56:24 +07:00
Chi Vinh Le 67b0624c90 Refactor styles 2017-08-30 19:48:54 +07:00
Chi Vinh Le 9348985191 Refactor into a ComponentClass 2017-08-30 19:04:30 +07:00
Chi Vinh Le 0df7df9dd6 Wrap ul before li 2017-08-30 19:03:55 +07:00
Chi Vinh Le ffab9ca8d5 Refactor users query 2017-08-30 18:48:20 +07:00
Kim Gardner b21eae78b0 Merge pull request #900 from coralproject/cli-fixes
Fixed errors when launching not-defined commands
2017-08-30 11:50:34 +01:00
Wyatt Johnson 8f3bfd2bd4 Replaced node_redis with ioredis 2017-08-29 17:52:29 -06:00
Wyatt Johnson 27661a9294 fixed errors when launching not-defined commands 2017-08-29 14:15:05 -06:00
Kim Gardner 2c3f5385ae Merge pull request #898 from coralproject/bump-version
Update version to 3.4.0
2017-08-29 19:31:42 +01:00
Wyatt Johnson 7bedfefecb Merge branch 'master' into bump-version 2017-08-29 12:10:56 -06:00
Wyatt Johnson 5ab78491b5 Merge pull request #890 from coralproject/lint-upgrade
Upgraded/pruned deps, applied linting fixes
2017-08-29 12:10:47 -06:00
Kim Gardner a83261397d Update version to 3.4.0 2017-08-29 19:06:46 +01:00
Wyatt Johnson c8d1871418 Merge branch 'master' into lint-upgrade 2017-08-29 11:56:59 -06:00
Kim Gardner 081cdfa145 Merge pull request #897 from coralproject/sort-enhancements
Sort enhancements
2017-08-29 18:50:34 +01:00
Kiwi fecfcf91ef Merge branch 'master' into sort-enhancements 2017-08-29 19:39:59 +02:00
Chi Vinh Le 90a0446afa Move Spinner down to replacing TabContent 2017-08-30 00:35:55 +07:00
Kim Gardner ec5c7f32b0 Merge pull request #895 from coralproject/my-comment-reactions
My comment reactions
2017-08-29 18:32:54 +01:00
Chi Vinh Le 21e4cf5865 Live update replies on all sort and always refetch 2017-08-30 00:27:14 +07:00
Kim Gardner 6894060648 Merge branch 'master' into my-comment-reactions 2017-08-29 18:24:54 +01:00
Kim Gardner 95d14fc5a6 Merge pull request #896 from coralproject/fix-popup-interval
Fix endless interval
2017-08-29 18:11:58 +01:00
Chi Vinh Le 46c031b35d Fix endless interval 2017-08-29 23:53:00 +07:00
Chi Vinh Le ddced5b8c9 Refactor styling 2017-08-29 23:43:58 +07:00
Wyatt Johnson c786805505 small bug fix 2017-08-29 10:28:30 -06:00
Wyatt Johnson 75125993b9 Added patch for missing action summary type 2017-08-29 10:21:31 -06:00
Kim Gardner 393005fce6 Merge pull request #889 from coralproject/my-comment-reactions
My Comment Reactions
2017-08-29 16:33:00 +01:00
Kim Gardner 952b253969 Merge branch 'master' into my-comment-reactions 2017-08-29 16:24:49 +01:00
Kim Gardner ad06d5136b Merge pull request #893 from coralproject/email-regex-fix
Changed email verification regex
2017-08-29 16:24:17 +01:00
Chi Vinh Le 18016ad23a Refactor and implement pagination 2017-08-29 22:23:35 +07:00
Kim Gardner d987724c7d Merge branch 'master' into email-regex-fix 2017-08-29 16:18:40 +01:00
Chi Vinh Le 87e90e956d Port current frontend to connections 2017-08-29 19:06:58 +07:00
Belen Curcio 9140688c65 Merge branch 'my-comment-reactions' of github.com:coralproject/talk into my-comment-reactions 2017-08-29 09:03:02 -03:00
Belen Curcio 1053c55d19 Adding zero state style for 0 counts 2017-08-29 09:01:40 -03:00
Chi Vinh Le eb67d86163 Implement UserConnection on the graph 2017-08-29 18:57:20 +07:00
Belen Curcio da92a8e7c2 Plural and singular translations 2017-08-29 08:47:23 -03:00
Kiwi d82932306d Merge branch 'master' into lint-upgrade 2017-08-29 12:47:47 +02:00
Kim Gardner a76464ea16 Merge branch 'master' into my-comment-reactions 2017-08-29 10:22:58 +01:00
Kim Gardner b76f1956dc Merge pull request #892 from coralproject/asset-tests
Added duplicated comments tests
2017-08-29 10:12:52 +01:00
Wyatt Johnson 7823b65c7d Changed email verification regex, fixes #891 2017-08-28 16:51:41 -06:00
Wyatt Johnson 38f98c7c43 Added duplicated comments tests 2017-08-28 16:41:58 -06:00
Wyatt Johnson 30e6f1d169 Merge branch 'master' into lint-upgrade 2017-08-28 16:19:10 -06:00
Wyatt Johnson df6f6a72ef Merge pull request #885 from coralproject/subscriber
Adding new Slot: commentAuthorTags
2017-08-28 16:18:32 -06:00
Wyatt Johnson c09381ff84 Merge branch 'master' into subscriber 2017-08-28 15:40:31 -06:00
Wyatt Johnson a172f5be51 Merge pull request #881 from coralproject/feature/heroku-deploy
Heroku Deploy
2017-08-28 15:39:52 -06:00
Wyatt Johnson 2fec9e3c0f Merge branch 'master' into feature/heroku-deploy 2017-08-28 15:33:24 -06:00
Wyatt Johnson eb586f217b Upgraded/pruned deps, applied linting fixes 2017-08-28 14:06:54 -06:00
Belen Curcio e77bdf0b90 linting 2017-08-28 16:31:46 -03:00
Belen Curcio 3e045291ee Translations added 2017-08-28 16:30:17 -03:00
Belen Curcio 93c1ecd12d My Comment Reactions 2017-08-28 16:25:26 -03:00
Kim Gardner a570626787 Merge pull request #888 from coralproject/debugging-optim
Increased debugging and optimized a query path
2017-08-28 18:55:07 +01:00
Wyatt Johnson e92f75d4ad Merge branch 'master' into debugging-optim 2017-08-28 11:50:02 -06:00
Wyatt Johnson 057d8cabd2 Increased debugging and optimized a query path 2017-08-28 11:47:51 -06:00
Kiwi 4f901d0d56 Merge branch 'master' into subscriber 2017-08-28 19:43:54 +02:00
Kim Gardner 9af0c5e01a Merge pull request #887 from coralproject/fix-action-type-query
Fix `action_type` query
2017-08-28 18:28:54 +01:00
Kiwi afd3b1b3fe Merge branch 'master' into fix-action-type-query 2017-08-28 19:21:19 +02:00
Chi Vinh Le 44c6798959 Fix action_type query 2017-08-29 00:20:36 +07:00
Belén Curcio e14d6007a7 Merge branch 'master' into subscriber 2017-08-28 13:12:19 -03:00
Wyatt Johnson 1aa4f83c89 Merge pull request #886 from coralproject/query-fix
Fixed query
2017-08-28 09:47:28 -06:00
Wyatt Johnson 8cfccb7b00 fixed query 2017-08-28 09:42:37 -06:00
Kim Gardner c05bee2316 Merge pull request #880 from coralproject/load-more-history
Only load my own comments in history
2017-08-28 16:37:11 +01:00
Kiwi f4365eb848 Merge branch 'master' into load-more-history 2017-08-28 17:27:04 +02:00
Belén Curcio 96006a0196 Merge branch 'master' into subscriber 2017-08-28 12:26:08 -03:00
Chi Vinh Le 9436188487 Merge branch 'master' into load-more-history
Conflicts:
	client/coral-settings/containers/ProfileContainer.js
2017-08-28 22:25:20 +07:00
Kim Gardner 733d8e1eae Merge pull request #882 from coralproject/fix-permalink-liveupdates
Fix live update false comments in permalink view
2017-08-28 16:23:57 +01:00
Belén Curcio 3108f3dac3 Merge branch 'master' into subscriber 2017-08-28 12:22:05 -03:00
Kim Gardner 1e2c617775 Merge branch 'master' into fix-permalink-liveupdates 2017-08-28 16:17:40 +01:00
Belen Curcio 12d66a49d0 Adding commentAuthorTags 2017-08-28 12:17:01 -03:00
Kim Gardner 22945d49df Merge pull request #884 from coralproject/rm-semicolon
Remove semicolon
2017-08-28 16:15:07 +01:00
Kiwi b8c6305939 Merge branch 'master' into rm-semicolon 2017-08-28 17:09:42 +02:00
Chi Vinh Le 7911e366e5 Remove semicolon 2017-08-28 22:04:21 +07:00
Kim Gardner 1ba5fd0028 Merge pull request #870 from coralproject/user-sort
Optimizations and User Sort
2017-08-28 15:30:31 +01:00
Chi Vinh Le 7856c9d947 Fix live update false comments in permalink view 2017-08-28 21:05:32 +07:00
Jeff Nelson 7c9e2297df don't hardcode branch 2017-08-28 09:54:20 -04:00
Chi Vinh Le 153c5acce1 Only load my own comments in history 2017-08-28 19:15:29 +07:00
Chi Vinh Le bb42cffc17 Add sort plugins for other reactions 2017-08-28 18:50:01 +07:00
Chi Vinh Le 0dffc15836 Hover styles 2017-08-28 18:22:30 +07:00
Chi Vinh Le e4490e20a7 Merge branch 'master' into user-sort
Conflicts:
	package.json
2017-08-28 18:16:18 +07:00
Chi Vinh Le f9bc5f79ec Don't sort and ignore duplicate comments (embed-stream) 2017-08-28 18:08:33 +07:00
Kim Gardner 5ec65c86ce Merge pull request #879 from coralproject/questionbox
Question Box Builder
2017-08-25 20:58:41 +01:00
Wyatt Johnson 8a5db167ff changed sort -> sortOrder 2017-08-25 09:59:37 -06:00
Chi Vinh Le 48595a1bbf Merge branch 'master' into user-sort
Conflicts:
	client/coral-admin/src/reducers/moderation.js
	client/coral-embed-stream/src/actions/stream.js
	client/coral-embed-stream/src/components/Stream.js
	client/coral-framework/components/Slot.js
	plugin-api/beta/client/utils/index.js
	test/server/routes/api/account/index.js
2017-08-25 21:55:00 +07:00
Belén Curcio ce63942902 Merge branch 'master' into questionbox 2017-08-25 11:52:11 -03:00
Kim Gardner 0ecf1cedc3 Merge pull request #874 from coralproject/refactor-global-dependencies
Dependency Management
2017-08-25 15:41:53 +01:00
Chi Vinh Le 7f899b0440 Attach to fragments tree 2017-08-25 21:40:57 +07:00
Chi Vinh Le 88b2798718 Merge branch 'user-sort' of ssh://github.com/coralproject/talk into user-sort 2017-08-25 21:30:24 +07:00
Chi Vinh Le e3227322a5 Some docs 2017-08-25 21:22:48 +07:00
Kim Gardner a767ab73da Update default plugins to include new sorts 2017-08-25 15:20:05 +01:00
Kim Gardner 3e65eac7a0 Merge branch 'master' into refactor-global-dependencies 2017-08-25 15:18:46 +01:00
Kim Gardner 1b6b0a4b87 Merge pull request #878 from coralproject/bump-version-3-3-0
Update version to 3.3.0
2017-08-25 15:18:36 +01:00
Chi Vinh Le 42951aaf0b Adding proptypes 2017-08-25 21:17:15 +07:00
Chi Vinh Le 6b96fcfad1 Only unsubscribe from newest comments 2017-08-25 21:05:30 +07:00
Jeff Nelson fe0c32898d Update README.md 2017-08-25 10:03:46 -04:00
Jeff Nelson 091d787e34 Update README.md 2017-08-25 10:02:16 -04:00
Jeff Nelson 7ee2794344 Merge branch 'master' into feature/heroku-deploy 2017-08-25 09:56:55 -04:00
Jeff Nelson cc66f1b459 update 2017-08-25 09:47:59 -04:00
Jeff Nelson 50caddee56 update 2017-08-25 09:47:40 -04:00
Jeff Nelson 28dafd3e07 update 2017-08-25 09:27:52 -04:00
Chi Vinh Le 38c290958c Merge branch 'user-sort' of ssh://github.com/coralproject/talk into user-sort 2017-08-25 20:22:09 +07:00
Jeff Nelson 2f3d7c5329 set default root url 2017-08-25 09:17:51 -04:00
Chi Vinh Le e51e8c543f Add more sort options and improve styling 2017-08-25 20:15:07 +07:00
Jeff Nelson 4b7cc9ffcc whoops 2017-08-25 08:55:26 -04:00
Kim Gardner 59ddcea592 Update version to 3.3.0 2017-08-25 13:55:19 +01:00
Jeff Nelson faecd80543 better handling for heroku dynamic port 2017-08-25 08:51:36 -04:00
Chi Vinh Le 9e2f6f1f63 Use factory 2017-08-25 19:40:55 +07:00
Jeff Nelson e73c113e0d use mailgun since postmark has been deprecated 2017-08-25 08:30:29 -04:00
Chi Vinh Le 6ae68a4f90 First working version 2017-08-25 19:21:57 +07:00
Chi Vinh Le f0931b4dc6 Fix asset.comment resolver 2017-08-25 19:21:14 +07:00
Kim Gardner 62687f307a Merge pull request #877 from coralproject/own-user-comments
Cleaned up user query logic
2017-08-25 12:37:16 +01:00
Kim Gardner a83ea98978 Merge branch 'master' into own-user-comments 2017-08-25 12:22:53 +01:00
Kim Gardner 7aadaa6f8b Merge pull request #873 from coralproject/banned-words
Banned Words / Suspect Words Fix
2017-08-25 12:22:28 +01:00
Belen Curcio 46f17f5381 Fully working :) 2017-08-24 19:10:30 -03:00
Belen Curcio de7ab8f7ac With fancy lazy load for the Markdown Editor :D 2017-08-24 19:05:57 -03:00
Belen Curcio 5302d6d935 Markdown Editor Added and Working 2017-08-24 18:21:52 -03:00
Belen Curcio 3d0d5df139 Markdown Added 2017-08-24 18:16:32 -03:00
Belen Curcio 575a3030f9 Smooth transition 2017-08-24 16:40:16 -03:00
Belen Curcio 73058c624f Linting 2017-08-24 16:32:51 -03:00
Belen Curcio 5cb71eaee8 Icon List for Question Box 2017-08-24 16:32:07 -03:00
Wyatt Johnson 60677e3d2a cleaned up user query logic 2017-08-24 13:07:44 -06:00
Belen Curcio 7991188f8d deleting old code from default.css and building QuestionBox component 2017-08-24 14:45:47 -03:00
Wyatt Johnson 13796fb268 added more test cases 2017-08-24 11:18:17 -06:00
Wyatt Johnson 308449a67c added more messaging for cli tool 2017-08-24 10:24:56 -06:00
Kim Gardner c733b7760d Merge branch 'master' into banned-words 2017-08-24 17:22:49 +01:00
Kiwi a56240675d Merge branch 'master' into refactor-global-dependencies 2017-08-24 18:17:19 +02:00
Kim Gardner 1381395dde Merge pull request #875 from coralproject/moderator-community
Moderator Community
2017-08-24 17:14:46 +01:00
Wyatt Johnson 9a1374070f small fixes 2017-08-24 09:47:27 -06:00
Kim Gardner 1e8ff9de7e Merge branch 'master' into moderator-community 2017-08-24 15:35:48 +01:00
Chi Vinh Le ddb555e07f Add new sorting variables 2017-08-24 21:30:16 +07:00
Chi Vinh Le d1a3a67b35 Fix featured comments 2017-08-24 20:12:08 +07:00
Chi Vinh Le df60ac4714 Merge branch 'master' into user-sort 2017-08-24 20:09:44 +07:00
Chi Vinh Le ca31dedca8 Make comments query optional 2017-08-24 20:07:45 +07:00
Kiwi cb7213ea58 Merge branch 'master' into refactor-global-dependencies 2017-08-24 13:49:46 +02:00
Kiwi 2d7d505913 Merge branch 'master' into banned-words 2017-08-24 13:49:31 +02:00
Chi Vinh Le a42d668329 Merge branch 'refactor-global-dependencies' of ssh://github.com/coralproject/talk into refactor-global-dependencies 2017-08-24 18:47:09 +07:00
Chi Vinh Le 645aaa09ae Add some docs 2017-08-24 18:40:38 +07:00
Kim Gardner 8b04da9cf4 Merge pull request #876 from coralproject/useful-errors
Added message to Extendable Errors
2017-08-24 12:24:42 +01:00
Wyatt Johnson acf792089d added more db optims 2017-08-23 15:42:30 -06:00
Wyatt Johnson 38469a7e69 Added message to Extendable Error's 2017-08-23 13:30:23 -06:00
Wyatt Johnson 763f753928 Merge branch 'master' into refactor-global-dependencies 2017-08-23 11:56:35 -06:00
Wyatt Johnson 1a27f0e00f Merge branch 'master' into moderator-community 2017-08-23 11:56:06 -06:00
Wyatt Johnson 6d8185c33e modified middleware to change AND -> OR, added mod to com pages 2017-08-23 11:52:02 -06:00
Belén Curcio 3f83a8f1a9 Merge pull request #868 from coralproject/slot-username
New Slot: commentAuthorName
2017-08-23 14:20:41 -03:00
Kiwi f5eac91e0e Merge branch 'master' into refactor-global-dependencies 2017-08-23 18:02:17 +02:00
Wyatt Johnson a68513227a adjusted tokenizer to use regex based tokenizer instead 2017-08-23 09:54:00 -06:00
Chi Vinh Le ce360a47f8 Add slot to fragments in featured comments 2017-08-23 22:40:48 +07:00
Chi Vinh Le 1bcf31d22d Merge branch 'slot-username' of ssh://github.com/coralproject/talk into slot-username 2017-08-23 20:15:44 +07:00
Chi Vinh Le ceba418e28 created_at is a Date 2017-08-23 20:14:55 +07:00
Belén Curcio 0961797f8f Merge branch 'master' into banned-words 2017-08-23 10:10:04 -03:00
Belen Curcio 0342b6c826 rolling back 2017-08-23 10:06:39 -03:00
Chi Vinh Le c2b0a60d8a Remove global dependency on router history 2017-08-23 20:02:36 +07:00
Chi Vinh Le bf89409f60 Remove global dependency on Storage 2017-08-23 19:53:06 +07:00
Kim Gardner 994923cae0 Merge branch 'master' into user-sort 2017-08-23 13:00:12 +01:00
Kim Gardner 2a839250ed Merge branch 'master' into slot-username 2017-08-23 13:00:06 +01:00
Kim Gardner dcf09adc1c Merge pull request #872 from coralproject/missing-fragment
Add missing fragment field to Comment
2017-08-23 12:59:38 +01:00
Kiwi 7ff4a2e75d Merge branch 'master' into slot-username 2017-08-23 13:09:46 +02:00
Kim Gardner 2d1bf32464 Merge branch 'master' into missing-fragment 2017-08-23 12:04:24 +01:00
Chi Vinh Le 0811c9302d Ignore access from react dev tools 2017-08-23 17:50:35 +07:00
Kim Gardner a5f7a7cd75 Merge pull request #871 from coralproject/graphiql-fix
Template bug fix
2017-08-23 11:41:29 +01:00
Chi Vinh Le 4dd20260a8 Unprotect User.created_at 2017-08-23 17:32:29 +07:00
Chi Vinh Le 5eafb672ef Add missing fragment field to Comment 2017-08-23 17:07:28 +07:00
Chi Vinh Le 61e601a2a5 Merge branch 'master' into refactor-global-dependencies 2017-08-23 04:05:56 +07:00
Chi Vinh Le a9e3115bd9 Expose getErrorMessages in plugin-api 2017-08-23 03:53:37 +07:00
Chi Vinh Le a23bad49c6 Remove global dependency on notification on admin 2017-08-23 03:48:20 +07:00
Chi Vinh Le 89037a2e05 Refactor notification 2017-08-23 03:22:56 +07:00
Chi Vinh Le f428bfa6c9 WIP 2017-08-23 03:16:19 +07:00
Belen Curcio b6951488c4 Tokenizer is messing with escaped regexes 2017-08-22 16:37:24 -03:00
Chi Vinh Le 1ee7c81675 Remove admin store impl 2017-08-23 02:24:26 +07:00
Chi Vinh Le a934722672 Cleanup 2017-08-23 02:23:18 +07:00
Wyatt Johnson 35f8b0dc5f Merge branch 'graphiql-fix' into user-sort 2017-08-22 13:22:35 -06:00
Chi Vinh Le a882b36501 Fix connection bug 2017-08-23 02:20:43 +07:00
Wyatt Johnson 23f4724f99 fixed template bug 2017-08-22 13:18:48 -06:00
Belen Curcio 9fa1f3d958 Adding auto escape 2017-08-22 16:15:41 -03:00
Wyatt Johnson f85480c0ae Merge branch 'master' into user-sort 2017-08-22 13:04:32 -06:00
Chi Vinh Le fccbcce7a8 Remove global dependency on registry and plugins 2017-08-23 01:41:58 +07:00
Wyatt Johnson ef0c0ed628 reduced image size by 65% 2017-08-22 12:05:25 -06:00
Wyatt Johnson ffc6a72017 added docs for Comments.Sort, some changes to interface 2017-08-22 11:47:52 -06:00
Wyatt Johnson b719168b18 Implemented sortBy 2017-08-22 10:11:47 -06:00
Kiwi 4eac739568 Merge branch 'master' into slot-username 2017-08-22 17:41:13 +02:00
Kim Gardner 8ecf243d2d Merge pull request #869 from coralproject/admin-reply
Reply Badge - Admin Side
2017-08-22 16:37:42 +01:00
Chi Vinh Le 54ea0b1a3f Remove global dependency on store, rest, pym and apollo client 2017-08-22 22:04:49 +07:00
Belen Curcio cb4d2998f6 Removing logic from ReplyBadge 2017-08-22 09:40:49 -03:00
Wyatt Johnson e0f9bb4165 optimized to use new query path 2017-08-21 16:08:30 -06:00
Wyatt Johnson eec0e18600 bug fixes, removed dead code 2017-08-21 16:05:35 -06:00
Wyatt Johnson 78d4740abe drastically improved queries involving actions + comments 2017-08-21 15:54:14 -06:00
Wyatt Johnson 01ed9880f4 comment count optim, prep for sortBy 2017-08-21 15:26:04 -06:00
Belen Curcio c402627280 Adding Reply Badge to User History 2017-08-21 17:27:32 -03:00
Belen Curcio 647b5ed745 Adding Reply Badge 2017-08-21 17:09:26 -03:00
Belen Curcio a540dfe8d4 Adding hasParent on Comment to the Graph 2017-08-21 16:21:39 -03:00
Belen Curcio 19dfe86118 Updates 2017-08-21 14:53:24 -03:00
Wyatt Johnson 15111c81a2 improved messaging 2017-08-21 11:32:16 -06:00
Belen Curcio cea6f7b8e8 Deleting Reference to FakeComment 2017-08-21 14:26:22 -03:00
Wyatt Johnson 7552e34a15 Merge branch 'master' into user-sort 2017-08-21 10:58:18 -06:00
Wyatt Johnson 7da7db1eb4 package upgrades 2017-08-21 10:57:57 -06:00
Wyatt Johnson b3e15de9f3 extended verify tool with reply count fixing 2017-08-21 10:40:18 -06:00
Belen Curcio 9bff90bd4d Adding Slot to Featured Comments 2017-08-21 13:18:46 -03:00
Belen Curcio 22f473a9ea Merge branch 'slot-username' of github.com:coralproject/talk into slot-username 2017-08-21 12:28:24 -03:00
Belen Curcio 663a46896e Merge branch 'master' of github.com:coralproject/talk into slot-username 2017-08-21 12:28:05 -03:00
Belén Curcio bfa195d7cb Merge branch 'master' into slot-username 2017-08-21 12:27:00 -03:00
Belen Curcio 9028c1b4eb Adding fragment Slot 2017-08-21 12:15:53 -03:00
Wyatt Johnson d43d359048 ensure that events are handled sync 2017-08-21 08:49:56 -06:00
Wyatt Johnson ec2c62a607 Merge branch 'master' into user-sort 2017-08-21 08:48:26 -06:00
Kim Gardner 8b044954d9 Merge pull request #865 from clintabrown/fix-routes-mount-path
Fix paths when TALK_ROOT_URL_MOUNT_PATH is set to true
2017-08-21 15:43:02 +01:00
Chi Vinh Le d47287fbd5 Remove unused import 2017-08-21 21:39:40 +07:00
Chi Vinh Le 74f778aef7 Merge branch 'master' into talk-provider
Conflicts:
	client/coral-embed-stream/src/containers/Embed.js
	plugin-api/beta/client/hocs/withReaction.js
2017-08-21 21:38:39 +07:00
Chi Vinh Le 9566f83eac Remove coral-framework reducers
Some of them were only related to the embed stream.
Auth code is too convoluted with the embed stream and can't be reused atm.
2017-08-21 21:31:54 +07:00
Wyatt Johnson 170ca044fe Merge branch 'master' into fix-routes-mount-path 2017-08-21 08:29:37 -06:00
Kim Gardner ccf978f87c Merge pull request #867 from coralproject/trust-defaults-change
Changed trust defaults
2017-08-21 15:29:18 +01:00
Chi Vinh Le 84e4091ea6 Implement TalkProvider 2017-08-21 21:23:59 +07:00
Wyatt Johnson 65e56d36a1 Merge branch 'master' into fix-routes-mount-path 2017-08-21 08:15:20 -06:00
Wyatt Johnson 1f813da68b changed trust defaults 2017-08-21 08:12:36 -06:00
Kim Gardner 7c7fe1b3cc Merge pull request #863 from coralproject/optimization
Front-End Optimizations
2017-08-21 14:44:49 +01:00
Belen Curcio 0ea2660c97 Adding new Slot: authorName - Deleting Old Code 2017-08-21 10:44:03 -03:00
Kim Gardner 262ec5b0f8 Merge branch 'master' into fix-routes-mount-path 2017-08-21 12:48:49 +01:00
Kim Gardner f7b06dc57c Merge branch 'master' into optimization 2017-08-21 12:21:55 +01:00
Kim Gardner c3884ed0ed Merge pull request #864 from coralproject/redis-changes
Redis Improvements
2017-08-21 12:21:09 +01:00
Kim Gardner f744598dfd Merge branch 'master' into redis-changes 2017-08-21 12:07:23 +01:00
Kim Gardner 64d760bffc Merge branch 'master' into fix-routes-mount-path 2017-08-21 12:02:57 +01:00
Kim Gardner c81a17ac25 Merge branch 'master' into optimization 2017-08-21 11:15:33 +01:00
Kim Gardner 7dfb2bded1 Merge pull request #866 from coralproject/quickfix-configure-state
Fix bug in configure
2017-08-21 11:15:14 +01:00
Chi Vinh Le 077bc5ad33 Fix typo 2017-08-21 17:09:15 +07:00
Chi Vinh Le 3b26e855bc Don't manipulate state directly... 2017-08-21 17:02:54 +07:00
Clint Brown 7a0e04ae56 Fix linting 2017-08-21 09:23:52 +10:00
Wyatt Johnson 7125250cd6 updated tests 2017-08-20 12:54:46 -06:00
Wyatt Johnson 5036a2665a fixes to tests and impl edit hooks 2017-08-20 12:25:22 -06:00
Clint Brown b738961e1c Fix paths when using TALK_ROOT_URL_MOUNT_PATH 2017-08-20 23:29:03 +10:00
Wyatt Johnson b2e871f4a6 added event for edit to manage reply count 2017-08-19 10:49:50 -06:00
Wyatt Johnson 77b7ddf337 updated dockerfile 2017-08-19 10:40:06 -06:00
Wyatt Johnson 3804a7afae removed more outdated test code 2017-08-19 10:34:52 -06:00
Wyatt Johnson d38dbc87b6 simplified test layout 2017-08-19 10:22:26 -06:00
Wyatt Johnson a7a80a452b removed old deps + outdated e2e 2017-08-19 09:59:46 -06:00
Wyatt Johnson 1b09825602 added more debugging to redis, improved retry 2017-08-18 11:50:51 -06:00
Chi Vinh Le f49591225f Use UserDetailComment container 2017-08-19 00:10:31 +07:00
Chi Vinh Le cf1c93f395 Add comments 2017-08-18 23:37:53 +07:00
Chi Vinh Le 4b9990e154 Merge branch 'master' into optimization
Conflicts:
	client/coral-embed-stream/src/components/Comment.js
	client/coral-embed-stream/src/components/Stream.js
2017-08-18 23:30:28 +07:00
Chi Vinh Le 58a5d5eea8 Refactor Moderation Comment a little bit 2017-08-18 23:20:05 +07:00
Chi Vinh Le 5028f4adf4 Better shouldComponentUpdate detection in withFragments 2017-08-18 23:19:10 +07:00
Chi Vinh Le 44ec469fa5 Disable fragment warnings 2017-08-18 22:47:30 +07:00
Kim Gardner 7c7a8d6b64 Merge pull request #862 from coralproject/flagging-reliability
Flagging Reliability
2017-08-18 16:40:49 +01:00
Chi Vinh Le e77a8ce3c7 More comments 2017-08-18 22:36:13 +07:00
Chi Vinh Le f2a67f04f9 Only use Proxy when available 2017-08-18 22:35:13 +07:00
Chi Vinh Le 0a602b79c3 Enable optimization for more slots 2017-08-18 22:21:51 +07:00
Chi Vinh Le b8452b535b Allow passing custom fragments to withReaction and withTag 2017-08-18 22:19:03 +07:00
Wyatt Johnson 6c19b90652 Merge branch 'master' into user-sort 2017-08-18 09:14:19 -06:00
Chi Vinh Le 0cbf6e24f7 Handle null values 2017-08-18 21:59:43 +07:00
Chi Vinh Le 833e78f99a Integrate ProfileContainer slot into graphql framework 2017-08-18 21:44:56 +07:00
Belen Curcio 64580696fb Reliability Added 2017-08-18 11:42:23 -03:00
Chi Vinh Le e5156f7fc1 Integrate embed slot in to graphql framework 2017-08-18 21:36:14 +07:00
Chi Vinh Le de90bdbd19 Gracefully handle when data is not available 2017-08-18 21:19:13 +07:00
Chi Vinh Le 00f365ab38 Add missing fragments 2017-08-18 20:40:51 +07:00
Chi Vinh Le 9f62754fd7 Show warnings when slot components uses query data without fragments 2017-08-18 20:33:37 +07:00
Belen Curcio 1dcdccecbc UserDetail UI redone and Flagging Reliability added 2017-08-18 10:19:10 -03:00
Chi Vinh Le dc49d23dc8 Define fragments for PermalinkButton 2017-08-18 19:55:51 +07:00
Chi Vinh Le 8eaac3eaa2 Tiny refactor.. 2017-08-18 19:50:56 +07:00
Kim Gardner 87d14f781f Merge pull request #861 from coralproject/fix-emit-showMoreReplies
Fix emit showMoreReplies event
2017-08-18 12:53:21 +01:00
Chi Vinh Le 5e13af8282 Fix emit showMoreReplies event 2017-08-18 18:42:41 +07:00
Chi Vinh Le 6a5f6e3ce1 Adapt loading detection 2017-08-18 17:57:32 +07:00
Chi Vinh Le fabc8b8c46 Check when one of the leaves are empty 2017-08-18 17:49:57 +07:00
Chi Vinh Le 0808f97fb5 Consider state when optimizing comment 2017-08-18 16:40:42 +07:00
Kim Gardner 11bd1a9771 Merge pull request #860 from coralproject/suspect-hl
Highlighting suspect words working
2017-08-18 09:35:16 +01:00
Wyatt Johnson 686eb614f7 changed cursor to Cursor scalar type, changed SORT_ORDER 2017-08-17 16:43:55 -06:00
Wyatt Johnson a9abd55895 added group_id support to action counts, cli verifier 2017-08-17 15:32:47 -06:00
Wyatt Johnson 165d07cae9 improved event layout, added db indicies 2017-08-17 11:56:58 -06:00
Chi Vinh Le af4b01c2ae Correctly handly activeReplyBox optimization 2017-08-17 23:23:18 +07:00
Chi Vinh Le 25c11ad690 Keep old query data when not changed in withFragments 2017-08-17 23:22:30 +07:00
Belen Curcio ef815005a8 Úpdated suspected words 2017-08-17 13:12:07 -03:00
Belen Curcio abb8b775b9 working banned and suspended words 2017-08-17 12:44:20 -03:00
Chi Vinh Le d228d49c7d Optimize rendering when activeReplyBox changes 2017-08-17 22:44:13 +07:00
Kim Gardner 6289f8021e Merge pull request #858 from coralproject/bump-version-3-2-0
Version 3.2.0
2017-08-17 15:32:30 +01:00
Chi Vinh Le 7ea6d133c2 Filter queryData in withFragments and optimize rendering 2017-08-17 21:31:09 +07:00
Kim Gardner ae71f5ddc3 Merge branch 'master' into bump-version-3-2-0 2017-08-17 15:26:50 +01:00
Kim Gardner fe673c390d Merge pull request #859 from coralproject/update-docs
Use latest version of Talk in examples
2017-08-17 15:26:42 +01:00
Kim Gardner 2022be4134 Use latest version of Talk in examples 2017-08-17 12:04:09 +01:00
Chi Vinh Le 1ff6c5bdcc Use comment container 2017-08-17 17:16:15 +07:00
Chi Vinh Le df141925fb add hoistStatics to withReaction 2017-08-17 17:15:06 +07:00
Kim Gardner 98e2cb3387 Version 3.2.0 2017-08-17 10:28:12 +01:00
Wyatt Johnson d4536cb1a2 replaced emit with emitAsync 2017-08-16 16:01:22 -06:00
Wyatt Johnson b126cc3cb3 initial impl of cached action counts using ee 2017-08-16 15:47:56 -06:00
Chi Vinh Le f8031be865 callback props from withMutation now keep their identity 2017-08-16 23:41:14 +07:00
Chi Vinh Le ae81d5cb3c Optimize IfSlotIsX rendering 2017-08-16 22:43:48 +07:00
Chi Vinh Le 4aa6b53022 PropTypes for StreamTabPanel 2017-08-16 22:32:44 +07:00
Chi Vinh Le 9ce6ab108a Refactor StreamTabPanel and use hoistStatics for all hocs 2017-08-16 22:15:31 +07:00
Kim Gardner 418e68aa8a Merge pull request #857 from coralproject/route-fixes
Fixed broken route
2017-08-16 15:01:43 +01:00
Wyatt Johnson 76e1d55601 fixed broken route 2017-08-16 07:53:58 -06:00
Kim Gardner 198b347ff0 Merge pull request #856 from coralproject/base-path
Base Path Support
2017-08-16 10:22:50 +01:00
Wyatt Johnson 34a19c532d Merge branch 'master' into base-path 2017-08-15 15:04:37 -06:00
Wyatt Johnson 4c1ebde3cc Merge pull request #848 from coralproject/update-readme
Add important links to README
2017-08-15 15:02:45 -06:00
Wyatt Johnson 83388b0791 Merge branch 'master' into update-readme 2017-08-15 14:54:29 -06:00
Wyatt Johnson 8afbe9d30d applied path fixes to template 2017-08-15 14:45:45 -06:00
Wyatt Johnson 6b60ceb52e Merge branch 'master' into base-path 2017-08-15 14:24:13 -06:00
Wyatt Johnson e71eb28e42 added base path support for router, spelling fixes 2017-08-15 14:19:58 -06:00
Chi Vinh Le 33aea66ff7 Refactor 2017-08-15 21:56:22 +07:00
Chi Vinh Le c3bcf7dd63 Merge branch 'master' into optimization 2017-08-15 21:48:09 +07:00
Chi Vinh Le 2b2e2f9b5f ClickOutside should only toggle action when open 2017-08-15 21:32:44 +07:00
Chi Vinh Le 4cfdb6574b withQuery props should be immutable 2017-08-15 21:23:39 +07:00
Chi Vinh Le b63fce4ebc Optimize Slot rendering 2017-08-15 18:53:06 +07:00
Kim Gardner b81c2e480e Merge pull request #853 from coralproject/rm-immutable-js
Remove immutable js
2017-08-15 11:54:42 +01:00
Kim Gardner d2525ac8cf Merge branch 'master' into rm-immutable-js 2017-08-15 11:49:15 +01:00
Kim Gardner d38128bad9 Merge pull request #850 from coralproject/update-apollo
Update apollo and fix warnings
2017-08-15 11:49:08 +01:00
Kim Gardner 1220ce464e Merge branch 'master' into update-readme 2017-08-15 11:27:53 +01:00
Kim Gardner 1ca40a3592 Merge branch 'master' into rm-immutable-js 2017-08-15 11:27:45 +01:00
Kim Gardner ad4ea08a6a Merge branch 'master' into update-apollo 2017-08-15 11:21:47 +01:00
Kim Gardner dc3adf0413 Merge pull request #851 from coralproject/fix-ignore-user
Fix Ignored User in permalink
2017-08-15 11:05:23 +01:00
Kim Gardner ac2699856f Merge branch 'master' into fix-ignore-user 2017-08-15 10:50:33 +01:00
Kim Gardner c0f66277b3 Merge pull request #854 from coralproject/fix-error-offtopic
Fix console error in offtopic
2017-08-15 10:50:20 +01:00
Chi Vinh Le db37a0b38e Check existance of featuredComments in query 2017-08-15 16:43:44 +07:00
Kim Gardner cadfb57cd2 Merge branch 'master' into fix-error-offtopic 2017-08-15 10:36:19 +01:00
Kim Gardner 2284692931 Merge branch 'master' into fix-ignore-user 2017-08-15 10:31:26 +01:00
Kim Gardner 2827ca5229 Merge branch 'master' into update-readme 2017-08-15 10:28:59 +01:00
Kim Gardner f159d8a488 Merge pull request #855 from coralproject/fix-create-username-dialog
Fix FakeComment styles
2017-08-15 10:28:46 +01:00
Kim Gardner 15f1733fa1 Merge branch 'master' into fix-create-username-dialog 2017-08-15 10:23:28 +01:00
Kim Gardner 776f29a39d Merge pull request #852 from coralproject/fix-dashboard-links
Fix dashboard links
2017-08-15 10:20:10 +01:00
Chi Vinh Le c52a97338c Fix FakeComment styles 2017-08-14 21:25:48 +07:00
Chi Vinh Le abda77a7e7 Fix console error in offtopic 2017-08-14 19:55:04 +07:00
Chi Vinh Le 06cf33521f Remove immutable-js traces from docs 2017-08-14 19:41:35 +07:00
Chi Vinh Le cdfef8c989 Remove immutable 2017-08-14 16:56:51 +07:00
Chi Vinh Le 2e8863285d Rm immutable-js from admin 2017-08-14 16:49:00 +07:00
Chi Vinh Le 7360a43898 Remove immutable from embed stream 2017-08-11 22:50:12 +07:00
Chi Vinh Le 45cf72256b Fix dashboard links 2017-08-11 21:11:59 +07:00
Chi Vinh Le 17bc396b06 Fix Ignored User in permalink 2017-08-11 20:55:23 +07:00
Chi Vinh Le ada5e6f747 Update apollo and fix warnings 2017-08-11 19:53:08 +07:00
Wyatt Johnson 20b182f455 Merge branch 'master' into update-readme 2017-08-11 09:58:06 +10:00
Kim Gardner b60c879599 Merge pull request #849 from coralproject/pluggable-modqueues
Pluggable modqueues
2017-08-10 17:32:36 +01:00
Kiwi 0f93d6ce8a Merge branch 'master' into pluggable-modqueues 2017-08-10 18:01:06 +02:00
Kim Gardner ed5810aceb Merge branch 'master' into update-readme 2017-08-10 16:50:46 +01:00
Kim Gardner b8ffe51812 Merge pull request #838 from coralproject/setup-fixes
Added whitelist question to cli-setup
2017-08-10 16:47:18 +01:00
Kim Gardner d008947b41 Add important links to README 2017-08-10 15:51:50 +01:00
Kim Gardner bf298b311a Merge branch 'master' into setup-fixes 2017-08-10 15:24:16 +01:00
Kim Gardner b1d9ca0640 Merge pull request #846 from coralproject/bump-version-3-1-0
Update version number to 3.1.0
2017-08-10 15:24:03 +01:00
Kim Gardner c0ac5b0799 Merge branch 'master' into bump-version-3-1-0 2017-08-10 14:56:16 +01:00
Kim Gardner 2039cc5b9e Merge pull request #845 from coralproject/cookie-updates
Cookie updates
2017-08-10 14:51:27 +01:00
Wyatt Johnson bb2284909f Merge branch 'master' into cookie-updates 2017-08-10 23:24:03 +10:00
Wyatt Johnson f018d7f597 Merge pull request #847 from coralproject/fix-tag-remove
Remove Tag should keep remaining tags
2017-08-10 23:23:40 +10:00
Chi Vinh Le 6794827a77 Support adding modqueues from plugins 2017-08-10 20:23:31 +07:00
Chi Vinh Le 6956b4cd31 Remove Tag should keep remaining tags 2017-08-10 18:39:02 +07:00
Chi Vinh Le 83ded4bda9 Refactor into queueConfig 2017-08-10 18:37:14 +07:00
Kim Gardner 1fea2f77c1 Merge branch 'master' into cookie-updates 2017-08-10 12:04:34 +01:00
Kim Gardner 80cfc663e3 Merge branch 'master' into bump-version-3-1-0 2017-08-10 11:52:05 +01:00
Kim Gardner a3cda1cf1c Merge pull request #844 from coralproject/pre-responsive-admin-cleanup
Pre responsive admin cleanup
2017-08-10 11:42:20 +01:00
Kim Gardner 0013c36686 Update version number to 3.1.0 2017-08-10 11:20:45 +01:00
Kim Gardner d947e103fc Merge branch 'master' into pre-responsive-admin-cleanup 2017-08-10 10:57:40 +01:00
Wyatt Johnson 6d70a7b20e improved code around cookie handling, added to docs 2017-08-10 10:46:11 +10:00
Wyatt Johnson b9243938bd Added support for changing the singing cookie name 2017-08-10 10:33:45 +10:00
IAmSamHankins ca422f0109 mod queue nav styling adjust 2017-08-09 10:23:03 -05:00
IAmSamHankins c62e7328c5 adjustments to top navigation and logo 2017-08-09 10:15:52 -05:00
Kim Gardner df286a31ad Merge branch 'master' into setup-fixes 2017-08-09 11:31:26 +01:00
Kim Gardner ca069317e9 Merge pull request #839 from coralproject/fix-bug-plugin-config
Pass config to plugins
2017-08-09 11:30:45 +01:00
Kim Gardner 7c7799d2c0 Merge branch 'master' into fix-bug-plugin-config 2017-08-09 11:10:24 +01:00
Kim Gardner 8d30129f47 Merge pull request #840 from coralproject/emit-events-view-more
Emit events when viewing new comments or replies
2017-08-09 11:09:35 +01:00
Kim Gardner a9ec160f95 Merge branch 'master' into emit-events-view-more 2017-08-09 10:52:59 +01:00
Kim Gardner 85836a3965 Merge branch 'master' into setup-fixes 2017-08-09 10:52:45 +01:00
Kim Gardner b10ed4e43e Merge pull request #842 from coralproject/paginate-my-comments
Paginate my comments in profile tab
2017-08-09 10:51:31 +01:00
Kim Gardner 1e00d75e3c Merge branch 'master' into paginate-my-comments 2017-08-09 10:26:18 +01:00
Kim Gardner 66b3d4ab00 Merge pull request #843 from coralproject/docs-fix
Docs Fixes
2017-08-09 10:25:54 +01:00
Kim Gardner 1ab662efda Merge branch 'master' into docs-fix 2017-08-09 10:15:40 +01:00
Wyatt Johnson d100128843 Merge pull request #836 from erikreyna/expose-comment-container-class
expose talk-stream-comment-container class
2017-08-09 13:23:36 +10:00
Wyatt Johnson 89e7f54a32 Merge branch 'master' into expose-comment-container-class 2017-08-09 11:24:18 +10:00
Wyatt Johnson e14d380d66 Merge pull request #834 from coralproject/update-my-comments
Update my comments after posting
2017-08-09 11:24:06 +10:00
Wyatt Johnson fd87ef3b85 Merge branch 'master' into update-my-comments 2017-08-09 09:58:44 +10:00
Wyatt Johnson b260945822 Merge pull request #833 from coralproject/view-more-history
Support load more in user history
2017-08-09 09:58:29 +10:00
Wyatt Johnson 1521ec43eb Merge branch 'master' into view-more-history 2017-08-09 09:39:53 +10:00
Wyatt Johnson 5ebdfa8c23 Merge pull request #837 from erikreyna/expose-avatar-container-class
expose talk-stream-comment-avatar class
2017-08-09 09:39:38 +10:00
Wyatt Johnson 6913fe05ae fixes #841 2017-08-09 09:38:09 +10:00
Wyatt Johnson 41b2af76c4 Merge branch 'master' into setup-fixes 2017-08-09 09:27:24 +10:00
Kiwi 9f1bce1221 Merge branch 'master' into update-my-comments 2017-08-08 17:55:22 +02:00
Kiwi 528c2a9491 Merge branch 'master' into view-more-history 2017-08-08 17:55:17 +02:00
Kiwi 948d362c40 Merge branch 'master' into expose-avatar-container-class 2017-08-08 17:54:52 +02:00
Kiwi 30842362cf Merge branch 'master' into expose-comment-container-class 2017-08-08 17:54:46 +02:00
Chi Vinh Le 976bd1e9ad Paginate my comments in profile tab 2017-08-08 22:39:24 +07:00
Chi Vinh Le 7bdd28cb9c Emit ui.AllCommentsPane.viewNewComments and ui.Comment.showMoreReplies 2017-08-08 20:48:52 +07:00
Wyatt Johnson e16dd14e47 fix bug with impl 2017-08-08 23:33:07 +10:00
Kiwi c9f2c27a69 Merge branch 'master' into fix-bug-plugin-config 2017-08-08 15:08:00 +02:00
Chi Vinh Le c4a91d225e Pass config also to withTags 2017-08-08 19:57:23 +07:00
Chi Vinh Le d1fc5668fb Pass config to plugins 2017-08-08 19:50:59 +07:00
Wyatt Johnson aad245fcbf Merge pull request #835 from coralproject/fix-bugs-action-summaries
Fix bugs action summaries
2017-08-08 15:45:44 +10:00
Wyatt Johnson 49ad48d70f Merge branch 'master' into fix-bugs-action-summaries 2017-08-08 15:40:21 +10:00
Wyatt Johnson 800fd63549 Merge pull request #832 from coralproject/more-css-classes
More css classes
2017-08-08 15:40:10 +10:00
Wyatt Johnson c08d040d1d Added whitelist question to cli-setup 2017-08-08 15:37:45 +10:00
Erik Reyna 1e8d2ce3a6 expose talk-stream-comment-avatar class 2017-08-07 14:11:51 -04:00
Erik Reyna a22d0f7536 expose talk-stream-comment-container class 2017-08-07 14:00:53 -04:00
IAmSamHankins 53cac48e3b resizing of the comment component 2017-08-07 11:53:39 -05:00
IAmSamHankins 27216cbfdb hierarchy for asset search title 2017-08-07 10:32:21 -05:00
IAmSamHankins ddadd6c5d3 adjustments/fixes to approved rejected widths 2017-08-07 10:26:42 -05:00
Chi Vinh Le 10936d9f41 Sort not by count 2017-08-07 18:23:44 +07:00
Chi Vinh Le 1d5f1e676f Sort aggregation 2017-08-07 18:12:25 +07:00
Chi Vinh Le c2f8f41e59 Support rendering system flags 2017-08-07 18:12:07 +07:00
Wyatt Johnson 567f8bf94e adjusted wrapResponse function 2017-08-07 14:54:11 +10:00
IAmSamHankins 11b2bc265a comment tag/marker updates for size 2017-08-04 13:50:14 -04:00
IAmSamHankins 76b125f042 adjustments to comment queue counters 2017-08-04 13:42:19 -04:00
Chi Vinh Le d2a2f94531 Merge branch 'view-more-history' of ssh://github.com/coralproject/talk into view-more-history 2017-08-04 23:30:10 +07:00
Chi Vinh Le 0df4fd1e37 Adjust styling 2017-08-04 23:24:40 +07:00
Chi Vinh Le 5ceda4f2d2 Update my comments after posting 2017-08-04 22:35:26 +07:00
Kiwi 0523a1f828 Merge branch 'master' into view-more-history 2017-08-04 17:07:57 +02:00
Chi Vinh Le 12d3273d3d Support load more in user history 2017-08-04 22:02:44 +07:00
Kim Gardner e6330cd8c3 Merge branch 'master' into more-css-classes 2017-08-04 07:04:43 -04:00
Kim Gardner bc67bc7b78 Merge pull request #831 from coralproject/secrets-fixes
Improvements and fixes to JWT configuration
2017-08-04 07:04:32 -04:00
Kim Gardner a91a2d3fa3 Merge branch 'master' into secrets-fixes 2017-08-04 06:55:06 -04:00
Kim Gardner 32a8d78eb8 Merge pull request #827 from coralproject/correctly-resolve-isClosed
Correctly determine the status of the asset
2017-08-04 06:54:20 -04:00
Chi Vinh Le 203707124c More css classes 2017-08-04 16:55:02 +07:00
Wyatt Johnson a39b7237e6 updates to docs 2017-08-04 16:10:08 +10:00
Wyatt Johnson 190e93c8d7 fixed comment 2017-08-04 15:57:44 +10:00
Wyatt Johnson 02a75c590f lint fix 2017-08-04 15:54:14 +10:00
Wyatt Johnson 8cdf289b13 exposed more controls on jwt and added docs 2017-08-04 15:49:37 +10:00
Wyatt Johnson e435c323ec Merge branch 'master' into correctly-resolve-isClosed 2017-08-04 14:01:49 +10:00
Wyatt Johnson f92754a700 updated docs related to secrets 2017-08-04 13:54:42 +10:00
Wyatt Johnson 5c8130bece Added guard for private key length 2017-08-04 12:58:40 +10:00
Wyatt Johnson 8ceeb4b275 Merge branch 'master' into secrets-fixes 2017-08-04 10:38:42 +10:00
Wyatt Johnson 3ec81099f7 Merge pull request #830 from coralproject/embed-upgrade
Added support for embed/stream unloading
2017-08-04 10:38:06 +10:00
Wyatt Johnson c9b82008de Fixes to secrets init 2017-08-04 10:37:47 +10:00
Chi Vinh Le 5d2b9439d8 Add some comments 2017-08-03 21:50:16 +07:00
Chi Vinh Le b8f24b952b Automatically close stream when passed closedAt date 2017-08-03 21:41:01 +07:00
Chi Vinh Le b6d284495e Merge branch 'master' into correctly-resolve-isClosed 2017-08-03 21:00:28 +07:00
Chi Vinh Le a527990413 Merge branch 'master' into correctly-resolve-isClosed 2017-08-03 20:26:32 +07:00
Wyatt Johnson 325626fecf removed all emitter listeners when we remove the stream 2017-08-03 11:33:07 +10:00
Wyatt Johnson 15c7a7cad7 Added support for embed unloading 2017-08-03 11:23:06 +10:00
Kim Gardner 8a52290793 Merge pull request #826 from coralproject/featured-adm
Feature Comments for Moderators / Admins
2017-08-02 13:13:13 -04:00
Chi Vinh Le e6b7fcbd9d Show success notification 2017-08-02 23:41:26 +07:00
Chi Vinh Le 29209f968b Merge branch 'featured-adm' of ssh://github.com/coralproject/talk into featured-adm 2017-08-02 22:29:53 +07:00
Chi Vinh Le 2e974ac82f Live update unfeatured 2017-08-02 22:29:01 +07:00
Kim Gardner 948fce9efb Merge branch 'master' into featured-adm 2017-08-02 10:59:08 -04:00
Kim Gardner 61690be021 Merge pull request #829 from coralproject/namespace-portugese-translation
Namespace portugese translations
2017-08-02 10:54:27 -04:00
Chi Vinh Le da0d0349dd Implement approve and live updates 2017-08-02 21:34:48 +07:00
Chi Vinh Le b6b7570bcc Remove subscription code 2017-08-02 18:14:52 +07:00
Chi Vinh Le eb02031354 Namespace portugese translations 2017-08-02 15:21:46 +07:00
Belen Curcio 94901d40f3 Merge branch 'featured-adm' of github.com:coralproject/talk into featured-adm 2017-08-01 18:27:38 -03:00
Belen Curcio 145910c4a6 Merge branch 'master' of github.com:coralproject/talk into featured-adm 2017-08-01 18:27:12 -03:00
Belen Curcio 4e5f1528f9 BE Subscription for Featured Comments 2017-08-01 18:27:02 -03:00
Kim Gardner 975a616ce7 Merge branch 'master' into featured-adm 2017-08-01 16:19:52 -04:00
Kim Gardner de7d5e542b Merge pull request #828 from coralproject/portuguese-translation
Portuguese translations
2017-08-01 16:18:10 -04:00
Belén Curcio ddf15df8a6 Merge branch 'master' into featured-adm 2017-08-01 15:10:31 -03:00
Kim Gardner 9429fe1afe Fix some tenses 2017-08-01 13:50:37 -04:00
Kim Gardner 8157a223d0 Merge branch 'master' into portuguese-translation 2017-08-01 13:38:45 -04:00
Kim Gardner 0039c05a78 Merge pull request #825 from coralproject/provide-valid-optimistic-status
Provide a valid comment status in optimistic response
2017-08-01 13:38:31 -04:00
Kim Gardner b03223b67b Merge branch 'master' into portuguese-translation 2017-08-01 13:37:48 -04:00
Kim Gardner c93c7b6ba1 Update text 2017-08-01 13:34:26 -04:00
Kim Gardner 4ad055197b Missing translation 2017-08-01 13:33:00 -04:00
Kim Gardner eac8c63045 Fix language files 2017-08-01 13:28:59 -04:00
Chi Vinh Le b972f4305e Correctly determine the status of the asset 2017-08-02 00:04:11 +07:00
Kim Gardner d300025c00 Update i18n config 2017-08-01 12:55:05 -04:00
Kim Gardner fdda5e5bef Add Portuguese translation of Talk core 2017-08-01 12:53:51 -04:00
Belen Curcio 779260c245 Working featured comments on the admin side 2017-08-01 13:06:21 -03:00
Kim Gardner faa6971961 Merge branch 'master' into provide-valid-optimistic-status 2017-08-01 11:59:34 -04:00
Chi Vinh Le 26c70bcee8 Provide a valid comment status in optimistic response 2017-08-01 22:44:53 +07:00
Belen Curcio 5266bfa647 Components, UI/UX and Styling 2017-08-01 12:38:41 -03:00
Kim Gardner 08a491b3a0 Merge pull request #824 from coralproject/fix-popup-safari-errors
Fix Safari issues for popups
2017-08-01 11:11:14 -04:00
Kim Gardner 1487a454e9 Merge branch 'master' into fix-popup-safari-errors 2017-08-01 11:05:17 -04:00
Kim Gardner c1a0273e84 Merge pull request #813 from coralproject/new-queue
New Queue
2017-08-01 11:04:17 -04:00
Belen Curcio d40242f92f Merge branch 'master' of github.com:coralproject/talk into featured-adm 2017-08-01 11:28:10 -03:00
Chi Vinh Le 5b751b7ba4 Fix Safari issues for popups 2017-08-01 20:32:49 +07:00
Kiwi 270d9b3e22 Merge branch 'master' into new-queue 2017-08-01 14:34:56 +02:00
Wyatt Johnson a4815ff927 Merge pull request #818 from coralproject/babel
Adds support for disabling the babel-polyfill on the parent embed.js
2017-08-01 22:32:20 +10:00
Kiwi fa451a8240 Merge branch 'master' into new-queue 2017-08-01 14:31:18 +02:00
Wyatt Johnson 8672dea500 Merge branch 'master' into babel 2017-08-01 22:26:40 +10:00
Wyatt Johnson 1a470957ab Merge pull request #823 from coralproject/cache-count-expiry
Added support for reconfiguring the comment count cache expiry
2017-08-01 22:26:28 +10:00
Wyatt Johnson dbeb31f0a7 removed more babel-runtime 2017-08-01 22:22:03 +10:00
Wyatt Johnson a99ffa1228 removed all traces of transform-runtime 2017-08-01 22:18:56 +10:00
Wyatt Johnson 2ac6a9d829 changes to babelrc 2017-08-01 22:12:02 +10:00
Chi Vinh Le 9929046536 Fix reported queue take 2 2017-08-01 18:06:06 +07:00
Chi Vinh Le cb874f8670 Change moderate link in user detail 2017-08-01 17:29:36 +07:00
Chi Vinh Le d2c8019408 Change moderate link 2017-08-01 17:28:51 +07:00
Chi Vinh Le 33a30e4f8d Remove unnessecary query fields 2017-08-01 17:24:48 +07:00
Chi Vinh Le 59ea17119d Remove unnessecary arrow function 2017-08-01 17:20:29 +07:00
Chi Vinh Le 2a78ce2bf1 Restore flagged queue 2017-08-01 17:18:52 +07:00
Chi Vinh Le b1b24d0f9d Change admin index route 2017-08-01 17:18:41 +07:00
Chi Vinh Le 3ae6dff098 Change href of moderate stream 2017-08-01 17:16:53 +07:00
Wyatt Johnson de97ad59ec Added configuration for the babel polyfill 2017-08-01 19:14:26 +10:00
Wyatt Johnson 83762b880f Added support for reconfiguring the comment count cache expiry 2017-08-01 09:52:18 +10:00
Kim Gardner 57ff4f10f3 Merge branch 'master' into babel 2017-07-31 19:22:51 -04:00
Kim Gardner bb5f245df8 Merge branch 'master' into new-queue 2017-07-31 19:21:57 -04:00
Kim Gardner b568f1f8a8 Merge pull request #820 from coralproject/comment-context-label
Label for inactive comments
2017-07-31 19:09:31 -04:00
Kim Gardner 4d9227840b Merge branch 'master' into babel 2017-07-31 18:44:11 -04:00
Belen Curcio 78e74336db Moderator Featured Tag/Button 2017-07-31 18:07:37 -03:00
Belen Curcio f3f441eb40 Merge branch 'master' of github.com:coralproject/talk into new-queue 2017-07-31 16:44:54 -03:00
Kim Gardner ef22909c6c Merge branch 'master' into comment-context-label 2017-07-31 15:29:28 -04:00
Kim Gardner a5790ca4df Merge pull request #819 from coralproject/comment-stream-tag-uniformity
create uniformity on tag label styling
2017-07-31 15:25:32 -04:00
Kim Gardner 109ef0cea4 Merge branch 'master' into comment-stream-tag-uniformity 2017-07-31 15:16:17 -04:00
Kim Gardner 1626721622 Merge pull request #821 from coralproject/dont-agree-action
"Don't agree" is not a flag action
2017-07-31 15:15:16 -04:00
Belen Curcio 694669bb85 Removing withRouter 2017-07-31 15:51:32 -03:00
Belen Curcio 4831d2d6a7 Merge branch 'new-queue' of github.com:coralproject/talk into new-queue 2017-07-31 15:47:42 -03:00
Belen Curcio d330bf9c25 Merge branch 'master' of github.com:coralproject/talk into new-queue 2017-07-31 15:47:12 -03:00
Belen Curcio 91d9d65372 Removing logic and adding active tabs 2017-07-31 15:46:06 -03:00
Chi Vinh Le 62a559b6fc "Don't agree" is not a flag action 2017-08-01 00:37:18 +07:00
Kim Gardner d606f4d094 Merge branch 'master' into new-queue 2017-07-31 12:55:53 -04:00
Belen Curcio 344d1840f0 lingin 2017-07-31 13:41:13 -03:00
Belen Curcio 4079a74ab8 Updated componentWillReceiveProps 2017-07-31 13:18:54 -03:00
Kiwi ba1dc3446b Merge branch 'master' into comment-context-label 2017-07-31 17:57:17 +02:00
Chi Vinh Le 3954b7a268 Label for inactive comments 2017-07-31 22:53:35 +07:00
Belen Curcio 31112b3a20 Passing to the container 2017-07-31 12:36:58 -03:00
Kim Gardner 337e6cc963 Merge branch 'master' into comment-stream-tag-uniformity 2017-07-31 11:01:00 -04:00
Kim Gardner 665bf5b102 Merge pull request #817 from coralproject/docs-update
Docs Updates
2017-07-31 11:00:39 -04:00
Samantha Hankins 70dcf2162f create uniformity on tag label styling 2017-07-31 10:52:26 -04:00
Belen Curcio 84391d9d69 Routes taking :id 2017-07-31 10:45:18 -03:00
Belen Curcio dbc8f87683 IndexRoute 2017-07-31 10:23:34 -03:00
Chi Vinh Le 97c75be604 Comments explaining why we have inactive comments in the stream 2017-07-31 20:17:06 +07:00
Belen Curcio 7f85b842f0 Adding fallback for separate assets 2017-07-31 10:09:21 -03:00
Chi Vinh Le fc3acecf61 Refactor isCommentActive logic 2017-07-31 20:09:16 +07:00
Belen Curcio 371348ced5 changes 2017-07-31 10:03:58 -03:00
Belen Curcio 33a75aa4d0 Moving flag to the bottom 2017-07-31 09:46:51 -03:00
Belen Curcio 1b017daf62 changes 2017-07-31 09:42:35 -03:00
Belen Curcio 4f34827ec8 accepted to approved 2017-07-31 09:38:39 -03:00
Belen Curcio c9df0e5e40 Changes 2017-07-31 09:36:00 -03:00
Belen Curcio ea17c068dd Remove Index and Redirect 2017-07-31 09:33:29 -03:00
Wyatt Johnson 08edddf0df adds support for babel-plugin-transform-runtime 2017-07-31 15:51:34 +10:00
Wyatt Johnson 12e48efa9f some docs updates 2017-07-31 14:11:40 +10:00
Wyatt Johnson 2217c89aa7 Merge pull request #815 from coralproject/embed-control-object
Embed Control
2017-07-31 11:27:20 +10:00
Wyatt Johnson b8eacf43a6 Merge branch 'master' into embed-control-object 2017-07-31 10:25:43 +10:00
Wyatt Johnson c1660030cd Merge pull request #816 from coralproject/router-fix
Added more router fixes
2017-07-31 10:25:30 +10:00
Wyatt Johnson 299bb35c9d added more path fixes 2017-07-31 09:52:46 +10:00
Belen Curcio 1b583fb945 Reported has priority over premod queue or new queue 2017-07-28 15:51:21 -03:00
Belen Curcio 9bfdfc86b9 Refactor menu too 2017-07-28 15:42:59 -03:00
Belen Curcio 3e6e991609 Merge branch 'new-queue' of github.com:coralproject/talk into new-queue 2017-07-28 15:32:39 -03:00
Belen Curcio 7ada3106d2 Default routing working 2017-07-28 15:23:40 -03:00
Kim Gardner 2af0f2bf65 Merge branch 'master' into new-queue 2017-07-28 14:03:05 -04:00
Kiwi b9583dd55d Merge branch 'master' into embed-control-object 2017-07-28 19:31:31 +02:00
Chi Vinh Le 85e478cf3f Return an object on Talk.render to control the embed 2017-07-29 00:17:23 +07:00
Kim Gardner 993688837a Merge pull request #814 from coralproject/optimize-permalink-queries
Optimize query during permalink view
2017-07-28 12:28:32 -04:00
Belén Curcio 8aa086af02 Merge branch 'master' into optimize-permalink-queries 2017-07-28 13:21:59 -03:00
Kim Gardner f5369802e6 Merge branch 'master' into new-queue 2017-07-28 11:39:12 -04:00
Kim Gardner 7f38c70e58 Merge pull request #812 from coralproject/view-context-non-public
Allow admins / mods to view context of rejected and premod comments
2017-07-28 11:04:30 -04:00
Kim Gardner 7dd20d740d Merge branch 'master' into view-context-non-public 2017-07-28 10:58:54 -04:00
Kim Gardner 50c8d3f314 Merge pull request #811 from coralproject/user-detail-everywhere
User detail everywhere
2017-07-28 10:56:59 -04:00
Belen Curcio 0f41165751 New Queue names 2017-07-28 11:16:49 -03:00
Belen Curcio 5ce9c950b8 Query search goes to the specific queue 2017-07-28 10:44:59 -03:00
Chi Vinh Le 91bc7eaa53 Make condition more clear 2017-07-28 20:10:03 +07:00
Belén Curcio 58a6333e02 Merge branch 'master' into user-detail-everywhere 2017-07-28 09:57:34 -03:00
Belen Curcio 4b0eeca08b Merge branch 'new-queue' of github.com:coralproject/talk into new-queue 2017-07-28 09:54:55 -03:00
Belen Curcio 0111e08bc1 lintin 2017-07-28 09:48:34 -03:00
Belén Curcio 0767c680fc Merge branch 'master' into new-queue 2017-07-28 09:44:29 -03:00
Chi Vinh Le 7323c92b26 Optimize query during permalink view 2017-07-28 19:43:33 +07:00
Belen Curcio c0ff056354 Old file 2017-07-28 09:39:12 -03:00
Chi Vinh Le 1f3d97fd6b Allow admins / mods to view context of rejected or premod comments 2017-07-28 18:03:35 +07:00
Wyatt Johnson 4ed68259c6 Merge pull request #807 from coralproject/basepath
Added support for hosting Talk under a different path
2017-07-28 08:44:29 +10:00
Kiwi d5b7fd0413 Merge branch 'master' into user-detail-everywhere 2017-07-27 22:12:52 +02:00
Chi Vinh Le 389ff0ca0d Change reducer property names 2017-07-28 03:03:19 +07:00
Chi Vinh Le 7a9aa331c5 viewUserDetail everywhere 2017-07-28 02:50:32 +07:00
Kim Gardner 673cb0ed04 Merge branch 'master' into basepath 2017-07-27 15:21:31 -04:00
Kim Gardner 3f8b2b947c Merge pull request #806 from coralproject/docs-update
Docs update
2017-07-27 15:09:15 -04:00
Belen Curcio dd8167a204 Check premod by asset 2017-07-27 14:42:36 -03:00
Belen Curcio fba4b23dd0 Reordering Menu 2017-07-27 14:20:15 -03:00
Belen Curcio 200d41f2a5 New Queue 2017-07-27 14:15:49 -03:00
Kim Gardner c9442c7412 Merge branch 'master' into docs-update 2017-07-27 13:02:50 -04:00
Kim Gardner 1994e44e94 Merge pull request #810 from coralproject/fix-viewing-options-plugin
Fix viewing options plugin
2017-07-27 13:02:25 -04:00
Belen Curcio 3c960db93d Merge branch 'master' of github.com:coralproject/talk into new-queue 2017-07-27 14:01:53 -03:00
Belen Curcio 47d37e31d8 prefixing translations 2017-07-27 13:50:14 -03:00
Belen Curcio 447c3aa7cc Wrong reducer name 2017-07-27 13:48:03 -03:00
Belen Curcio b3d1ff8003 Renaming flag by reported 2017-07-27 13:43:40 -03:00
Chi Vinh Le ce2f8f97e8 Move userDetail out of Moderation route 2017-07-27 23:23:40 +07:00
Kim Gardner cf8528dcf1 Remove file 2017-07-27 12:23:18 -04:00
Kim Gardner 9bd3e6b541 Fix translation file 2017-07-27 12:20:07 -04:00
Kim Gardner f8758bd755 Fix folders 2017-07-27 11:58:36 -04:00
Kim Gardner 46a2699ecb Merge branch 'master' into docs-update 2017-07-27 11:05:06 -04:00
Kim Gardner ce0479ae5f Merge pull request #779 from coralproject/renaming-talk
Renaming Talk Plugins
2017-07-27 11:04:31 -04:00
Chi Vinh Le 57fb77008a Make UserDetail work independently 2017-07-27 21:44:29 +07:00
Kim Gardner 433c4fcd03 Merge branch 'master' into renaming-talk 2017-07-27 10:06:17 -04:00
Kim Gardner 0f84586e1e Merge pull request #805 from coralproject/redis-fixes
Fixed bug with Kue
2017-07-27 10:05:55 -04:00
Wyatt Johnson f4b765ccc5 template updates 2017-07-27 16:50:49 +10:00
Wyatt Johnson 0d7e4d6541 Added support for hosting Talk under a different path 2017-07-27 16:37:26 +10:00
Wyatt Johnson 30b3e91fdb documented more env vars 2017-07-27 15:34:04 +10:00
Wyatt Johnson a54a240ae6 fixed bug with job init 2017-07-27 15:14:11 +10:00
Wyatt Johnson b482cf0a0f updated styles 2017-07-27 12:03:25 +10:00
Kim Gardner 3a1a5e4e37 Fix merge conflicts 2017-07-26 21:49:50 -04:00
Wyatt Johnson 1a1d28c915 Merge pull request #788 from coralproject/docs-update
Updated docs, theme, jekyll
2017-07-27 11:33:56 +10:00
Kim Gardner 1359dfdc0b Merge branch 'master' into renaming-talk 2017-07-26 21:29:19 -04:00
Kim Gardner e7bc3002ef Add translations file for viewing options plugin 2017-07-26 21:21:42 -04:00
Wyatt Johnson 7eee40368a Merge branch 'master' into docs-update 2017-07-27 11:21:40 +10:00
Wyatt Johnson 8d59678c8c Merge pull request #802 from coralproject/no-yarn-generate-introspection
Don't use `yarn generate-introspection`
2017-07-27 11:21:17 +10:00
Kim Gardner 7058e7e7e8 Merge branch 'master' into renaming-talk 2017-07-26 21:16:18 -04:00
Wyatt Johnson 0d7acbd44d Merge branch 'master' into no-yarn-generate-introspection 2017-07-27 11:12:22 +10:00
Wyatt Johnson 9c425c371d Merge pull request #801 from coralproject/jwt
Secret Rotation + Improvements
2017-07-27 11:10:19 +10:00
Wyatt Johnson 8cee74ae06 Merge branch 'master' into jwt 2017-07-27 11:05:26 +10:00
Wyatt Johnson ea16d50516 docs update for jwt 2017-07-27 11:04:47 +10:00
Kim Gardner 98a638cb4d Merge branch 'master' into no-yarn-generate-introspection 2017-07-26 21:02:28 -04:00
Wyatt Johnson 59a1435089 Updated config 2017-07-27 11:01:31 +10:00
Wyatt Johnson 4733233cae Added new cookie config params 2017-07-27 10:58:12 +10:00
Wyatt Johnson 9b37767674 refactored secret parsing 2017-07-27 10:49:33 +10:00
Kim Gardner 22b3a59351 Merge branch 'master' into renaming-talk 2017-07-26 15:17:19 -04:00
Kim Gardner fdb3a27616 Merge pull request #804 from coralproject/offtopic-style-change
changes to off-topic badge colors
2017-07-26 15:07:06 -04:00
Samantha Hankins 67f1ee1e39 changes to off-topic badge colors 2017-07-26 15:00:48 -04:00
Kim Gardner 13434556fd Merge pull request #798 from coralproject/live-reactions-no-comment
Live Reactions should not always expect the comment to be there.
2017-07-26 14:52:08 -04:00
Belén Curcio 2713254358 Merge branch 'master' into live-reactions-no-comment 2017-07-26 15:22:20 -03:00
Belén Curcio 605cefaeac Merge pull request #799 from coralproject/prefix-redux-actions-plugins
Prefix redux constants in plugins
2017-07-26 15:21:35 -03:00
Chi Vinh Le b00435bfe3 Click on flagged user shows correct drawer 2017-07-26 18:43:10 +07:00
Chi Vinh Le b1b4e9fc57 Don't use yarn generate-introspection 2017-07-26 16:34:07 +07:00
Kiwi cd3adb93dd Merge branch 'master' into prefix-redux-actions-plugins 2017-07-26 11:15:44 +02:00
Kiwi 0ea3422126 Merge branch 'master' into live-reactions-no-comment 2017-07-26 11:15:34 +02:00
Wyatt Johnson e848df0976 docs update for jwt + move code of conduct 2017-07-26 18:09:04 +10:00
Wyatt Johnson 6a12ab28db fixes for tests 2017-07-26 15:58:52 +10:00
Wyatt Johnson a91624bf4b Expanded JWT Capabilities 2017-07-26 15:24:16 +10:00
Wyatt Johnson 2dbcb0fe45 Updates to docs, fix bug with jwt 2017-07-26 11:23:09 +10:00
Wyatt Johnson 557bda6444 Merge branch 'master' into docs-update 2017-07-26 08:55:32 +10:00
Kim Gardner f22350c92e Merge pull request #794 from coralproject/fix-vc
Mobile and Touch Events for Featured Tags/ Viewing Options
2017-07-25 16:45:56 -04:00
Kim Gardner 772e68f72e Merge branch 'master' into fix-vc 2017-07-25 14:26:04 -04:00
Kim Gardner 49943ddac0 Merge pull request #785 from coralproject/event-emitter
Implement Events
2017-07-25 14:11:18 -04:00
Kim Gardner 11a735f82e Merge branch 'master' into event-emitter 2017-07-25 14:04:22 -04:00
Kim Gardner c7515ff126 Merge pull request #800 from coralproject/fix-docker-deploy
Exclude generateIntrospectionResult from dockerignore
2017-07-25 13:55:50 -04:00
Chi Vinh Le 21d19d170d Exclude generateIntrospectionResult from dockerignore 2017-07-26 00:49:16 +07:00
Belen Curcio 608ad26e88 deleting unused utils 2017-07-25 14:18:40 -03:00
Belen Curcio 17e6064b9e Adding react touch events 2017-07-25 14:15:52 -03:00
Chi Vinh Le 8ea8bb521a Merge branch 'event-emitter' of ssh://github.com/coralproject/talk into event-emitter 2017-07-26 00:06:20 +07:00
Belen Curcio 9aa5c1ec46 Viewing Options for mobile 2017-07-25 14:04:13 -03:00
Belen Curcio 432afd2d8a Merge branch 'fix-vc' of github.com:coralproject/talk into fix-vc 2017-07-25 14:03:53 -03:00
Belen Curcio 48dadd8571 Viewing Options for mobile 2017-07-25 14:00:30 -03:00
Chi Vinh Le 1473c778a5 Refactor listener 2017-07-25 23:26:23 +07:00
Chi Vinh Le b8c5e56404 Rename action names to match the new constant names :-D 2017-07-25 23:04:32 +07:00
Belén Curcio a7adf3469d Merge branch 'master' into event-emitter 2017-07-25 13:04:08 -03:00
Belen Curcio 5e9791af9c Merge branch 'master' of github.com:coralproject/talk into fix-vc 2017-07-25 12:57:20 -03:00
Chi Vinh Le 84a20042f2 Prefix redux constants in plugins 2017-07-25 22:53:26 +07:00
Chi Vinh Le 8ecf55610c More fetchMore events and more data for graphql events 2017-07-25 22:28:28 +07:00
Kim Gardner 589f5129c9 Merge branch 'master' into renaming-talk 2017-07-25 10:25:00 -04:00
Chi Vinh Le d4bf5fd423 Add subscription data events 2017-07-25 21:20:04 +07:00
Chi Vinh Le ceb83834a7 Remove subscribeToMore Event because it's incomplete 2017-07-25 21:19:42 +07:00
Kim Gardner fefbec032c Merge pull request #790 from coralproject/commentsid
Search Comment in an Asset
2017-07-25 10:19:33 -04:00
Kim Gardner 788a35d9bb Merge branch 'master' into commentsid 2017-07-25 10:12:23 -04:00
Kim Gardner fc1b69681e Merge pull request #772 from coralproject/next
Generate and integrate introspection query results
2017-07-25 10:12:12 -04:00
Kim Gardner 1077bfbec2 Merge branch 'master' into next 2017-07-25 10:07:13 -04:00
Kim Gardner 7ab171a813 Merge branch 'master' into renaming-talk 2017-07-25 10:07:03 -04:00
Kim Gardner 9b5bbe9971 Merge pull request #791 from coralproject/bump-version-3-0-0
Update version to 3.0.0
2017-07-25 10:06:41 -04:00
Kim Gardner 00597378d6 Merge branch 'master' into bump-version-3-0-0 2017-07-25 10:01:14 -04:00
Kim Gardner 3bb0370a5a Merge pull request #797 from coralproject/jwt-alg
JWT Changes
2017-07-25 10:01:00 -04:00
Chi Vinh Le 3c2229f3a7 Remove redundant networkStatus 2017-07-25 20:48:26 +07:00
Chi Vinh Le fe9d0eb8e2 Merge branch 'event-emitter' of ssh://github.com/coralproject/talk into event-emitter 2017-07-25 20:45:03 +07:00
Chi Vinh Le 45a2e2a8a3 Activate wildcard matching 2017-07-25 20:40:51 +07:00
Chi Vinh Le 039b620ecf Live Reactions should not always expect the comment to be there. 2017-07-25 19:26:44 +07:00
Chi Vinh Le d9049ab602 Merge branch 'commentsid' of ssh://github.com/coralproject/talk into commentsid 2017-07-25 19:16:01 +07:00
Chi Vinh Le ef759e068b Adapt query helpers 2017-07-25 19:14:03 +07:00
Chi Vinh Le f10d1a9f0d Adapt auto scroll when loading a permalinked comment 2017-07-25 19:11:11 +07:00
Kiwi 374b99ddc2 Merge branch 'master' into commentsid 2017-07-25 12:55:24 +02:00
Kiwi f60e580f14 Merge branch 'master' into next 2017-07-25 12:17:50 +02:00
Chi Vinh Le 0c05da9285 Add comment 2017-07-25 17:16:59 +07:00
Chi Vinh Le b4244b8671 More integration with the introspection query result
- Auto generate introspection on any change when using  `dev-start`
- Port client to use introspection matcher
2017-07-25 16:51:41 +07:00
Wyatt Johnson 4d6271d255 Added log about result 2017-07-25 17:31:28 +10:00
Wyatt Johnson 2e74e2fe62 Added introspection results to build pipeline 2017-07-25 17:29:51 +10:00
Wyatt Johnson 3f304823ca Added new config for jwt alg + added query parsing of access token 2017-07-25 15:55:40 +10:00
Wyatt Johnson b92516ec40 Merge branch 'master' into docs-update 2017-07-25 12:59:08 +10:00
Wyatt Johnson 0c8def722b Refined task singleton to factory 2017-07-25 12:53:52 +10:00
Belén Curcio 34e484abf3 Merge pull request #795 from coralproject/plugin-seed-fix
Fix for the plugin seed builder
2017-07-24 23:48:43 -03:00
Wyatt Johnson 07114aed1b removed unused var 2017-07-25 12:40:54 +10:00
Wyatt Johnson f13e6b0e16 Fixes to impl 2017-07-25 12:38:49 +10:00
Wyatt Johnson c2f6b9aa09 Suggestions for refactoring 2017-07-25 12:32:15 +10:00
Belen Curcio fac63099c7 Using path.resolve instead 2017-07-24 23:26:18 -03:00
Belen Curcio 3a7353e4fe fix for plugin seed builder 2017-07-24 23:15:30 -03:00
Belén Curcio 148a87033a Merge branch 'master' into fix-vc 2017-07-24 23:09:05 -03:00
Wyatt Johnson aeeafb6284 Merge branch 'master' into next 2017-07-25 11:52:53 +10:00
Wyatt Johnson c4d79dd215 Merge branch 'master' into event-emitter 2017-07-25 11:52:42 +10:00
Wyatt Johnson 79be92a8bd Merge branch 'master' into renaming-talk 2017-07-25 11:52:34 +10:00
Wyatt Johnson ed3b26b100 Merge branch 'master' into docs-update 2017-07-25 11:52:24 +10:00
Wyatt Johnson c3bb399782 Merge branch 'master' into commentsid 2017-07-25 11:52:17 +10:00
Wyatt Johnson 2af0a2309e Merge branch 'master' into bump-version-3-0-0 2017-07-25 11:52:12 +10:00
Andrew Losowsky ef5e70f179 Merge pull request #793 from coralproject/remove-cache
Removed yarn cache
2017-07-24 21:51:49 -04:00
Wyatt Johnson b8bd997fed removed yarn cache 2017-07-25 11:39:36 +10:00
Kim Gardner 6013b4b136 Merge branch 'master' into renaming-talk 2017-07-24 21:22:10 -04:00
Kim Gardner 712c7e2ad2 Merge branch 'master' into commentsid 2017-07-24 21:22:03 -04:00
Kim Gardner 6c43a8accd Merge branch 'master' into bump-version-3-0-0 2017-07-24 21:21:54 -04:00
Wyatt Johnson 4b5942dab8 Merge branch 'master' into docs-update 2017-07-25 11:21:27 +10:00
Wyatt Johnson 9e1d22b988 Merge pull request #792 from coralproject/version-bump
Bumped node version, replaced bcrypt -> bcryptjs
2017-07-25 11:20:56 +10:00
Wyatt Johnson 9ffe542234 added some forcing logic to circle 2017-07-25 10:51:29 +10:00
Wyatt Johnson e292475f1e Added yarn global add node-gyp back 2017-07-25 10:20:54 +10:00
Wyatt Johnson c8f3be4bf5 fixed docker version number 2017-07-25 10:03:50 +10:00
Wyatt Johnson 3e09d28308 Bumped node version, replaced bcrypt -> bcryptjs 2017-07-25 10:01:05 +10:00
Belen Curcio 85a10ba693 Touch events for mobile :) 2017-07-24 18:49:51 -03:00
Kim Gardner be46ac826b Update version to 3.0.0 2017-07-24 16:51:35 -04:00
Belen Curcio a036a051cb Using async await 2017-07-24 16:40:08 -03:00
Belen Curcio 36df7cb784 Extended Asset with comment 2017-07-24 16:31:24 -03:00
Belen Curcio 4b74c3312f typo 2017-07-24 15:36:00 -03:00
Chi Vinh Le c68cca0e5b Change some remaining coral-plugin-* strings 2017-07-24 22:45:20 +07:00
Wyatt Johnson 2ef219cc7c fixed link 2017-07-24 19:44:32 +10:00
Wyatt Johnson f35cce866a Updated docs, theme, jekyll 2017-07-24 19:41:51 +10:00
Kim Gardner 6383abd445 Merge branch 'master' into renaming-talk 2017-07-23 21:03:38 -04:00
Wyatt Johnson 7e917c9611 Merge branch 'master' into next 2017-07-23 18:35:53 -06:00
Kim Gardner 661354998f Merge branch 'master' into event-emitter 2017-07-22 18:54:35 -04:00
Chi Vinh Le 1db61941ed Comment WithEmit 2017-07-22 00:10:45 +07:00
Chi Vinh Le 7b1c0dfbaa Docs & stuff 2017-07-22 00:05:36 +07:00
Chi Vinh Le 6ea7e9d564 Use correct name 2017-07-21 23:58:41 +07:00
Chi Vinh Le 92d1fc5759 Comment out events listener 2017-07-21 23:55:15 +07:00
Chi Vinh Le 8b582e85f8 No redux prefix 2017-07-21 23:42:10 +07:00
Chi Vinh Le 970fe6fe04 Provide event emitter on admin side 2017-07-21 23:39:28 +07:00
Chi Vinh Le f5d4b7216e Merge branch 'master' into event-emitter 2017-07-21 22:57:51 +07:00
Chi Vinh Le cb95e9a308 Implement support for events 2017-07-21 22:54:45 +07:00
Kim Gardner 537311704d Merge branch 'master' into renaming-talk 2017-07-21 10:33:13 -04:00
Chi Vinh Le 0f82fa8bc5 Get store from context 2017-07-21 20:36:07 +07:00
Kim Gardner be63ee86b6 Merge branch 'master' into renaming-talk 2017-07-21 09:17:22 -04:00
Kim Gardner 10a82f277d Merge branch 'master' into next 2017-07-21 09:17:11 -04:00
Belen Curcio 1e9fb24f87 Renaming old reference plugin 2017-07-20 12:12:02 -03:00
Belen Curcio f17b4a318a Missing plugins 2017-07-20 12:04:00 -03:00
Belen Curcio 6ec33a0225 Plugins renaming 2017-07-20 11:52:36 -03:00
Belen Curcio eb3a9431f9 Renaming 2017-07-20 11:50:13 -03:00
Kim Gardner 8d7dc67544 Merge branch 'master' into next 2017-07-20 10:46:36 -04:00
Kim Gardner 0a4000328b Merge branch 'master' into next 2017-07-19 15:15:02 -04:00
Kim Gardner add1b67a61 Merge branch 'master' into next 2017-07-18 10:06:02 -04:00
Kim Gardner 9cbcb902c5 Merge branch 'master' into next 2017-07-18 10:00:27 -04:00
Wyatt Johnson 745c579b82 Adjust redis to not start during webpack build
- Added new WEBPACK env var which is enabled during yarn build scripts
- Defered redis starting until listen is called
- Moved pubsub to a factory pattern init
- Async/Await'ed the routes
- Moved pubsub handle for routes into middleware
- Adjusted redis cache and job processors to have lazy connection
starting
- Disabled mongo from auto-connecting on require
- Adjusted package redis clients to act as factory singletons instead
2017-07-17 13:34:04 -06:00
894 changed files with 35003 additions and 24268 deletions
+4 -2
View File
@@ -1,11 +1,12 @@
# excluded because we'll likely need to rebuild this.
node_modules
# scripts are used during development and testing, not
# most scripts are used during development and testing, not
# production.
scripts
!scripts/generateIntrospectionResult.js
# documentation should not be visable in production.
# documentation should not be visible in production.
docs
# static assets are rebuild in the docker container.
@@ -13,6 +14,7 @@ dist
# tests are not run in the docker container.
test
__tests__
# we won't use the .git folder in production.
.git
+18 -10
View File
@@ -1,18 +1,26 @@
dist
docs
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
!plugins/coral-plugin-viewing-options
!plugins/coral-plugin-comment-content
!plugins/talk-plugin-facebook-auth
!plugins/talk-plugin-auth
!plugins/talk-plugin-respect
!plugins/talk-plugin-offtopic
!plugins/talk-plugin-like
!plugins/talk-plugin-mod
!plugins/talk-plugin-love
!plugins/talk-plugin-viewing-options
!plugins/talk-plugin-comment-content
!plugins/talk-plugin-permalink
!plugins/talk-plugin-featured-comments
!plugins/talk-plugin-sort-newest
!plugins/talk-plugin-sort-oldest
!plugins/talk-plugin-sort-most-replied
!plugins/talk-plugin-sort-most-liked
!plugins/talk-plugin-sort-most-loved
!plugins/talk-plugin-sort-most-respected
!plugins/talk-plugin-author-menu
!plugins/talk-plugin-member-since
!plugins/talk-plugin-ignore-user
node_modules
+35 -26
View File
@@ -7,51 +7,53 @@
"parserOptions": {
"ecmaVersion": 2017
},
"plugins": [
"promise",
"json"
],
"rules": {
"indent": ["error",
2
],
"no-console": [
0
],
"no-console": "off",
"linebreak-style": ["error", "unix"],
"quotes": ["error", "single"],
"semi": ["error", "always"],
"no-template-curly-in-string": [1],
"no-unsafe-negation": [1],
"array-callback-return": [1],
"no-template-curly-in-string": "warn",
"no-unsafe-negation": "warn",
"array-callback-return": "warn",
"arrow-parens": ["warn", "always"],
"template-curly-spacing": "warn",
"eqeqeq": [2, "smart"],
"no-eval": [2],
"no-global-assign": [2],
"no-implied-eval": [2],
"eqeqeq": ["error", "smart"],
"no-eval": "error",
"no-global-assign": "error",
"no-implied-eval": "error",
"lines-around-comment": ["warn", {"beforeLineComment": true}],
"spaced-comment": ["warn", "always", { "line": { "exceptions": ["-", "="] } }],
"no-script-url": [2],
"no-throw-literal": [2],
"yoda": [1],
"no-path-concat": [2],
"eol-last": [1],
"no-nested-ternary": [1],
"no-tabs": [2],
"no-unneeded-ternary": [1],
"object-curly-spacing": [1],
"no-script-url": "error",
"no-throw-literal": "error",
"yoda": "warn",
"no-path-concat": "error",
"eol-last": "warn",
"no-nested-ternary": "warn",
"no-tabs": "error",
"no-unneeded-ternary": "warn",
"object-curly-spacing": "warn",
"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],
"no-const-assign": "error",
"no-duplicate-imports": "error",
"prefer-template": "warn",
"comma-spacing": ["error", {
"after": true
}],
"no-var": [2],
"no-lonely-if": [2],
"curly": [2],
"no-var": "error",
"no-lonely-if": "error",
"curly": "error",
"no-unused-vars": ["error", {
"argsIgnorePattern": "^_|next",
"varsIgnorePattern": "^_"
@@ -61,6 +63,13 @@
}],
"newline-per-chained-call": ["error", {
"ignoreChainWithDepth": 2
}]
}],
"promise/no-return-wrap": "error",
"promise/param-names": "error",
"promise/catch-or-return": "error",
"promise/no-native": "off",
"promise/no-nesting": "warn",
"promise/no-promise-in-callback": "warn",
"promise/no-callback-in-promise": "warn"
}
}
+21 -10
View File
@@ -5,27 +5,38 @@ dist
npm-debug.log*
dump.rdb
client/coral-framework/graphql/introspection.json
.env
*.cfg
.idea/
*.swp
*.DS_STORE
test/e2e/reports
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
!plugins/coral-plugin-viewing-options
!plugins/coral-plugin-comment-content
!plugins/talk-plugin-facebook-auth
!plugins/talk-plugin-auth
!plugins/talk-plugin-respect
!plugins/talk-plugin-offtopic
!plugins/talk-plugin-like
!plugins/talk-plugin-mod
!plugins/talk-plugin-love
!plugins/talk-plugin-viewing-options
!plugins/talk-plugin-comment-content
!plugins/talk-plugin-permalink
!plugins/talk-plugin-featured-comments
!plugins/talk-plugin-sort-newest
!plugins/talk-plugin-sort-oldest
!plugins/talk-plugin-sort-most-replied
!plugins/talk-plugin-sort-most-liked
!plugins/talk-plugin-sort-most-loved
!plugins/talk-plugin-sort-most-respected
!plugins/talk-plugin-author-menu
!plugins/talk-plugin-member-since
!plugins/talk-plugin-ignore-user
**/node_modules/*
+4 -5
View File
@@ -1,4 +1,4 @@
FROM node:7.10.1
FROM node:8-alpine
# Create app directory
RUN mkdir -p /usr/src/app
@@ -12,15 +12,14 @@ EXPOSE 5000
# Bundle app source
COPY . /usr/src/app
# Ensure the runtime of the container is in production mode.
ENV NODE_ENV production
# Install app dependencies and build static assets.
RUN yarn global add node-gyp && \
yarn install --frozen-lockfile && \
cli plugins reconcile && \
yarn build && \
yarn install --production && \
yarn cache clean
# Ensure the runtime of the container is in production mode.
ENV NODE_ENV production
CMD ["yarn", "start"]
+2 -5
View File
@@ -7,8 +7,5 @@ ONBUILD COPY . /usr/src/app
# we need to have webpack available. We then build the new dependancies and
# clear out the development dependancies again. After this we of course need to
# clear out the yarn cache, this saves quite a lot of size.
ONBUILD RUN NODE_ENV=development yarn install --frozen-lockfile && \
NODE_ENV=production cli plugins reconcile && \
NODE_ENV=production yarn build && \
NODE_ENV=production yarn install --production --force && \
yarn cache clean
ONBUILD RUN cli plugins reconcile && \
yarn build
-275
View File
@@ -1,275 +0,0 @@
## Contents
- [Installation](#installation) - install the application on a machine
- [Via Docker](#installation-from-docker)
- [Via Source](#installation-from-source)
- [Setup](#setup) - setup the application for first use
- [Usage](#usage) - connect the application to a website
# Installation
## Requirements
- Any flavour of Linux, OSX or Windows
- 1GB memory (minimum)
- 5GB storage (minimum)
- [MongoDB](https://www.mongodb.com/) v3.4 or later
- [Redis](https://redis.io/) v3.2 or later
- SSL Certificate
- This application assumes that you will be serving this application in a
production environment, and therefore requires that you serve it behind a
webserver with a valid SSL certificate. This is chosen in order to secure
user's sessions.
## Installation From Docker
We currently support packaging the Talk application via Docker, which automates
the dependency install and asset build process. This is the recommended way to
deploy the application when used in production.
Available as [coralproject/talk](https://hub.docker.com/r/coralproject/talk/) on Docker Hub.
Images are tagged using the following notation:
- `x` (where `x` is the major version number): any minor or patch updates will be included in this. If you're ok getting
new features occasionally and all the bug fixes, this is the tag for you.
- `x.y` (where `y` is the minor version number): any patch updates will be
included with this tag. If you like getting fixes and having features change
only when you want, this is the tag for you. **(recommended)**
- `x.y.z` (where `z` is the patch version): this tag never gets updated, and
essentially freezes your version, this should only be used when you are either
extending Talk or are sure of a specific version you want to freeze.
We provide tags with `*-onbuild` that can be used for easy plugin integration and
acts as a customization endpoint. Instructions are provided in the `PLUGINS.md`
document as to how to use it.
### Requirements
There are some runtime requirements for running Talk for Docker:
- [Docker](https://www.docker.com/) v1.13.0 or later
- [Docker Compose](https://docs.docker.com/compose/) v1.10.0 or later
_Please be sure to check the versions of these requirements. Incorrect versions
of these may lead to unexpected errors!_
### Installing
An example docker-compose.yml:
```yaml
version: '2'
services:
talk:
image: coralproject/talk:1.5
restart: always
ports:
- "5000:5000"
depends_on:
- mongo
- redis
environment:
- TALK_MONGO_URL=mongodb://mongo/talk
- TALK_REDIS_URL=redis://redis
mongo:
image: mongo:3.2
restart: always
volumes:
- mongo:/data/db
redis:
image: redis:3.2
restart: always
volumes:
- redis:/data
volumes:
mongo:
external: false
redis:
external: false
```
At this stage, you should refer to the `README.md` for configuration variables
that are specific to your installation. Some pre-defined fields have been filled
in the above example which are consistent with Docker Compose naming conventions
for [Docker Links](https://docs.docker.com/compose/networking/#links).
### Scaling
If you are interested in splitting apart services, you can simply adjust the
command being executed in the container to optimize for your use case. An
example would be if you wanted to run the API server and the job processor
on different machines. You can achieve this easily with docker compose:
```yaml
version: '2'
services:
talk-api:
image: coralproject/talk:1.5
command: cli serve
restart: always
ports:
- "5000:5000"
depends_on:
- mongo
- redis
environment:
- TALK_MONGO_URL=mongodb://mongo/talk
- TALK_REDIS_URL=redis://redis
talk-jobs:
image: coralproject/talk:1.5
command: cli jobs process
restart: always
ports:
- "5001:5000"
depends_on:
- mongo
- redis
environment:
- TALK_MONGO_URL=mongodb://mongo/talk
- TALK_REDIS_URL=redis://redis
mongo:
image: mongo:3.2
restart: always
volumes:
- mongo:/data/db
redis:
image: redis:3.2
restart: always
volumes:
- redis:/data
volumes:
mongo:
external: false
redis:
external: false
```
Note that the only difference is in the `command` key. From this, you are able
to discretely control which modules are running in order to have the maximum
flexibility when managing your application.
### Running
If you're using docker compose:
```bash
# Start the services using compose
docker-compose up -d
```
If you're using plain docker:
```bash
docker run -d -P coralproject/talk:latest
```
## Installation From Source
This provides information on how to setup the application from source. Note that
this is not recommended for production deploys, but will work for development
and testing purposes.
### Requirements
There are some runtime requirements for running Talk from source:
- [Node](https://nodejs.org/) ~7.8
- [Yarn](https://yarnpkg.com/) ^0.22.0
_Please be sure to check the versions of these requirements. Incorrect versions
of these may lead to unexpected errors!_
### Installing
#### Download
It is highly recommended that you download a released version as the code
available in `master` may not be stable. You can download the latest release
from the [releases page](https://github.com/coralproject/talk/releases).
You can also clone the git repository via:
```bash
git clone https://github.com/coralproject/talk.git
```
#### Building
We now have to install the dependencies and build the static assets.
```bash
# Install package dependancies
yarn
# Build static files
yarn build
```
After you create/modify the `plugins.json` (refer to `PLUGINS.md` for plugin
docs) file, you can re-run the following to install their dependencies:
```bash
# Reconcile plugins
./bin/cli plugins reconcile
# Build static files
yarn build
```
### Running
Refer to the `README.md` file for required configuration variables to add to the
environment.
You can start the server after configuring the server using the command:
```bash
yarn start
```
This will setup the server to serve everything on a single node.js process and
is designed to be used in production.
You can see other scripts we've made available by consulting the `package.json`
file under the `scripts` key including:
- `yarn test` run unit tests
- `yarn e2e` run end to end tests
- `yarn build-watch` watch for changes to client files and build static assets
- `yarn dev-start` watch for changes to server files and reload the server while
also sourcing a `.env` file in your local directory for configuration
# Setup
Once you've installed Talk (either via Docker or source), you still need to
setup the application. If you are unfamiliar with any terminology used in the
setup process, refer to the `TERMINOLOGY.md` document.
## Via Web
If you want to perform your setup via the web, you can navigate to your
installation of Talk at the path `/admin/install`. There you will be asked a
series of questions for your installation.
## Via CLI
If you want to perform your setup through the terminal, you can simply run:
```bash
cli setup
```
And follow the instructions to perform initial setup and create your first user
account.
# Usage
After setup is complete, you can then refer to the `/admin/configure` path to
get the embed code that you can copy/paste onto your blog or website in order to
start using Talk.
_In order for the embed to work correctly, you will need to whitelist the domain
that is allowed to embed your site on the `/admin/configure` page, failure to do
so will result in the comment stream not loading._
-3
View File
@@ -1,3 +0,0 @@
# Talk Plugins
Our documentation for Talk has moved! Utilize [this hyperlink](https://coralproject.github.io/talk/plugins.html) via click or tap to navigate your browser to the new location!
+9 -93
View File
@@ -1,110 +1,26 @@
# Talk [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk)
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://dashboard.heroku.com/new?template=https%3A%2F%2Fgithub.com%2Fcoralproject%2Ftalk&env[TALK_FACEBOOK_APP_ID]=ignore&env[TALK_FACEBOOK_APP_SECRET]=ignore)
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.
## Contributing to Talk
## Important Links
See our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md).
- Developer Documentation & Setup Guides: https://coralproject.github.io/talk/
## Documentation
- Pivotal Tracker Backlog & Release Schedule: https://www.pivotaltracker.com/n/projects/1863625
### General
## Learn More about Coral
See our [Talk Documentation & Guides](https://coralproject.github.io/talk/index.html).
- Community Forums: https://community.coralproject.net/
### Plugins
- Website: https://coralproject.net
See our guide to using and building [Talk Plugins](https://github.com/coralproject/talk/blob/master/PLUGINS.md).
- Blog: https://blog.coralproject.net
### Recipes
Recipes are plugin templates provided by the Coral Core team. Developers can use these recipes to build their own plugins. You can find all the Talk recipes here: https://github.com/coralproject/talk-recipes/
## Usage
### Installation
To set up a development environment or build from source, see [INSTALL.md](https://github.com/coralproject/talk/blob/master/INSTALL.md).
To launch a Talk server of your own from your browser without any need to muck about in a terminal or think about engineering concepts, stay tuned. We will launch [our installer](https://github.com/coralproject/talk-install) shortly!
### Configuration
The Talk application looks for the following configuration values either as environment variables:
- `TALK_MONGO_URL` (*required*) - the database connection string for the MongoDB database.
- `TALK_REDIS_URL` (*required*) - the database connection string for the Redis database.
- `TALK_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.
- `TALK_SMTP_PASSWORD` (*required for email*) - password for the SMTP provider you are using.
- `TALK_SMTP_HOST` (*required for email*) - SMTP host url with format `smtp.domain.com`.
- `TALK_SMTP_PORT` (*required for email*) - SMTP port.
- `TALK_INSTALL_LOCK` (_optional for dynamic setup_) - When `TRUE`, disables the dynamic setup endpoint. (Default `FALSE`)
- `TALK_RECAPTCHA_SECRET` (*required for reCAPTCHA support*) - server secret used for enabling reCAPTCHA powered logins. If not provided it will instead default to providing only a time based lockout.
- `TALK_RECAPTCHA_PUBLIC` (*required for reCAPTCHA support*) - client secret used for enabling reCAPTCHA powered logins. If not provided it will instead default to providing only a time based lockout.
- `TALK_PLUGINS_JSON` (_optional_) - used to specify the plugin config via the environment
- `TALK_KEEP_ALIVE` (_optional_) - The keepalive timeout that should be used to send keep alive messages through the websocket to keep the socket alive. (Default `30s`)
- `TALK_DISABLE_AUTOFLAG_SUSPECT_WORDS` (_optional_) When `TRUE`, disables flagging of comments that match the suspect word filter. (Default `FALSE`)
Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for
alternative methods of loading configuration during development.
### Running Migrations
We have a migration tool that can be run using `bin/cli migration run`. This will detect new migrations available and prompt you to backup your database before proceeding with the migration. Migrations are required with major version releases.
### Using Trust
Talk ships with core components we call "Trust". This allows Talk to automate certain actions based on previous user behavior.
Our first feature is the notion of Karma. Talk will automatically pre-moderate comments of users who have a negative karma score. You can [see more how karma works here](/services/karma.js).
## Supported Browsers
### 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
- Community Guides for Journalism: https://guides.coralproject.net/
## License
-73
View File
@@ -1,73 +0,0 @@
# Product's Terminology
This is a guide to have a common language to talk about "Talk".
## Definitions
* Site - a top level site, aka nytimes.com
* Section - the section of a site, aka, Politics.
* Subsection - the section of a site, aka, Politics.
* Asset - An article/video/etc identified by URL.
* Embed - Things we put on a asset: comment box, ToS, Stream, etc…
* Stream - All the activity on a certain asset. Container for Comments, actions, user
* Thread - defined by a parent and everything below. All replies to a comment and their replies, etc…
* Comment - a kind of user-generated content submitted by a comment author
* A parent comment has replies to it
* A child comments is a reply to another comment
* A comment can be both a parent comment and a child of another comment
* A top-level comment is a comment that is not a reply to any other comment
* A nth-level comment refers to the number of replies away from the top-level comment
* User - an item to represent a person using Talk. It could be a moderator, reader, etc.
* User Roles:
* Active: some who takes action (logged in or not)
* Passive: some who just reads, no actions performed
* Comment Author: The user who wrote the comment
* Staff Member: someone who works for an organization (tagged for leverage in trust)
* Moderator: someone with the ability to access the moderation queue and perform moderation actions
* Administrator: has the ability to change the setup of their coral space
* Public Profile: information about users shown in public
* Private Profile: information about users shown only to user about themselves
* Protected Profile: information about users that only moderators and admins can see
* Queue - Group of items based on a query, aka - moderation queue
* Target - The item/s on which an action is performed
## Actions
Actions are performed by users on items. Actions themselves are items. This requires two relationships: action on item, and user performs action.
### Flag
* A Flagger(user) performs a Flag
* A Flag is performed on a Comment or a username or profile content
## Moderation Actions and Status
Comments contain a field `status`. As moderation actions are peformed, the status changes.
* Initial status is empty.
* When a moderator Approves, the status is set to 'approved'.
* When a moderator Rejects, the status is set to 'reject'.
### Pre and post moderation
Comments can be set to be premoderated or postmoderated.
Premoderation means that moderation has to occur _before_ a comment is shown on the site:
* New comments are shown in the moderator queues immediately.
* The are not shown to users until (aka in streams) until they are approved by a moderator.
Postmoderation means that comments appear on the site _before_ any moderation action is taken.
* New comments appear in comment streams immediately.
* New comments do not appear in moderation queues unless they are flagged by other users.
### Word lists
* Banned words - words that the site never allows in a comment
* Suspect words - words whose usage needs to be approved by a moderator before being shown in the stream
* Approved words - words that are usually Banned or Suspect sitewide, but approved for use in a specific article stream
+26 -139
View File
@@ -3,69 +3,42 @@ 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 i18n = require('./services/i18n');
const enabled = require('debug').enabled;
const errors = require('./errors');
const {createGraphOptions} = require('./graph');
const apollo = require('graphql-server-express');
const accepts = require('accepts');
const compression = require('compression');
const cookieParser = require('cookie-parser');
const {BASE_URL, BASE_PATH, MOUNT_PATH} = require('./url');
const routes = require('./routes');
const debug = require('debug')('talk:app');
const app = express();
// Middleware declarations.
//==============================================================================
// APPLICATION WIDE MIDDLEWARE
//==============================================================================
// Add the logging middleware only if we aren't testing.
if (app.get('env') !== 'test') {
if (process.env.NODE_ENV !== 'test') {
app.use(morgan('dev'));
}
//==============================================================================
// APP MIDDLEWARE
//==============================================================================
// Trust the first proxy in front of us, this will enable us to trust the fact
// that SSL was terminated correctly.
app.set('trust proxy', 1);
// We disable frameward on helmet to allow crossdomain injection of the embed
// Enable a suite of security good practices through helmet. We disable
// frameguard to allow crossdomain injection of the embed.
app.use(helmet({
frameguard: false
frameguard: false,
}));
// Compress the responses if appropriate.
app.use(compression());
// Parse the cookies on the request.
app.use(cookieParser());
// Parse the body json if it's there.
app.use(bodyParser.json());
//==============================================================================
// STATIC FILES
//==============================================================================
// 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') {
// 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');
// Rewrite the url so that the gzip version will be served instead.
req.url = `${req.url}.gz`;
}
next();
});
}
app.use('/client', express.static(path.join(__dirname, 'dist')));
app.use('/public', express.static(path.join(__dirname, 'public')));
//==============================================================================
// VIEW CONFIGURATION
//==============================================================================
@@ -73,105 +46,19 @@ app.use('/public', express.static(path.join(__dirname, 'public')));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
//==============================================================================
// PASSPORT MIDDLEWARE
//==============================================================================
const passportDebug = require('debug')('talk:passport');
// Install the passport plugins.
plugins.get('server', 'passport').forEach((plugin) => {
passportDebug(`added plugin '${plugin.plugin.name}'`);
// Pass the passport.js instance to the plugin to allow it to inject it's
// functionality.
plugin.passport(passport);
});
// Setup the PassportJS Middleware.
app.use(passport.initialize());
// 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(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', (req, res) => {
res.render('graphiql', {
endpointURL: '/api/v1/graph/ql'
});
});
// GraphQL documention.
app.get('/admin/docs', (req, res) => {
res.render('admin/docs');
});
}
//==============================================================================
// ROUTES
//==============================================================================
app.use('/', require('./routes'));
// Apply the BASE_PATH, BASE_URL, and MOUNT_PATH on the app.locals, which will
// make them available on the templates and the routers.
app.locals.BASE_URL = BASE_URL;
app.locals.BASE_PATH = BASE_PATH;
app.locals.MOUNT_PATH = MOUNT_PATH;
//==============================================================================
// ERROR HANDLING
//==============================================================================
debug(`mounting routes on the ${MOUNT_PATH} path`);
// Catch 404 and forward to error handler.
app.use((req, res, next) => {
next(errors.ErrNotFound);
});
// General error handler. Respond with the message and error if we have it while
// returning a status code that makes sense.
app.use('/api', (err, req, res, next) => {
if (err !== errors.ErrNotFound) {
if (app.get('env') !== 'test' || enabled('talk:errors')) {
console.error(err);
}
}
if (err instanceof errors.APIError) {
res.status(err.status).json({
message: err.message,
error: err
});
} else {
res.status(500).json({});
}
});
app.use('/', (err, req, res, next) => {
if (err !== errors.ErrNotFound) {
console.error(err);
}
i18n.init(req);
if (err instanceof errors.APIError) {
res.status(err.status);
res.render('error', {
message: err.message,
error: app.get('env') === 'development' ? err : {}
});
} else {
res.render('error', {
message: err.message,
error: app.get('env') === 'development' ? err : {}
});
}
});
// Actually apply the routes.
app.use(MOUNT_PATH, routes);
module.exports = app;
+9 -5
View File
@@ -1,10 +1,15 @@
{
"name": "The Coral Project: Talk",
"env": {
"TALK_SESSION_SECRET": {
"TALK_JWT_SECRET": {
"description": "The session secret",
"generator": "secret"
},
"TALK_ROOT_URL": {
"description": "Please copy the App Name you choose above. If you did not choose one, please do so now and copy it here. Talk on Heroku will not work without this setting.",
"value":"https://<COPY APP NAME HERE>.herokuapp.com",
"required": true
},
"TALK_FACEBOOK_APP_ID": {
"value": "",
"required": true
@@ -14,8 +19,7 @@
"required": true
},
"NODE_ENV": "production",
"TALK_SMTP_PORT": "2525",
"REWRITE_ENV": "TALK_PORT:PORT,TALK_MONGO_URL:MONGO_URI,TALK_REDIS_URL:REDIS_URL,TALK_SMTP_HOST:POSTMARK_SMTP_SERVER,TALK_SMTP_USERNAME:POSTMARK_API_TOKEN,TALK_SMTP_PASSWORD:POSTMARK_API_TOKEN",
"REWRITE_ENV": "TALK_MONGO_URL:MONGO_URI,TALK_REDIS_URL:REDIS_URL,TALK_SMTP_HOST:MAILGUN_SMTP_SERVER,TALK_SMTP_PORT:MAILGUN_SMTP_PORT,TALK_SMTP_USERNAME:MAILGUN_SMTP_LOGIN,TALK_SMTP_PASSWORD:MAILGUN_SMTP_PASSWORD",
"NPM_CONFIG_PRODUCTION": "false"
},
"addons": [{
@@ -25,8 +29,8 @@
"plan": "rediscloud:30",
"as": "REDIS"
}, {
"plan": "postmark:10k",
"as": "POSTMARK"
"plan": "mailgun:starter",
"as": "MAILGUN"
}],
"image": "heroku/nodejs",
"success_url": "/admin/install"
+20 -1
View File
@@ -4,8 +4,9 @@
* Module dependencies.
*/
// const util = require('./util');
const program = require('./commander');
const {head, map} = require('lodash');
const Matcher = require('did-you-mean');
program
.command('serve', 'serve the application')
@@ -16,12 +17,28 @@ program
.command('token', 'work with the access tokens')
.command('users', 'work with the application auth')
.command('migration', 'provides utilities for migrating the database')
.command('verify', 'provides utilities for performing data verification')
.command(
'plugins',
'provides utilities for interacting with the plugin system'
)
.parse(process.argv);
// If the command wasn't found, output help.
const cmds = map(program.commands, '_name');
const cmd = head(program.args);
if (!cmds.includes(cmd)) {
const m = new Matcher(cmds);
const similarCMDs = m.list(cmd);
console.error(`cli '${cmd}' is not a talk cli command. See 'cli --help'.`);
if (similarCMDs.length > 0) {
const sc = similarCMDs.map(({value}) => `\t${value}\n`).join('');
console.error(`\nThe most similar commands are\n${sc}`);
}
process.exit(1);
}
/**
* When this provess exists, check to see if we have a running command, if we do
* check to see if it is still running. If it is, then kill it with a SIGINT
@@ -30,6 +47,8 @@ program
*/
process.once('exit', () => {
if (
// program.runningCommand &&
program.runningCommand.killed === false &&
program.runningCommand.exitCode === null
) {
+13 -8
View File
@@ -78,18 +78,23 @@ function rangeJobsByState(state = 'complete', limit) {
/**
* Cleans up the jobs that are in the queue.
*/
function cleanupJobs(options) {
async function cleanupJobs(options) {
const n = 100;
Promise.all([
rangeJobsByState('complete', n),
options.stuck ? rangeJobsByState('failed', n) : false
])
.then((joblists) => joblists.filter((jobs) => jobs).map(removeJobs))
.then(() => {
try {
const joblists = await Promise.all([
rangeJobsByState('complete', n),
options.stuck ? rangeJobsByState('failed', n) : false
]);
await joblists.filter((jobs) => jobs).map(removeJobs);
util.shutdown();
console.log('Removed old jobs');
});
} catch (err) {
console.error(err);
util.shutdown(1);
}
}
//==============================================================================
+4 -4
View File
@@ -53,7 +53,7 @@ function versionMatch(name, version) {
}
}
const EXTERNAL = /^\w[a-z\-0-9\.]+$/; // Match "react", "path", "fs", "lodash.random", etc.
const EXTERNAL = /^\w[a-z\-0-9.]+$/; // Match "react", "path", "fs", "lodash.random", etc.
function reconcilePackages({quiet = false, upgradeRemote = false}) {
const fetchable = [];
@@ -274,7 +274,7 @@ async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote
}
async function createSeedPlugin() {
const pluginsDir = path.join(__dirname, 'plugins');
const pluginsDir = path.resolve(__dirname, '..', 'plugins');
function pluginNameExists(pluginName) {
const pluginNames = fs.readdirSync(pluginsDir);
@@ -321,7 +321,7 @@ async function createSeedPlugin() {
// Creating plugin seed
//==============================================================================
const seedPlugin = path.join(__dirname, 'bin/templates/plugin');
const seedPlugin = path.join(__dirname, 'templates/plugin');
const newPluginPath = path.join(pluginsDir, answers.pluginName);
if (fs.existsSync(seedPlugin)) {
@@ -355,7 +355,7 @@ async function createSeedPlugin() {
// Let's add this to the plugins.json
if (answers.addPluginsJson) {
const pluginsJson = path.join(dir, 'plugins.json');
const pluginsJson = path.resolve(__dirname, '..', 'plugins.json');
fs.readJson(pluginsJson)
.then((j) => {
+13 -5
View File
@@ -12,6 +12,7 @@ const SetupService = require('../services/setup');
const kue = require('../services/kue');
const mongoose = require('../services/mongoose');
const util = require('./util');
const cache = require('../services/cache');
const {createSubscriptionManager} = require('../graph/subscriptions');
const {
PORT
@@ -80,12 +81,16 @@ function normalizePort(val) {
* Event listener for HTTP server "listening" event.
*/
function onListening() {
async function onListening() {
// Start the cache instance.
await cache.init();
let addr = server.address();
let bind = typeof addr === 'string'
? `pipe ${addr}`
: `port ${addr.port}`;
console.log(`API Server Listening on ${bind}`);
debug(`API Server Listening on ${bind}`);
}
/**
@@ -135,18 +140,21 @@ async function startApp(program) {
/**
* Listen on provided port, on all network interfaces.
*/
server.on('error', onError);
server.on('listening', onListening);
server.on('listening', () => {
});
server.listen(port, () => {
// Mount the websocket server if requested.
if (program.websockets) {
console.log(`Websocket Server Listening on ${port}`);
debug(`Websocket Server Listening on ${port}`);
// Mount the subscriptions server on the application server.
createSubscriptionManager(server);
}
});
server.on('error', onError);
server.on('listening', onListening);
}
//==============================================================================
+32 -6
View File
@@ -94,7 +94,7 @@ const performSetup = async () => {
name: 'requireEmailConfirmation',
default: settings.requireEmailConfirmation,
message: 'Should emails always be confirmed'
}
},
]);
// Update the settings that were changed.
@@ -104,6 +104,32 @@ const performSetup = async () => {
}
});
answers = await inquirer.prompt([
{
type: 'confirm',
name: 'inputWhitelistedDomains',
default: true,
message: 'Would you like to specify a whitelisted domain'
},
{
type: 'input',
name: 'whitelistedDomain',
message: 'Whitelisted Domain',
when: ({inputWhitelistedDomains}) => inputWhitelistedDomains,
validate: (input) => {
if (input && input.length > 0) {
return true;
}
return 'Whitelisted Domain cannot be empty.';
}
}
]);
if (answers.inputWhitelistedDomains) {
settings.domains.whitelist = [answers.whitelistedDomain];
}
console.log('\nWe\'ll ask you some questions about your first admin user.\n');
let user = await inquirer.prompt([
@@ -147,7 +173,11 @@ const performSetup = async () => {
name: 'confirmPassword',
message: 'Confirm Password',
type: 'password',
filter: (confirmPassword) => {
filter: (confirmPassword, {password}) => {
if (password !== confirmPassword) {
return Promise.reject(new Error('Passwords do not match'));
}
return UsersService
.isValidPassword(confirmPassword)
.catch((err) => {
@@ -157,10 +187,6 @@ const performSetup = async () => {
},
]);
if (user.password !== user.confirmPassword) {
return Promise.reject(new Error('Passwords do not match'));
}
let {user: newUser} = await SetupService.setup({
settings: settings.toObject(),
user: {
+38 -45
View File
@@ -98,39 +98,32 @@ function getUserCreateAnswers(options) {
/**
* Prompts for input and registers a user based on those.
*/
function createUser(options) {
getUserCreateAnswers(options)
.then((answers) => {
if (answers.password !== answers.confirmPassword) {
return Promise.reject(new Error('Passwords do not match'));
}
async function createUser(options) {
try {
const answers = await getUserCreateAnswers(options);
if (answers.password !== answers.confirmPassword) {
throw new Error('Passwords do not match');
}
return answers;
})
.then((answers) => {
return UsersService
.createLocalUser(answers.email.trim(), answers.password.trim(), answers.username.trim())
.then((user) => {
console.log(`Created user ${user.id}.`);
const user = await UsersService.createLocalUser(answers.email.trim(), answers.password.trim(), answers.username.trim());
console.log(`Created user ${user.id}.`);
if (answers.roles.length > 0) {
return Promise.all(answers.roles.map((role) => {
return UsersService
.addRoleToUser(user.id, role)
.then(() => {
console.log(`Added the role ${role} to User ${user.id}.`);
});
}));
}
});
})
.then(() => {
util.shutdown();
})
.catch((err) => {
console.error(err);
util.shutdown();
});
if (answers.roles.length > 0) {
return Promise.all(answers.roles.map((role) => {
return UsersService
.addRoleToUser(user.id, role)
.then(() => {
console.log(`Added the role ${role} to User ${user.id}.`);
});
}));
}
util.shutdown();
} catch (err) {
console.error(err);
util.shutdown();
}
}
/**
@@ -169,21 +162,21 @@ function passwd(userID) {
validate: validateRequired('Confirm Password is required')
}
])
.then((answers) => {
if (answers.password !== answers.confirmPassword) {
return Promise.reject(new Error('Password mismatch'));
}
.then((answers) => {
if (answers.password !== answers.confirmPassword) {
throw new Error('Password mismatch');
}
return UsersService.changePassword(userID, answers.password);
})
.then(() => {
console.log('Password changed.');
util.shutdown();
})
.catch((err) => {
console.error(err);
util.shutdown(1);
});
return UsersService.changePassword(userID, answers.password);
})
.then(() => {
console.log('Password changed.');
util.shutdown();
})
.catch((err) => {
console.error(err);
util.shutdown(1);
});
}
/**
Executable
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
const program = require('./commander');
const mongoose = require('../services/mongoose');
const util = require('./util');
const databaseVerifications = require('./verifications/database');
// Register the shutdown criteria.
util.onshutdown([
() => mongoose.disconnect()
]);
async function database({fix = false, limit = Infinity, batch = 1000}) {
try {
for (const verification of databaseVerifications) {
await verification({fix, limit, batch});
}
} catch (err) {
console.error(`Failed to process all the ${databaseVerifications.length} verifications`, err);
util.shutdown(1);
return;
}
util.shutdown();
}
//==============================================================================
// Setting up the program command line arguments.
//==============================================================================
program
.command('db')
.description('verifies the database integrity')
.option('-f, --fix', 'fix the problems found with database inconsistencies')
.option('-l, --limit [size]', 'limit the amount of documents to process in a single pass, this will ensure only a maximum number of batch operations are issued [default: inf]', parseInt)
.option('-b, --batch [size]', 'batch size to process verifications and repairs of documents [default: 1000]', parseInt)
.action(database);
program.parse(process.argv);
// If there is no command listed, output help.
if (!process.argv.slice(2).length) {
program.outputHelp();
util.shutdown();
}
+2 -2
View File
@@ -4,11 +4,11 @@
#
# ```
# en:
# coral-plugin-respect:
# talk-plugin-respect:
# respect: Respect
# respected: Respected
# es:
# coral-plugin-respect:
# talk-plugin-respect:
# respect: Respetar
# respected: Respetado
# ```
+193
View File
@@ -0,0 +1,193 @@
const CommentModel = require('../../../models/comment');
const ActionsService = require('../../../services/actions');
const {arrayJoinBy, singleJoinBy} = require('../../../graph/loaders/util');
const sc = require('snake-case');
const debug = require('debug')('talk:cli:verify');
const getBatch = async (limit, offset) => CommentModel
.find({})
.select({'id': 1, 'action_counts': 1, 'reply_count': 1})
.limit(limit)
.skip(offset)
.sort('created_at');
module.exports = async ({fix, limit, batch}) => {
let operations = [];
// Count how many comments there are to process.
const totalCount = await CommentModel.count();
let offset = 0;
let comments = [];
let commentIDs = [];
console.log(`Processing ${totalCount} comments in batches of ${limit}...`);
// Keep processing documents until there are is none left.
while (offset < totalCount) {
// Get a batch of comments.
comments = await getBatch(batch, offset);
commentIDs = comments.map(({id}) => id);
// Get their reply counts.
let allReplyCounts = await CommentModel
.aggregate([
{
$match: {
parent_id: {
$in: commentIDs,
},
status: {
$in: ['NONE', 'ACCEPTED']
}
}
},
{
$group: {
_id: '$parent_id',
count: {
$sum: 1
}
}
}
])
.then(singleJoinBy(commentIDs, '_id'))
.then((results) => results.map((result) => result ? result.count : 0));
// Get their action summaries.
let allActionSummaries = await ActionsService
.getActionSummaries(commentIDs)
.then(arrayJoinBy(commentIDs, 'item_id'));
// Loop over the comments, with their action summaries.
for (let i = 0; i < comments.length; i++) {
let comment = comments[i];
let actionSummaries = allActionSummaries[i];
let replyCount = allReplyCounts[i];
// And check to see if the action summaries we just computed match what is
// currently set for the comments.
let commentOperations = [];
// If the reply count needs to be updated, then update it!
if (comment.reply_count !== replyCount) {
commentOperations.push({
reply_count: replyCount,
});
}
// First we process all the group id's.
for (let actionSummary of actionSummaries) {
if (actionSummary.group_id === null) {
continue;
}
// And we generate the group id.
const ACTION_TYPE = sc(actionSummary.action_type.toLowerCase());
const GROUP_ID = sc(actionSummary.group_id.toLowerCase());
if (GROUP_ID.length <= 0) {
continue;
}
// And we add a new batch operation if the action summary is associated
// with a group.
const ACTION_COUNT_FIELD = `${ACTION_TYPE}_${GROUP_ID}`;
// Check that the action summaries match the cached counts.
if (!comment.action_counts || !(ACTION_COUNT_FIELD in comment.action_counts) || comment.action_counts[ACTION_COUNT_FIELD] !== actionSummary.count) {
// Batch updates for those changes.
commentOperations.push({
[`action_counts.${ACTION_COUNT_FIELD}`]: actionSummary.count,
});
}
}
// Group all the action summaries together from all the different group
// ids.
let groupedActionSummaries = actionSummaries.reduce((acc, actionSummary) => {
const ACTION_TYPE = sc(actionSummary.action_type.toLowerCase());
if (!(ACTION_TYPE in acc)) {
acc[ACTION_TYPE] = 0;
}
acc[ACTION_TYPE] += actionSummary.count;
return acc;
}, {});
for (const ACTION_COUNT_FIELD of Object.keys(groupedActionSummaries)) {
const count = groupedActionSummaries[ACTION_COUNT_FIELD];
// Check that the action summaries match the cached counts.
if (!comment.action_counts || !(ACTION_COUNT_FIELD in comment.action_counts) || comment.action_counts[ACTION_COUNT_FIELD] !== count) {
// Batch updates for those changes.
commentOperations.push({
[`action_counts.${ACTION_COUNT_FIELD}`]: count,
});
}
}
// If this comment has action summaries that should be updated, then
// perform an update!
if (commentOperations.length > 0) {
operations.push({
updateOne: {
filter: {
id: comment.id
},
update: {
$set: Object.assign({}, ...commentOperations),
},
},
});
}
}
debug(`Processed batch of ${comments.length} comments.`);
if (operations.length >= limit) {
debug(`Queued operations are ${operations.length}, reached limit of ${limit}, not processing any more.`);
if (operations.length > limit) {
debug(`${operations.length - limit} operations have been truncated to enforce the limit`);
}
break;
}
offset += batch;
}
const OPERATIONS_LENGTH = operations.length;
if (limit < Infinity && offset + comments.length < totalCount) {
console.log(`Processed ${offset + comments.length}/${totalCount} comments because we reached the update limit of ${limit}.`);
} else {
console.log(`Processed all ${totalCount} comments.`);
}
console.log(`${OPERATIONS_LENGTH} documents need fixing.`);
// If fix was enabled, execute the batch writes.
if (OPERATIONS_LENGTH > 0) {
if (fix) {
debug(`Fixing ${OPERATIONS_LENGTH} documents...`);
while (operations.length) {
let batchOperations = operations.splice(0, batch);
let result = await CommentModel.collection.bulkWrite(batchOperations);
debug(`Fixed batch of ${result.modifiedCount} documents.`);
}
console.log(`Applied all ${OPERATIONS_LENGTH} fixes.`);
} else {
console.warn('Skipping fixing, --fix was not enabled, pass --fix to fix these errors');
}
}
};
+12
View File
@@ -0,0 +1,12 @@
// This will import all the verifications that should be run by the:
//
// cli verify database
//
// command. They exist in the form:
//
// async ({fix = false, batch = 1000}) => {}
//
// where their options are derrived.
module.exports = [
require('./comments'),
];
+2 -5
View File
@@ -1,6 +1,6 @@
machine:
node:
version: 7.10.1
version: 8
services:
- docker
- redis
@@ -21,9 +21,8 @@ dependencies:
# Install node dependencies.
- yarn --version
- yarn global add node-gyp --force
- yarn
cache_directories:
- ~/.cache/yarn
post:
# Build the static assets.
- yarn build
@@ -41,8 +40,6 @@ test:
override:
# Run the tests using the junit reporter.
- MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test
# Run the e2e test suite.
# - E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e
deployment:
release:
+3 -2
View File
@@ -9,6 +9,7 @@
"transform-object-assign",
"transform-object-rest-spread",
"transform-async-to-generator",
"transform-react-jsx"
"transform-react-jsx",
"syntax-dynamic-import"
]
}
}
+1
View File
@@ -4,6 +4,7 @@
"es6": true,
"mocha": true
},
"extends": "../.eslintrc.json",
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
+17 -18
View File
@@ -1,5 +1,6 @@
import React from 'react';
import {Router, Route, IndexRedirect, browserHistory, Redirect} from 'react-router';
import {Router, Route, IndexRedirect, IndexRoute} from 'react-router';
import PropTypes from 'prop-types';
import Configure from 'routes/Configure';
import Dashboard from 'routes/Dashboard';
@@ -14,7 +15,7 @@ const routes = (
<div>
<Route exact path="/admin/install" component={Install}/>
<Route path='/admin' component={Layout}>
<IndexRedirect to='/admin/moderate/all' />
<IndexRedirect to='/admin/moderate' />
<Route path='configure' component={Configure} />
<Route path='stories' component={Stories} />
<Route path='dashboard' component={Dashboard} />
@@ -34,28 +35,26 @@ const routes = (
{/* Moderation Routes */}
<Route path='moderate' component={ModerationLayout}>
<Route path='all' components={Moderation}>
<IndexRoute components={Moderation} />
<Route path=':tabOrId' components={Moderation} />
<Route path=':tab' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='accepted' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='premod' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='rejected' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='flagged' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Redirect from=':id' to='all/:id' />
<IndexRedirect to='all' />
</Route>
</Route>
</div>
);
const AppRouter = () => <Router history={browserHistory} routes={routes} />;
class AppRouter extends React.Component {
static contextTypes = {
history: PropTypes.object,
};
render() {
return <Router history={this.context.history} routes={routes} />;
}
}
export default AppRouter;
+5 -6
View File
@@ -8,7 +8,6 @@ import {
UPDATE_ASSETS
} from '../constants/assets';
import coralApi from '../../../coral-framework/helpers/request';
import t from 'coral-framework/services/i18n';
/**
@@ -17,9 +16,9 @@ import t from 'coral-framework/services/i18n';
// Fetch a page of assets
// Get comments to fill each of the three lists on the mod queue
export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filter = '') => (dispatch) => {
export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filter = '') => (dispatch, _, {rest}) => {
dispatch({type: FETCH_ASSETS_REQUEST});
return coralApi(`/assets?skip=${skip}&limit=${limit}&sort=${sort}&search=${search}&filter=${filter}`)
return rest(`/assets?skip=${skip}&limit=${limit}&sort=${sort}&search=${search}&filter=${filter}`)
.then(({result, count}) =>
dispatch({type: FETCH_ASSETS_SUCCESS,
assets: result,
@@ -34,9 +33,9 @@ export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filte
// Update an asset state
// Get comments to fill each of the three lists on the mod queue
export const updateAssetState = (id, closedAt) => (dispatch) => {
dispatch({type: UPDATE_ASSET_STATE_REQUEST});
return coralApi(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}})
export const updateAssetState = (id, closedAt) => (dispatch, _, {rest}) => {
dispatch({type: UPDATE_ASSET_STATE_REQUEST, id, closedAt});
return rest(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}})
.then(() => dispatch({type: UPDATE_ASSET_STATE_SUCCESS}))
.catch((error) => {
console.error(error);
+43 -16
View File
@@ -1,16 +1,13 @@
import bowser from 'bowser';
import * as actions from '../constants/auth';
import coralApi from 'coral-framework/helpers/request';
import * as Storage from 'coral-framework/helpers/storage';
import {handleAuthToken} from 'coral-framework/actions/auth';
import {resetWebsocket} from 'coral-framework/services/client';
import t from 'coral-framework/services/i18n';
import jwtDecode from 'jwt-decode';
//==============================================================================
// SIGN IN
//==============================================================================
export const handleLogin = (email, password, recaptchaResponse) => (dispatch) => {
export const handleLogin = (email, password, recaptchaResponse) => (dispatch, _, {rest, client, storage}) => {
dispatch({type: actions.LOGIN_REQUEST});
const params = {
@@ -27,18 +24,18 @@ export const handleLogin = (email, password, recaptchaResponse) => (dispatch) =>
};
}
return coralApi('/auth/local', params)
return rest('/auth/local', params)
.then(({user, token}) => {
if (!user) {
if (!bowser.safari && !bowser.ios) {
Storage.removeItem('token');
if (!bowser.safari && !bowser.ios && storage) {
storage.removeItem('token');
}
return dispatch(checkLoginFailure('not logged in'));
}
dispatch(handleAuthToken(token));
resetWebsocket();
client.resetWebsocket();
dispatch(checkLoginSuccess(user));
})
.catch((error) => {
@@ -84,11 +81,11 @@ const forgotPasswordFailure = (error) => ({
error,
});
export const requestPasswordReset = (email) => (dispatch) => {
export const requestPasswordReset = (email) => (dispatch, _, {rest}) => {
dispatch(forgotPasswordRequest(email));
const redirectUri = location.href;
return coralApi('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}})
return rest('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}})
.then(() => dispatch(forgotPasswordSuccess()))
.catch((error) => {
console.error(error);
@@ -116,18 +113,18 @@ const checkLoginFailure = (error) => ({
error
});
export const checkLogin = () => (dispatch) => {
export const checkLogin = () => (dispatch, _, {rest, client, storage}) => {
dispatch(checkLoginRequest());
return coralApi('/auth')
return rest('/auth')
.then(({user}) => {
if (!user) {
if (!bowser.safari && !bowser.ios) {
Storage.removeItem('token');
if (!bowser.safari && !bowser.ios && storage) {
storage.removeItem('token');
}
return dispatch(checkLoginFailure('not logged in'));
}
resetWebsocket();
client.resetWebsocket();
dispatch(checkLoginSuccess(user));
})
.catch((error) => {
@@ -136,3 +133,33 @@ export const checkLogin = () => (dispatch) => {
dispatch(checkLoginFailure(errorMessage));
});
};
//==============================================================================
// LOGOUT
//==============================================================================
export const logout = () => (dispatch, _, {rest, client, storage}) => {
return rest('/auth', {method: 'DELETE'}).then(() => {
if (storage) {
storage.removeItem('token');
}
// Reset the websocket.
client.resetWebsocket();
dispatch({type: actions.LOGOUT});
});
};
//==============================================================================
// AUTH TOKEN
//==============================================================================
export const handleAuthToken = (token) => (dispatch, _, {storage}) => {
if (storage) {
storage.setItem('exp', jwtDecode(token).exp);
storage.setItem('token', token);
}
dispatch({type: 'HANDLE_AUTH_TOKEN'});
};
+12 -13
View File
@@ -14,13 +14,12 @@ import {
HIDE_REJECT_USERNAME_DIALOG
} from '../constants/community';
import coralApi from '../../../coral-framework/helpers/request';
import t from 'coral-framework/services/i18n';
export const fetchAccounts = (query = {}) => (dispatch) => {
export const fetchAccounts = (query = {}) => (dispatch, _, {rest}) => {
dispatch(requestFetchAccounts());
coralApi(`/users?${qs.stringify(query)}`)
rest(`/users?${qs.stringify(query)}`)
.then(({result, page, count, limit, totalPages}) =>{
dispatch({
type: FETCH_COMMENTERS_SUCCESS,
@@ -51,18 +50,18 @@ export const newPage = () => ({
type: COMMENTERS_NEW_PAGE
});
export const setRole = (id, role) => (dispatch) => {
return coralApi(`/users/${id}/role`, {method: 'POST', body: {role}})
.then(() => {
return dispatch({type: SET_ROLE, id, role});
});
export const setRole = (id, role) => (dispatch, _, {rest}) => {
return rest(`/users/${id}/role`, {method: 'POST', body: {role}})
.then(() => {
return dispatch({type: SET_ROLE, id, role});
});
};
export const setCommenterStatus = (id, status) => (dispatch) => {
return coralApi(`/users/${id}/status`, {method: 'POST', body: {status}})
.then(() => {
return dispatch({type: SET_COMMENTER_STATUS, id, status});
});
export const setCommenterStatus = (id, status) => (dispatch, _, {rest}) => {
return rest(`/users/${id}/status`, {method: 'POST', body: {status}})
.then(() => {
return dispatch({type: SET_COMMENTER_STATUS, id, status});
});
};
// Ban User Dialog
+17 -18
View File
@@ -1,4 +1,3 @@
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';
@@ -27,19 +26,19 @@ const validation = (formData, dispatch, next) => {
// Required Validation
const empty = validKeys
.filter((name) => {
const cond = !formData[name].length;
.filter((name) => {
const cond = !formData[name].length;
if (cond) {
if (cond) {
// Adding Error
dispatch(addError(name, 'This field is required.'));
} else {
dispatch(addError(name, ''));
}
// Adding Error
dispatch(addError(name, 'This field is required.'));
} else {
dispatch(addError(name, ''));
}
return cond;
});
return cond;
});
if (empty.length) {
dispatch(hasError());
@@ -93,23 +92,23 @@ const validation = (formData, dispatch, next) => {
};
export const submitSettings = () => (dispatch, getState) => {
const settingsFormData = getState().install.toJS().data.settings;
const settingsFormData = getState().install.data.settings;
validation(settingsFormData, dispatch, function() {
dispatch(nextStep());
});
};
export const submitUser = () => (dispatch, getState) => {
const userFormData = getState().install.toJS().data.user;
const userFormData = getState().install.data.user;
validation(userFormData, dispatch, function() {
dispatch(nextStep());
});
};
export const finishInstall = () => (dispatch, getState) => {
const data = getState().install.toJS().data;
export const finishInstall = () => (dispatch, getState, {rest}) => {
const data = getState().install.data;
dispatch(installRequest());
return coralApi('/setup', {method: 'POST', body: data})
return rest('/setup', {method: 'POST', body: data})
.then(() => {
dispatch(installSuccess());
dispatch(nextStep());
@@ -129,9 +128,9 @@ const checkInstallRequest = () => ({type: actions.CHECK_INSTALL_REQUEST});
const checkInstallSuccess = (installed) => ({type: actions.CHECK_INSTALL_SUCCESS, installed});
const checkInstallFailure = (error) => ({type: actions.CHECK_INSTALL_FAILURE, error});
export const checkInstall = (next) => (dispatch) => {
export const checkInstall = (next) => (dispatch, _, {rest}) => {
dispatch(checkInstallRequest());
coralApi('/setup')
rest('/setup')
.then(({installed}) => {
dispatch(checkInstallSuccess(installed));
if (installed) {
+5 -25
View File
@@ -4,44 +4,24 @@ export const toggleModal = (open) => ({type: actions.TOGGLE_MODAL, open});
export const singleView = () => ({type: actions.SINGLE_VIEW});
// hide shortcuts note
export const hideShortcutsNote = () => {
export const hideShortcutsNote = () => (dispatch, _, {storage}) => {
try {
window.localStorage.setItem('coral:shortcutsNote', 'hide');
if (storage) {
storage.setItem('coral:shortcutsNote', 'hide');
}
} catch (e) {
// above will fail in Safari private mode
}
return {type: actions.HIDE_SHORTCUTS_NOTE};
dispatch({type: actions.HIDE_SHORTCUTS_NOTE});
};
export const viewUserDetail = (userId) => ({type: actions.VIEW_USER_DETAIL, userId});
export const hideUserDetail = () => ({type: actions.HIDE_USER_DETAIL});
export const setSortOrder = (order) => ({
type: actions.SET_SORT_ORDER,
order
});
export const changeUserDetailStatuses = (tab) => {
let statuses;
if (tab === 'all') {
statuses = ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'];
} else if (tab === 'rejected') {
statuses = ['REJECTED'];
}
return {type: actions.CHANGE_USER_DETAIL_STATUSES, tab, statuses};
};
export const clearUserDetailSelections = () => ({type: actions.CLEAR_USER_DETAIL_SELECTIONS});
export const toggleSelectCommentInUserDetail = (id, active) => {
return {
type: active ? actions.SELECT_USER_DETAIL_COMMENT : actions.UNSELECT_USER_DETAIL_COMMENT,
id
};
};
export const toggleStorySearch = (active) => ({
type: active ? actions.SHOW_STORY_SEARCH : actions.HIDE_STORY_SEARCH
});
+5 -6
View File
@@ -1,4 +1,3 @@
import coralApi from '../../../coral-framework/helpers/request';
import t from 'coral-framework/services/i18n';
export const SETTINGS_LOADING = 'SETTINGS_LOADING';
@@ -14,9 +13,9 @@ 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, _, {rest}) => {
dispatch({type: SETTINGS_LOADING});
coralApi('/settings')
rest('/settings')
.then((settings) => {
dispatch({type: SETTINGS_RECEIVED, settings});
})
@@ -41,13 +40,13 @@ export const updateDomainlist = (listName, list) => {
return {type: DOMAINLIST_UPDATED, listName, list};
};
export const saveSettingsToServer = () => (dispatch, getState) => {
let settings = getState().settings.toJS();
export const saveSettingsToServer = () => (dispatch, getState, {rest}) => {
let settings = getState().settings;
if (settings.charCount) {
settings.charCount = parseInt(settings.charCount);
}
dispatch({type: SAVE_SETTINGS_LOADING});
coralApi('/settings', {method: 'PUT', body: settings})
rest('/settings', {method: 'PUT', body: settings})
.then(() => {
dispatch({type: SAVE_SETTINGS_SUCCESS, settings});
})
@@ -0,0 +1,24 @@
import * as actions from 'constants/userDetail';
export const viewUserDetail = (userId) => ({type: actions.VIEW_USER_DETAIL, userId});
export const hideUserDetail = () => ({type: actions.HIDE_USER_DETAIL});
export const changeUserDetailStatuses = (tab) => {
let statuses;
if (tab === 'all') {
statuses = ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'];
} else if (tab === 'rejected') {
statuses = ['REJECTED'];
}
return {type: actions.CHANGE_USER_DETAIL_STATUSES, tab, statuses};
};
export const clearUserDetailSelections = () => ({type: actions.CLEAR_USER_DETAIL_SELECTIONS});
export const toggleSelectCommentInUserDetail = (id, active) => {
return {
type: active ? actions.SELECT_USER_DETAIL_COMMENT : actions.UNSELECT_USER_DETAIL_COMMENT,
id
};
};
+6 -7
View File
@@ -1,4 +1,3 @@
import coralApi from '../../../coral-framework/helpers/request';
import * as userTypes from '../constants/users';
import t from 'coral-framework/services/i18n';
@@ -7,9 +6,9 @@ import t from 'coral-framework/services/i18n';
*/
// change status of a user
export const userStatusUpdate = (status, userId, commentId) => {
return (dispatch) => {
return (dispatch, _, {rest}) => {
dispatch({type: userTypes.UPDATE_STATUS_REQUEST});
return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
return rest(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
.then((res) => dispatch({type: userTypes.UPDATE_STATUS_SUCCESS, res}))
.catch((error) => {
console.error(error);
@@ -21,8 +20,8 @@ export const userStatusUpdate = (status, userId, commentId) => {
// change status of a user
export const sendNotificationEmail = (userId, subject, body) => {
return (dispatch) => {
return coralApi(`/users/${userId}/email`, {method: 'POST', body: {subject, body}})
return (dispatch, _, {rest}) => {
return rest(`/users/${userId}/email`, {method: 'POST', body: {subject, body}})
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
@@ -33,8 +32,8 @@ export const sendNotificationEmail = (userId, subject, body) => {
// let a user edit their username
export const enableUsernameEdit = (userId) => {
return (dispatch) => {
return coralApi(`/users/${userId}/username-enable`, {method: 'POST'})
return (dispatch, _, {rest}) => {
return rest(`/users/${userId}/username-enable`, {method: 'POST'})
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
@@ -1,7 +1,7 @@
import React, {PropTypes} from 'react';
import styles from './ModerationList.css';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../routes/Moderation/helpers/moderationQueueActionsMap';
import {menuActionsMap} from '../utils/moderationQueueActionsMap';
import t from 'coral-framework/services/i18n';
+14 -14
View File
@@ -69,23 +69,23 @@ class AdminLogin extends React.Component {
);
const requestPasswordForm = (
this.props.passwordRequestSuccess
? <p className={styles.passwordRequestSuccess} onClick={() => {
location.href = location.href;
}}>
? <p className={styles.passwordRequestSuccess} onClick={() => {
location.href = location.href;
}}>
{this.props.passwordRequestSuccess} <a className={styles.signInLink} href="#">Sign in</a>
<Success />
</p>
: <form onSubmit={this.handleRequestPassword}>
<TextField
label='Email Address'
value={this.state.email}
onChange={(e) => this.setState({email: e.target.value})} />
<Button
type='submit'
cStyle='black'
full
onClick={this.handleRequestPassword}>Reset Password</Button>
</form>
: <form onSubmit={this.handleRequestPassword}>
<TextField
label='Email Address'
value={this.state.email}
onChange={(e) => this.setState({email: e.target.value})} />
<Button
type='submit'
cStyle='black'
full
onClick={this.handleRequestPassword}>Reset Password</Button>
</form>
);
return (
<Layout fixedDrawer restricted={true}>
@@ -0,0 +1,24 @@
.bodyLeave {
position: absolute;
width: 100%;
top: 0;
background-color: white;
opacity: 1.0;
transition: background 400ms, opacity 800ms 1600ms;
pointer-events: none;
}
.bodyLeaveActive {
opacity: 0;
background-color: rgba(255,255,0, 0.2);
}
.bodyEnter {
opacity: 0;
pointer-events: none;
}
.bodyEnterActive {
opacity: 1.0;
transition: opacity 800ms 2400ms;
}
@@ -0,0 +1,24 @@
import React from 'react';
import {murmur3} from 'murmurhash-js';
import {CSSTransitionGroup} from 'react-transition-group';
import styles from './CommentAnimatedEdit.css';
export default ({children, body}) => {
return (
<CSSTransitionGroup
component={'div'}
transitionName={{
enter: styles.bodyEnter,
enterActive: styles.bodyEnterActive,
leave: styles.bodyLeave,
leaveActive: styles.bodyLeaveActive,
}}
transitionEnter={true}
transitionLeave={true}
transitionEnterTimeout={3600}
transitionLeaveTimeout={2800}
>
{React.cloneElement(React.Children.only(children), {key: murmur3(body)})}
</CSSTransitionGroup>
);
};
@@ -0,0 +1,25 @@
import React from 'react';
import Highlighter from 'react-highlight-words';
import Linkify from 'react-linkify';
const linkify = new Linkify();
export default ({suspectWords, bannedWords, body, ...rest}) => {
const links = linkify.getMatches(body);
const linkText = links ? links.map((link) => link.raw) : [];
const searchWords = [
...suspectWords,
...bannedWords,
...linkText
];
return (
<Highlighter
{...rest}
autoEscape={true}
searchWords={searchWords}
textToHighlight={body}
/>
);
};
@@ -1,22 +1,19 @@
.commentType {
position: absolute;
right: 15px;
top: 11px;
display: inline-block;
color: white;
background: grey;
height: 32px;
box-sizing: border-box;
line-height: 29px;
padding: 2px 8px 2px 26px;
border-radius: 2px;
padding: 2px 5px;
font-size: 12px;
i {
height: 24px;
letter-spacing: 0.4px;
line-height: 22px;
> i {
font-size: 14px;
position: absolute;
left: 6px;
top: 8px;
vertical-align: text-top;
margin: 0;
margin-right: 4px;
}
&.premod {
@@ -1,12 +1,13 @@
import React, {PropTypes} from 'react';
import styles from './CommentType.css';
import {Icon} from 'coral-ui';
import cn from 'classnames';
const CommentType = (props) => {
const typeData = getTypeData(props.type);
return (
<span className={`${styles.commentType} ${styles[typeData.className]}`}>
<span className={cn(styles.commentType, styles[typeData.className], props.className)}>
<Icon name={typeData.icon}/>{typeData.text}
</span>
);
@@ -1,6 +1,6 @@
.flagBox {
border-top: 1px solid rgba(66, 66, 66, 0.12);
margin-top: 10px;
.container {
padding: 0 14px;
}
@@ -61,9 +61,11 @@ class FlagBox extends Component {
<ul>
{actionList.map((action, j) =>
<li key={`${i}_${j}`} className={styles.subDetail}>
<a className={styles.username} onClick={viewUserDetail}>
{action.user.username}
</a>
{action.user &&
<a className={styles.username} onClick={() => viewUserDetail(action.user.id)}>
{action.user.username}
</a>
}
{action.message}
</li>
)}
@@ -0,0 +1,13 @@
import React from 'react';
import Linkify from 'react-linkify';
const linkify = new Linkify();
export default ({text, children}) => {
const hasLinks = !!linkify.getMatches(text);
if (!hasLinks) {
return null;
}
return React.Children.only(children);
};
@@ -0,0 +1,21 @@
.loadMoreContainer {
display: flex;
justify-content: center;
width: 100%;
}
.loadMore {
width: 100%;
text-align: center;
color: #FFF;
max-width: 660px;
margin-bottom: 30px;
background-color: #2376D8;
cursor: pointer;
}
.loadMore:hover {
background-color: #4399FF;
}
@@ -1,9 +1,10 @@
import React, {PropTypes} from 'react';
import {Button} from 'coral-ui';
import styles from './styles.css';
import styles from './LoadMore.css';
import cn from 'classnames';
const LoadMore = ({loadMore, showLoadMore}) =>
<div className={styles.loadMoreContainer}>
const LoadMore = ({loadMore, showLoadMore, className, ...rest}) =>
<div {...rest} className={cn(className, styles.loadMoreContainer)}>
{
showLoadMore && <Button
className={styles.loadMore}
@@ -186,13 +186,11 @@
.actionButton {
transform: scale(.8);
margin: 0;
width: 140px;
}
.minimal {
width: 45px;
min-width: 0;
float: left;
}
.approve__active {
@@ -0,0 +1,11 @@
import React from 'react';
import {Badge} from 'coral-ui';
import t from 'coral-framework/services/i18n';
const ReplyBadge = () => (
<Badge icon="reply">
{t('modqueue.reply')}
</Badge>
);
export default ReplyBadge;
@@ -65,7 +65,7 @@ class SuspendUserDialog extends React.Component {
{t('suspenduser.title_suspend')}
</h1>
<p className={styles.description}>
{t('suspenduser.description_suspend', username)}
{t('suspenduser.description_suspend', username)}
</p>
<fieldset>
<legend className={styles.legend}>{t('suspenduser.select_duration')}</legend>
@@ -103,7 +103,7 @@ class SuspendUserDialog extends React.Component {
{t('suspenduser.title_notify')}
</h1>
<p className={styles.description}>
{t('suspenduser.description_notify', username)}
{t('suspenduser.description_notify', username)}
</p>
<fieldset>
<legend className={styles.legend}>{t('suspenduser.write_message')}</legend>
@@ -0,0 +1,144 @@
.copyButton {
background-color: white;
border: solid 1px;
padding: 2px 6px;
height: auto;
line-height: initial;
min-width: auto;
letter-spacing: normal;
font-size: 0.9em;
margin-left: 10px;
}
.userDetailList {
list-style: none;
padding: 0;
margin: 0;
}
.userDetailItem {
margin: 0 5px;
font-weight: 500;
}
.stats {
display: flex;
list-style: none;
padding: 0;
margin: 0;
text-align: center;
margin: 15px 0 5px;
color: #595959;
}
.stat {
margin-right: 20px;
}
.stat:last-child {
margin-right: 0px;
}
.statItem, .statReportResult {
padding: 3px 5px;
background-color: #D8D8D8;
border-radius: 3px;
font-weight: 500;
display: block;
font-size: 0.9em;
line-height: normal;
letter-spacing: 0.4px;
min-width: 60px;
}
.statResult {
font-size: 1.5em;
padding: 5px 0;
display: inline-block;
}
.statReportResult {
color: white;
margin: 5px 0;
font-weight: 400;
}
.statReportResult.reliable {
background-color: #749C48;
}
.statReportResult.neutral {
background-color: #616161;
}
.statReportResult.unreliable {
background-color: #F44336;
}
.memberSince {
clear: both;
}
.small {
color: #888888;
font-size: 0.9em;
letter-spacing: 0.4px;
}
.profileEmail {
border: none;
background-color: transparent;
font-size: 16px;
outline: none;
width: calc(100% - 90px);
}
.commentStatuses {
padding: 10px 0 0 0;
margin: 0;
height: 52px;
list-style: none;
box-sizing: border-box;
li {
display: inline-block;
margin-right: 10px;
cursor: pointer;
padding: 0 10px;
}
}
.active {
font-weight: bold;
border-bottom: 3px solid #F36451;
}
.bulkActionGroup {
height: 52px;
background-color: #efefef;
i {
margin-right: 0;
}
.bulkAction {
display: inline-block;
width: 48px;
height: 48px;
transform: scale(.7);
min-width: 0;
}
.bulkAction:last-child {
margin-left: -10px;
}
}
.loadMore > button {
background-color: #696969;
&:hover {
background-color: #404040;
color: white;
}
}
@@ -0,0 +1,205 @@
import React from 'react';
import PropTypes from 'prop-types';
import Comment from '../containers/UserDetailComment';
import styles from './UserDetail.css';
import {Icon, Button, Drawer, Spinner} from 'coral-ui';
import {Slot} from 'coral-framework/components';
import ButtonCopyToClipboard from './ButtonCopyToClipboard';
import {actionsMap} from '../utils/moderationQueueActionsMap';
import ClickOutside from 'coral-framework/components/ClickOutside';
import LoadMore from '../components/LoadMore';
import cn from 'classnames';
import capitalize from 'lodash/capitalize';
import {getReliability} from 'coral-framework/utils/user';
export default class UserDetail extends React.Component {
static propTypes = {
userId: PropTypes.string.isRequired,
hideUserDetail: PropTypes.func.isRequired,
root: PropTypes.object.isRequired,
bannedWords: PropTypes.array.isRequired,
suspectWords: PropTypes.array.isRequired,
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
changeStatus: PropTypes.func.isRequired,
toggleSelect: PropTypes.func.isRequired,
bulkAccept: PropTypes.func.isRequired,
bulkReject: PropTypes.func.isRequired,
}
rejectThenReload = (info) => {
this.props.rejectComment(info).then(() => {
this.props.data.refetch();
});
}
acceptThenReload = (info) => {
this.props.acceptComment(info).then(() => {
this.props.data.refetch();
});
}
showAll = () => {
this.props.changeStatus('all');
}
showRejected = () => {
this.props.changeStatus('rejected');
}
renderLoading() {
return (
<ClickOutside onClickOutside={this.props.hideUserDetail}>
<Drawer onClose={this.props.hideUserDetail}>
<Spinner />
</Drawer>
</ClickOutside>
);
}
renderLoaded() {
const {
root,
root: {
user,
totalComments,
rejectedComments,
comments: {nodes, hasNextPage}
},
activeTab,
selectedCommentIds,
bannedWords,
suspectWords,
toggleSelect,
bulkAccept,
bulkReject,
hideUserDetail,
viewUserDetail,
loadMore,
} = this.props;
let rejectedPercent = (rejectedComments / totalComments) * 100;
if (rejectedPercent === Infinity || isNaN(rejectedPercent)) {
// if totalComments is 0, you're dividing by zero, which is naughty
rejectedPercent = 0;
}
return (
<ClickOutside onClickOutside={hideUserDetail}>
<Drawer onClose={hideUserDetail}>
<h3>{user.username}</h3>
<div>
<ul className={styles.userDetailList}>
<li>
<Icon name="assignment_ind"/>
<span className={styles.userDetailItem}>Member Since:</span>
{new Date(user.created_at).toLocaleString()}
</li>
{user.profiles.map(({id}) =>
<li key={id}>
<Icon name="email"/>
<span className={styles.userDetailItem}>Email:</span>
{id} <ButtonCopyToClipboard className={styles.copyButton} icon="content_copy" copyText={id} />
</li>
)}
</ul>
<ul className={styles.stats}>
<li className={styles.stat}>
<span className={styles.statItem}> Total Comments </span>
<spam className={styles.statResult}> {totalComments} </spam>
</li>
<li className={styles.stat}>
<spam className={styles.statItem}> Reject Rate </spam>
<spam className={styles.statResult}> {`${(rejectedPercent).toFixed(1)}%`} </spam>
</li>
<li className={styles.stat}>
<spam className={styles.statItem}> Reports </spam>
<spam className={cn(styles.statReportResult, styles[getReliability(user.reliable.flagger)])}>
{capitalize(getReliability(user.reliable.flagger))}
</spam>
</li>
</ul>
<p className={styles.small}>
Data represents the last six months of activity
</p>
</div>
<Slot
fill="userProfile"
data={this.props.data}
queryData={root, user}
/>
<hr/>
{
selectedCommentIds.length === 0
? (
<ul className={styles.commentStatuses}>
<li className={activeTab === 'all' ? styles.active : ''} onClick={this.showAll}>All</li>
<li className={activeTab === 'rejected' ? styles.active : ''} onClick={this.showRejected}>Rejected</li>
</ul>
)
: (
<div className={styles.bulkActionGroup}>
<Button
onClick={bulkAccept}
className={styles.bulkAction}
cStyle='approve'
icon='done'>
</Button>
<Button
onClick={bulkReject}
className={styles.bulkAction}
cStyle='reject'
icon='close'>
</Button>
{`${selectedCommentIds.length} comments selected`}
</div>
)
}
<div>
{
nodes.map((comment) => {
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
const selected = selectedCommentIds.indexOf(comment.id) !== -1;
return <Comment
key={comment.id}
user={user}
comment={comment}
selected={false}
suspectWords={suspectWords}
bannedWords={bannedWords}
actions={actionsMap[status]}
acceptComment={this.acceptThenReload}
rejectComment={this.rejectThenReload}
selected={selected}
toggleSelect={toggleSelect}
viewUserDetail={viewUserDetail}
/>;
})
}
</div>
<LoadMore
className={styles.loadMore}
loadMore={loadMore}
showLoadMore={hasNextPage}
/>
</Drawer>
</ClickOutside>
);
}
render () {
if (this.props.loading) {
return this.renderLoading();
}
return this.renderLoaded();
}
}
@@ -0,0 +1,132 @@
.root {
display: block;
margin: 0;
border-bottom: 1px solid #e0e0e0;
width: 100%;
transition: all 200ms;
padding: 10px 0px;
min-height: 0;
}
.root:last-child {
border: 0;
}
.rootSelected {
background-color: #ecf4ff;
}
.container {
padding: 0px 14px;
}
.story {
font-size: 14px;
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
max-width: 500px;
}
.story > a {
display: inline-block;
color: #063b9a;
text-decoration: none;
font-weight: 500;
letter-spacing: .5px;
margin-left: 10px;
font-size: 13px;
margin-left: 5px;
padding-bottom: 0px;
border-bottom: solid 1px;
line-height: 16px;
}
.bodyContainer {
display: flex;
justify-content: space-between;
font-size: 14px;
line-height: 1.5;
font-weight: 300;
margin-bottom: 8px;
}
.header {
position: relative;
}
.badgeBar {
position: absolute;
right: 0px;
}
.created {
padding: 5px;
color: #262626;
font-size: 14px;
line-height: 1px;
font-weight: 300;
}
.body {
margin-top: 0px;
flex: 1;
color: black;
max-width: 500px;
word-wrap: break-word;
font-weight: 300;
font-size: 16px;
max-width: 360px;
}
.sideActions {
}
.editedMarker {
font-style: italic;
color: #666;
font-size: 12px;
line-height: 1px;
font-weight: 300;
}
.actions {
display: flex;
justify-content: flex-end;
}
.bulkSelectInput {
cursor: pointer;
}
.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;
}
}
.hasLinks {
color: #f00;
text-align: right;
display: flex;
align-items: center;
> i {
margin-right: 5px;
}
}
@@ -0,0 +1,156 @@
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import {Icon} from 'coral-ui';
import FlagBox from './FlagBox';
import ReplyBadge from './ReplyBadge';
import styles from './UserDetailComment.css';
import CommentType from './CommentType';
import {getActionSummary} from 'coral-framework/utils';
import ActionButton from 'coral-admin/src/components/ActionButton';
import CommentBodyHighlighter from 'coral-admin/src/components/CommentBodyHighlighter';
import IfHasLink from 'coral-admin/src/components/IfHasLink';
import cn from 'classnames';
import {getCommentType} from 'coral-admin/src/utils/comment';
import CommentAnimatedEdit from './CommentAnimatedEdit';
import t, {timeago} from 'coral-framework/services/i18n';
class UserDetailComment extends React.Component {
render() {
const {
actions = [],
comment,
viewUserDetail,
suspectWords,
bannedWords,
selected,
toggleSelect,
className,
user,
...props
} = this.props;
const flagActionSummaries = getActionSummary('FlagActionSummary', comment);
const flagActions = comment.actions && comment.actions.filter((a) => a.__typename === 'FlagAction');
const commentType = getCommentType(comment);
return (
<li
tabIndex={0}
className={cn(className, styles.root, {[styles.rootSelected]: selected})}
>
<div className={styles.container}>
<div className={styles.header}>
<input
className={styles.bulkSelectInput}
type='checkbox'
value={comment.id}
checked={selected}
onChange={(e) => toggleSelect(e.target.value, e.target.checked)} />
<span className={styles.created}>
{timeago(comment.created_at)}
</span>
{
(comment.editing && comment.editing.edited)
? <span>&nbsp;<span className={styles.editedMarker}>({t('comment.edited')})</span></span>
: null
}
<div className={styles.badgeBar}>
{comment.hasParent && <ReplyBadge/>}
<CommentType type={commentType}/>
</div>
</div>
<div className={styles.story}>
Story: {comment.asset.title}
{<Link to={`/admin/moderate/${comment.asset.id}`}>{t('modqueue.moderate')}</Link>}
</div>
<CommentAnimatedEdit body={comment.body}>
<div className={styles.bodyContainer}>
<p className={styles.body}>
<CommentBodyHighlighter
suspectWords={suspectWords}
bannedWords={bannedWords}
body={comment.body}
/>
{' '}
<a
className={styles.external}
href={`${comment.asset.url}?commentId=${comment.id}`}
target="_blank"
>
<Icon name="open_in_new" /> {t('comment.view_context')}
</a>
</p>
<div className={styles.sideActions}>
<IfHasLink text={comment.body}>
<span className={styles.hasLinks}>
<Icon name="error_outline" /> Contains Link
</span>
</IfHasLink>
<div className={styles.actions}>
{actions.map((action, i) => {
const active =
(action === 'REJECT' && comment.status === 'REJECTED') ||
(action === 'APPROVE' && comment.status === 'ACCEPTED');
return (
<ActionButton
minimal={true}
key={i}
type={action}
user={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>
</div>
</div>
</CommentAnimatedEdit>
</div>
{flagActions && flagActions.length
? <FlagBox
actions={flagActions}
actionSummaries={flagActionSummaries}
viewUserDetail={viewUserDetail}
/>
: null}
</li>
);
}
}
UserDetailComment.propTypes = {
user: PropTypes.object.isRequired,
viewUserDetail: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
className: PropTypes.string,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
toggleSelect: PropTypes.func,
comment: PropTypes.shape({
body: PropTypes.string.isRequired,
action_summaries: PropTypes.array,
actions: PropTypes.array,
created_at: PropTypes.string.isRequired,
asset: PropTypes.shape({
title: PropTypes.string,
url: PropTypes.string,
id: PropTypes.string
})
})
};
export default UserDetailComment;
+30 -30
View File
@@ -13,9 +13,9 @@ const CoralHeader = ({
}) => (
<Header className={styles.header}>
<Logo className={styles.logo} />
<div>
{
auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
<div>
{
auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
<Navigation className={styles.nav}>
<IndexLink
id='dashboardNav'
@@ -63,36 +63,36 @@ const CoralHeader = ({
</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={() => showShortcuts(true)}>{t('configure.shortcuts')}</MenuItem>
<MenuItem>
<a href="https://github.com/coralproject/talk/releases" target="_blank">
}
<div className={styles.rightPanel}>
<ul>
<li className={styles.settings}>
<div>
<IconButton name="settings" id="menu-settings"/>
<Menu target="menu-settings" align="right">
<MenuItem onClick={() => showShortcuts(true)}>{t('configure.shortcuts')}</MenuItem>
<MenuItem>
<a href="https://github.com/coralproject/talk/releases" target="_blank">
View latest version
</a>
</MenuItem>
<MenuItem>
<a href="https://coralproject.net/contribute.html#other-ideas-and-bug-reports" target="_blank">
</a>
</MenuItem>
<MenuItem>
<a href="https://coralproject.net/contribute.html#other-ideas-and-bug-reports" target="_blank">
Report a bug or give feedback
</a>
</MenuItem>
<MenuItem onClick={handleLogout}>
{t('configure.sign_out')}
</MenuItem>
</Menu>
</div>
</li>
<li>
{`v${process.env.VERSION}`}
</li>
</ul>
</div>
</a>
</MenuItem>
<MenuItem onClick={handleLogout}>
{t('configure.sign_out')}
</MenuItem>
</Menu>
</div>
</li>
<li>
{`v${process.env.VERSION}`}
</li>
</ul>
</div>
</div>
</Header>
);
@@ -10,16 +10,18 @@
.logo span {
display: inline-block;
margin-left: 10px;
font-size: 18px;
font-size: 26px;
vertical-align: middle;
font-weight: 500;
color: white;
}
.logo {
background: #E5E5E5;
background: #696969;
height: 100%;
width: 128px;
z-index: 10;
border-right: 1px #757575 solid;
}
.base {
@@ -1,13 +1,7 @@
export const TOGGLE_MODAL = 'TOGGLE_MODAL';
export const SINGLE_VIEW = 'SINGLE_VIEW';
export const HIDE_SHORTCUTS_NOTE = 'HIDE_SHORTCUTS_NOTE';
export const VIEW_USER_DETAIL = 'VIEW_USER_DETAIL';
export const HIDE_USER_DETAIL = 'HIDE_USER_DETAIL';
export const SET_SORT_ORDER = 'MODERATION_SET_SORT_ORDER';
export const CHANGE_USER_DETAIL_STATUSES = 'CHANGE_USER_DETAIL_STATUSES';
export const SELECT_USER_DETAIL_COMMENT = 'SELECT_USER_DETAIL_COMMENT';
export const UNSELECT_USER_DETAIL_COMMENT = 'UNSELECT_USER_DETAIL_COMMENT';
export const CLEAR_USER_DETAIL_SELECTIONS = 'CLEAR_USER_DETAIL_SELECTIONS';
export const SHOW_STORY_SEARCH = 'SHOW_STORY_SEARCH';
export const HIDE_STORY_SEARCH = 'HIDE_STORY_SEARCH';
export const STORY_SEARCH_CHANGE_VALUE = 'STORY_SEARCH_CHANGE_VALUE';
@@ -0,0 +1,7 @@
export const VIEW_USER_DETAIL = 'VIEW_USER_DETAIL';
export const HIDE_USER_DETAIL = 'HIDE_USER_DETAIL';
export const CHANGE_USER_DETAIL_STATUSES = 'CHANGE_USER_DETAIL_STATUSES';
export const SELECT_USER_DETAIL_COMMENT = 'SELECT_USER_DETAIL_COMMENT';
export const UNSELECT_USER_DETAIL_COMMENT = 'UNSELECT_USER_DETAIL_COMMENT';
export const CLEAR_USER_DETAIL_SELECTIONS = 'CLEAR_USER_DETAIL_SELECTIONS';
+8 -9
View File
@@ -3,13 +3,13 @@ import {connect} from 'react-redux';
import Layout from '../components/ui/Layout';
import {fetchConfig} from '../actions/config';
import AdminLogin from '../components/AdminLogin';
import {logout} from 'coral-framework/actions/auth';
import {FullLoading} from '../components/FullLoading';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import {toggleModal as toggleShortcutModal} from '../actions/moderation';
import {checkLogin, handleLogin, requestPasswordReset} from '../actions/auth';
import {checkLogin, handleLogin, requestPasswordReset, logout} from '../actions/auth';
import {can} from 'coral-framework/services/perms';
import UserDetail from 'coral-admin/src/containers/UserDetail';
class LayoutContainer extends Component {
componentWillMount() {
@@ -55,9 +55,10 @@ class LayoutContainer extends Component {
toggleShortcutModal={toggleShortcutModal}
{...this.props}
>
<BanUserDialog />
<SuspendUserDialog />
{this.props.children}
<BanUserDialog />
<SuspendUserDialog />
<UserDetail />
{this.props.children}
</Layout>
);
} else if (loggedIn) {
@@ -72,10 +73,8 @@ class LayoutContainer extends Component {
}
const mapStateToProps = (state) => ({
auth: state.auth.toJS(),
TALK_RECAPTCHA_PUBLIC: state.config
.get('data')
.get('TALK_RECAPTCHA_PUBLIC', null)
auth: state.auth,
TALK_RECAPTCHA_PUBLIC: state.config.data.TALK_RECAPTCHA_PUBLIC,
});
const mapDispatchToProps = (dispatch) => ({
@@ -5,22 +5,24 @@ import SuspendUserDialog from '../components/SuspendUserDialog';
import {hideSuspendUserDialog} from '../actions/suspendUserDialog';
import {withSetCommentStatus, withSuspendUser} from 'coral-framework/graphql/mutations';
import {compose, gql} from 'react-apollo';
import * as notification from 'coral-admin/src/services/notification';
import t, {timeago} from 'coral-framework/services/i18n';
import withQuery from 'coral-framework/hocs/withQuery';
import {getErrorMessages} from 'coral-framework/utils';
import get from 'lodash/get';
import {notify} from 'coral-framework/actions/notification';
class SuspendUserDialogContainer extends Component {
suspendUser = async ({message, until}) => {
const {userId, username, commentStatus, commentId, hideSuspendUserDialog, setCommentStatus, suspendUser} = this.props;
const {userId, username, commentStatus, commentId, hideSuspendUserDialog, setCommentStatus, suspendUser, notify} = this.props;
hideSuspendUserDialog();
try {
const result = await suspendUser({id: userId, message, until});
if (result.data.suspendUser.errors) {
throw result.data.suspendUser.errors;
}
notification.success(
notify(
'success',
t('suspenduser.notify_suspend_until', username, timeago(until)),
);
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
@@ -33,7 +35,7 @@ class SuspendUserDialogContainer extends Component {
}
}
catch(err) {
notification.showMutationErrors(err);
notify('error', getErrorMessages(err));
}
};
@@ -52,8 +54,9 @@ class SuspendUserDialogContainer extends Component {
const withOrganizationName = withQuery(gql`
query CoralAdmin_SuspendUserDialog {
__typename
settings {
organizationName
organizationName
}
}
`);
@@ -69,6 +72,7 @@ const mapStateToProps = ({suspendUserDialog: {open, userId, username, commentId,
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
hideSuspendUserDialog,
notify,
}, dispatch),
});
@@ -0,0 +1,193 @@
import React from 'react';
import {compose, gql} from 'react-apollo';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import UserDetail from '../components/UserDetail';
import withQuery from 'coral-framework/hocs/withQuery';
import {getDefinitionName, getSlotFragmentSpreads} from 'coral-framework/utils';
import {
viewUserDetail,
hideUserDetail,
changeUserDetailStatuses,
clearUserDetailSelections,
toggleSelectCommentInUserDetail,
} from 'coral-admin/src/actions/userDetail';
import {withSetCommentStatus} from 'coral-framework/graphql/mutations';
import UserDetailComment from './UserDetailComment';
import update from 'immutability-helper';
const commentConnectionFragment = gql`
fragment CoralAdmin_Moderation_CommentConnection on CommentConnection {
nodes {
...${getDefinitionName(UserDetailComment.fragments.comment)}
}
hasNextPage
startCursor
endCursor
}
${UserDetailComment.fragments.comment}
`;
const slots = [
'userProfile',
];
class UserDetailContainer extends React.Component {
isLoadingMore = false;
// status can be 'ACCEPTED' or 'REJECTED'
bulkSetCommentStatus = (status) => {
const changes = this.props.selectedCommentIds.map((commentId) => {
return this.props.setCommentStatus({commentId, status});
});
Promise.all(changes).then(() => {
this.props.clearUserDetailSelections(); // un-select everything
});
}
bulkReject = () => {
this.bulkSetCommentStatus('REJECTED');
}
bulkAccept = () => {
this.bulkSetCommentStatus('ACCEPTED');
}
acceptComment = ({commentId}) => {
return this.props.setCommentStatus({commentId, status: 'ACCEPTED'});
}
rejectComment = ({commentId}) => {
return this.props.setCommentStatus({commentId, status: 'REJECTED'});
}
loadMore = () => {
if (this.isLoadingMore) {
return;
}
this.isLoadingMore = true;
const variables = {
limit: 10,
cursor: this.props.root.comments.endCursor,
author_id: this.props.data.variables.author_id,
statuses: this.props.data.variables.statuses,
};
this.props.data.fetchMore({
query: LOAD_MORE_QUERY,
variables,
updateQuery: (prev, {fetchMoreResult:{comments}}) => {
return update(prev, {
comments: {
nodes: {$push: comments.nodes},
hasNextPage: {$set: comments.hasNextPage},
startCursor: {$set: comments.startCursor},
endCursor: {$set: comments.endCursor},
},
});
}
})
.then(() => {
this.isLoadingMore = false;
})
.catch((err) => {
this.isLoadingMore = false;
throw err;
});
};
componentWillReceiveProps(next) {
if (this.props.userId === null && next.userId) {
next.data.refetch();
}
}
render () {
if (!this.props.userId) {
return null;
}
const loading = this.props.data.loading;
return <UserDetail
bulkReject={this.bulkReject}
bulkAccept={this.bulkAccept}
changeStatus={this.props.changeUserDetailStatuses}
toggleSelect={this.props.toggleSelectCommentInUserDetail}
acceptComment={this.acceptComment}
rejectComment={this.rejectComment}
loading={loading}
loadMore={this.loadMore}
{...this.props} />;
}
}
const LOAD_MORE_QUERY = gql`
query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Cursor, $author_id: ID!, $statuses: [COMMENT_STATUS!]) {
comments(query: {limit: $limit, cursor: $cursor, author_id: $author_id, statuses: $statuses}) {
...CoralAdmin_Moderation_CommentConnection
}
}
${commentConnectionFragment}
`;
export const withUserDetailQuery = withQuery(gql`
query CoralAdmin_UserDetail($author_id: ID!, $statuses: [COMMENT_STATUS!]) {
user(id: $author_id) {
id
username
created_at
profiles {
id
provider
}
reliable {
flagger
}
${getSlotFragmentSpreads(slots, 'user')}
}
totalComments: commentCount(query: {author_id: $author_id})
rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]})
comments: comments(query: {
author_id: $author_id,
statuses: $statuses
}) {
...CoralAdmin_Moderation_CommentConnection
}
${getSlotFragmentSpreads(slots, 'root')}
}
${commentConnectionFragment}
`, {
options: ({userId, statuses}) => {
return {
variables: {author_id: userId, statuses}
};
},
skip: (ownProps) => !ownProps.userId,
});
const mapStateToProps = (state) => ({
userId: state.userDetail.userId,
selectedCommentIds: state.userDetail.selectedCommentIds,
statuses: state.userDetail.statuses,
activeTab: state.userDetail.activeTab,
bannedWords: state.settings.wordlist.banned,
suspectWords: state.settings.wordlist.suspect,
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
changeUserDetailStatuses,
clearUserDetailSelections,
toggleSelectCommentInUserDetail,
viewUserDetail,
hideUserDetail,
}, dispatch)
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withUserDetailQuery,
withSetCommentStatus,
)(UserDetailContainer);
@@ -0,0 +1,40 @@
import {gql} from 'react-apollo';
import UserDetailComment from '../components/UserDetailComment';
import withFragments from 'coral-framework/hocs/withFragments';
export default withFragments({
comment: gql`
fragment CoralAdmin_UserDetail_comment on Comment {
id
body
created_at
status
hasParent
asset {
id
title
url
}
action_summaries {
count
... on FlagActionSummary {
reason
}
}
actions {
... on FlagAction {
id
reason
message
user {
id
username
}
}
}
editing {
edited
}
}
`
})(UserDetailComment);
+27 -7
View File
@@ -1,14 +1,34 @@
import {add} from 'coral-framework/services/graphqlRegistry';
import update from 'immutability-helper';
const extension = {
export default {
mutations: {
SetUserStatus: () => ({
refetchQueries: ['CoralAdmin_Community'],
SetUserStatus: ({variables: {status, userId}}) => ({
updateQueries: {
TalkAdmin_FlaggedAccounts: (prev) => {
if (status !== 'APPROVED') {
return prev;
}
const updated = update(prev, {
users: {
nodes: {$apply: (nodes) => nodes.filter((node) => node.id !== userId)},
},
});
return updated;
}
}
}),
RejectUsername: () => ({
refetchQueries: ['CoralAdmin_Community'],
RejectUsername: ({variables: {input: {id: userId}}}) => ({
updateQueries: {
TalkAdmin_FlaggedAccounts: (prev) => {
const updated = update(prev, {
users: {
nodes: {$apply: (nodes) => nodes.filter((node) => node.id !== userId)},
},
});
return updated;
}
}
}),
},
};
add(extension);
+22 -12
View File
@@ -1,24 +1,34 @@
import React from 'react';
import {render} from 'react-dom';
import {ApolloProvider} from 'react-apollo';
import smoothscroll from 'smoothscroll-polyfill';
import {getClient} from './services/client';
import store from './services/store';
import TalkProvider from 'coral-framework/components/TalkProvider';
import {createContext} from 'coral-framework/services/bootstrap';
import reducers from './reducers';
import App from './components/App';
import 'react-mdl/extra/material.js';
import './graphql';
import {loadPluginsTranslations, injectPluginsReducers} from 'coral-framework/helpers/plugins';
import graphqlExtension from './graphql';
import pluginsConfig from 'pluginsConfig';
import {toast} from 'react-toastify';
import {createNotificationService} from './services/notification';
import {hideShortcutsNote} from './actions/moderation';
function hidrateStore({store, storage}) {
if (storage && storage.getItem('coral:shortcutsNote') === 'hide') {
store.dispatch(hideShortcutsNote());
}
}
loadPluginsTranslations();
injectPluginsReducers();
smoothscroll.polyfill();
const notification = createNotificationService(toast);
const context = createContext({reducers, graphqlExtension, pluginsConfig, notification});
// hidrate Store with external data.
hidrateStore(context);
render(
<ApolloProvider client={getClient()} store={store}>
<TalkProvider {...context}>
<App />
</ApolloProvider>
</TalkProvider>
, document.querySelector('#root')
);
+28 -23
View File
@@ -1,35 +1,40 @@
import {Map, List, fromJS} from 'immutable';
import * as actions from '../constants/assets';
import update from 'immutability-helper';
const initialState = Map({
byId: Map(),
ids: List(),
assets: List()
});
const initialState = {
byId: {},
ids: [],
assets: []
};
export default function assets (state = initialState, action) {
switch (action.type) {
case actions.FETCH_ASSETS_SUCCESS:
return replaceAssets(action, state);
case actions.FETCH_ASSETS_SUCCESS: {
const assets = action.assets.reduce((prev, curr) => {
prev[curr.id] = curr;
return prev;
}, {});
return update(state, {
byId: {$set: assets},
count: {$set: action.count},
ids: {$set: Object.keys(assets)},
});
}
case actions.UPDATE_ASSET_STATE_REQUEST:
return state
.setIn(['byId', action.id, 'closedAt'], action.closedAt);
return update(state, {
byId: {
[action.id]: {
closedAt: {$set: action.closedAt},
},
},
});
case actions.UPDATE_ASSETS:
return state
.set('assets', List(action.assets));
return update(state, {
assets: {$set: action.assets},
});
default:
return state;
}
}
const replaceAssets = (action, state) => {
const assets = fromJS(action.assets.reduce((prev, curr) => {
prev[curr.id] = curr;
return prev;
}, {}));
return state
.set('byId', assets)
.set('count', action.count)
.set('ids', List(assets.keys()));
};
+40 -20
View File
@@ -1,43 +1,63 @@
import {Map} from 'immutable';
import * as actions from '../constants/auth';
const initialState = Map({
const initialState = {
loggedIn: false,
user: null,
loginError: null,
loginMaxExceeded: false,
passwordRequestSuccess: null
});
};
export default function auth (state = initialState, action) {
switch (action.type) {
case actions.CHECK_LOGIN_REQUEST:
return state
.set('loadingUser', true);
return {
...state,
loadingUser: true,
};
case actions.CHECK_LOGIN_FAILURE:
return state
.set('loggedIn', false)
.set('loadingUser', false)
.set('user', null);
return {
...state,
loggedIn: false,
loadingUser: false,
user: null,
};
case actions.CHECK_LOGIN_SUCCESS:
return state
.set('loggedIn', true)
.set('loadingUser', false)
.set('user', action.user);
return {
...state,
loggedIn: true,
loadingUser: false,
user: action.user,
};
case actions.LOGOUT:
return initialState;
case actions.LOGIN_SUCCESS:
return state.set('loginMaxExceeded', false).set('loginError', null);
return {
...state,
loginMaxExceeded: false,
loginError: null,
};
case actions.LOGIN_FAILURE:
return state.set('loginError', action.message);
return {
...state,
loginError: action.message,
};
case actions.FETCH_FORGOT_PASSWORD_REQUEST:
return state.set('passwordRequestSuccess', null);
return {
...state,
passwordRequestSuccess: null,
};
case actions.FETCH_FORGOT_PASSWORD_SUCCESS:
return state.set('passwordRequestSuccess', 'If you have a registered account, a password reset link was sent to that email.');
return {
...state,
passwordRequestSuccess: 'If you have a registered account, a password reset link was sent to that email.',
};
case actions.LOGIN_MAXIMUM_EXCEEDED:
return state
.set('loginMaxExceeded', true)
.set('loginError', action.message);
return {
...state,
loginMaxExceeded: true,
loginError: action.message,
};
default :
return state;
}
+57 -44
View File
@@ -1,5 +1,3 @@
import {Map} from 'immutable';
import {
FETCH_COMMENTERS_REQUEST,
FETCH_COMMENTERS_FAILURE,
@@ -13,8 +11,8 @@ import {
HIDE_REJECT_USERNAME_DIALOG
} from '../constants/community';
const initialState = Map({
community: Map(),
const initialState = {
community: {},
isFetchingPeople: false,
errorPeople: '',
accounts: [],
@@ -22,72 +20,87 @@ const initialState = Map({
ascPeople: false,
totalPagesPeople: 0,
pagePeople: 0,
user: Map({}),
user: {},
banDialog: false,
rejectUsernameDialog: false
});
};
export default function community (state = initialState, action) {
switch (action.type) {
case FETCH_COMMENTERS_REQUEST :
return state
.set('isFetchingPeople', true);
return {
...state,
isFetchingPeople: true,
};
case FETCH_COMMENTERS_FAILURE :
return state
.set('isFetchingPeople', false)
.set('errorPeople', action.error);
return {
...state,
isFetchingPeople: false,
errorPeople: action.error,
};
case FETCH_COMMENTERS_SUCCESS : {
const {accounts, type, page, count, limit, totalPages, ...rest} = action; // eslint-disable-line
return state
.merge({
isFetchingPeople: false,
errorPeople: '',
pagePeople: page,
countPeople: count,
limitPeople: limit,
totalPagesPeople: totalPages,
...rest
})
.set('accounts', accounts); // Sets to normal array
return {
...state,
isFetchingPeople: false,
errorPeople: '',
pagePeople: page,
countPeople: count,
limitPeople: limit,
totalPagesPeople: totalPages,
...rest,
accounts, // Sets to normal array
};
}
case SET_ROLE : {
const commenters = state.get('accounts');
const commenters = state.accounts;
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,
accounts: commenters.map((id) => id),
};
}
case SET_COMMENTER_STATUS: {
const commenters = state.get('accounts');
const commenters = state.accounts;
const idx = commenters.findIndex((el) => el.id === action.id);
commenters[idx].status = action.status;
return state.set('accounts', commenters.map((id) => id));
return {
...state,
accounts: commenters.map((id) => id),
};
}
case SORT_UPDATE :
return state
.set('fieldPeople', action.sort.field)
.set('ascPeople', !state.get('ascPeople'));
return {
...state,
fieldPeople: action.sort.field,
ascPeople: !state.ascPeople,
};
case HIDE_BANUSER_DIALOG:
return state
.set('banDialog', false);
return {
...state,
banDialog: false,
};
case SHOW_BANUSER_DIALOG:
return state
.merge({
user: Map(action.user),
banDialog: true
});
return {
...state,
user: action.user,
banDialog: true,
};
case HIDE_REJECT_USERNAME_DIALOG:
return state
.set('rejectUsernameDialog', false);
return {
...state,
rejectUsernameDialog: false,
};
case SHOW_REJECT_USERNAME_DIALOG:
return state
.merge({
user: Map(action.user),
rejectUsernameDialog: true
});
return {
...state,
user: action.user,
rejectUsernameDialog: true
};
default :
return state;
}
+7 -6
View File
@@ -1,15 +1,16 @@
import {Map} from 'immutable';
import * as actions from '../actions/config';
const initialState = Map({
data: Map({})
});
const initialState = {
data: {}
};
export default function config (state = initialState, action) {
switch (action.type) {
case actions.CONFIG_UPDATED:
return state.set('data', Map(action.data));
return {
...state,
data: action.data,
};
default:
return state;
}
+2
View File
@@ -7,11 +7,13 @@ import install from './install';
import config from './config';
import banUserDialog from './banUserDialog';
import suspendUserDialog from './suspendUserDialog';
import userDetail from './userDetail';
export default {
auth,
banUserDialog,
suspendUserDialog,
userDetail,
assets,
settings,
community,
+82 -49
View File
@@ -1,30 +1,29 @@
import {Map, List} from 'immutable';
import * as actions from '../constants/install';
import update from 'immutability-helper';
const initialState = Map({
const initialState = {
isLoading: false,
data: Map({
settings: Map({
data: {
settings: {
organizationName: '',
domains: Map({
whitelist: List()
})
}),
user: Map({
domains: {
whitelist: [],
}
},
user: {
username: '',
email: '',
password: '',
confirmPassword: ''
})
}),
errors: Map({
}
},
errors: {
organizationName: '',
username: '',
email: '',
password: '',
confirmPassword: ''
}),
},
showErrors: false,
hasError: false,
error: null,
@@ -44,57 +43,91 @@ const initialState = Map({
installRequest: null,
installRequestError: null,
alreadyInstalled: false
});
};
export default function install (state = initialState, action) {
switch (action.type) {
case actions.NEXT_STEP:
return state
.set('step', state.get('step') + 1);
return {
...state,
step: state.step + 1,
};
case actions.PREVIOUS_STEP:
return state
.set('step', state.get('step') - 1);
return {
...state,
step: state.step - 1,
};
case actions.GO_TO_STEP:
return state
.set('step', action.step);
return {
...state,
step: action.step,
};
case actions.UPDATE_PERMITTED_DOMAINS_SETTINGS:
return state
.setIn(['data', 'settings', 'domains', 'whitelist'], action.value);
return update(state, {
data: {
settings: {
domains: {
whitelist: {$set: action.value},
},
},
},
});
case actions.UPDATE_FORMDATA_SETTINGS:
return state
.setIn(['data', 'settings', action.name], action.value);
return update(state, {
data: {
settings: {
[action.name]: {$set: action.value},
},
},
});
case actions.UPDATE_FORMDATA_USER:
return state
.setIn(['data', 'user', action.name], action.value);
return update(state, {
data: {
user: {
[action.name]: {$set: action.value},
},
},
});
case actions.HAS_ERROR:
return state
.merge({
hasError: true,
showErrors: true
});
return {
...state,
hasError: true,
showErrors: true,
};
case actions.ADD_ERROR:
return state
.setIn(['errors', action.name], action.error);
return update(state, {
errors: {
[action.name]: {$set: action.error},
},
});
case actions.CLEAR_ERRORS:
return state
.set('errors', Map());
return {
...state,
errors: {},
};
case actions.INSTALL_REQUEST:
return state
.set('isLoading', true);
return {
...state,
isLoading: true,
};
case actions.INSTALL_SUCCESS:
return state
.set('isLoading', false)
.set('installRequest', 'SUCCESS');
return {
...state,
isLoading: false,
installRequest: 'SUCCESS',
};
case actions.INSTALL_FAILURE:
return state
.merge({
isLoading: false,
installRequest: 'FAILURE',
installRequestError: action.error
});
return {
...state,
isLoading: false,
installRequest: 'FAILURE',
installRequestError: action.error
};
case actions.CHECK_INSTALL_SUCCESS:
return state
.set('alreadyInstalled', action.installed);
return {
...state,
alreadyInstalled: action.installed,
};
default :
return state;
}
+37 -40
View File
@@ -1,60 +1,57 @@
import {fromJS, Set} from 'immutable';
import * as actions from '../constants/moderation';
const initialState = fromJS({
const initialState = {
singleView: false,
modalOpen: false,
userDetailId: null,
userDetailActiveTab: 'all',
userDetailStatuses: ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'],
userDetailSelectedIds: new Set(),
storySearchVisible: false,
storySearchString: '',
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
sortOrder: 'REVERSE_CHRONOLOGICAL',
});
shortcutsNoteVisible: 'show',
sortOrder: 'DESC',
};
export default function moderation (state = initialState, action) {
switch (action.type) {
case actions.MODERATION_CLEAR_STATE:
return initialState;
case actions.SET_ACTIVE_TAB:
return state
.set('activeTab', action.activeTab);
return {
...initialState,
shortcutsNoteVisible: state.shortcutsNoteVisible,
};
case actions.TOGGLE_MODAL:
return state
.set('modalOpen', action.open);
return {
...state,
modalOpen: action.open,
};
case actions.SINGLE_VIEW:
return state
.set('singleView', !state.get('singleView'));
return {
...state,
singleView: !state.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)
.update('userDetailSelectedIds', (set) => set.clear());
case actions.CLEAR_USER_DETAIL_SELECTIONS:
return state.update('userDetailSelectedIds', (set) => set.clear());
case actions.CHANGE_USER_DETAIL_STATUSES:
return state
.set('userDetailActiveTab', action.tab)
.set('userDetailStatuses', action.statuses);
case actions.SELECT_USER_DETAIL_COMMENT:
return state.update('userDetailSelectedIds', (set) => set.add(action.id));
case actions.UNSELECT_USER_DETAIL_COMMENT:
return state.update('userDetailSelectedIds', (set) => set.delete(action.id));
return {
...state,
shortcutsNoteVisible: 'hide',
};
case actions.SHOW_STORY_SEARCH:
return state.set('storySearchVisible', true);
return {
...state,
storySearchVisible: true,
};
case actions.HIDE_STORY_SEARCH:
return state.set('storySearchVisible', false);
return {
...state,
storySearchVisible: false,
};
case actions.STORY_SEARCH_CHANGE_VALUE:
return state.set('storySearchString', action.value);
return {
...state,
storySearchString: action.value,
};
case actions.SET_SORT_ORDER:
return state.set('sortOrder', action.order);
default :
return {
...state,
sortOrder: action.order,
};
default:
return state;
}
}
+51 -32
View File
@@ -1,5 +1,5 @@
import {Map, List} from 'immutable';
import * as actions from '../actions/settings';
import update from 'immutability-helper';
// this is initialized here because
// currently you have to reload the dashboard to get new stats
@@ -10,63 +10,82 @@ const DASHBOARD_WINDOW_MINUTES = 5;
let then = new Date();
then.setMinutes(then.getMinutes() - DASHBOARD_WINDOW_MINUTES);
const initialState = Map({
wordlist: Map({
banned: List(),
suspect: List()
}),
const initialState = {
wordlist: {
banned: [],
suspect: []
},
dashboardWindowStart: then.toISOString(),
dashboardWindowEnd: new Date().toISOString(),
domains: Map({
whitelist: List()
}),
domains: {
whitelist: []
},
saveSettingsError: null,
fetchSettingsError: null,
fetchingSettings: false
});
};
export default function settings (state = initialState, action) {
switch (action.type) {
case actions.SETTINGS_LOADING:
return state
.set('fetchingSettings', true)
.set('fetchSettingsError', null);
return {
...state,
fetchingSettings: true,
fetchSettingsError: null,
};
case actions.SETTINGS_RECEIVED:
return state.merge({
return {
...state,
fetchingSettings: false,
fetchSettingsError: null,
...action.settings
});
};
case actions.SETTINGS_FETCH_ERROR:
return state
.set('fetchingSettings', false)
.set('fetchSettingsError', action.error);
return {
...state,
fetchingSettings: false,
fetchSettingsError: action.error,
};
case actions.SETTINGS_UPDATED:
return state.merge({
return {
...state,
fetchingSettings: false,
fetchSettingsError: null,
...action.settings
});
};
case actions.SAVE_SETTINGS_LOADING:
return state
.set('fetchingSettings', true)
.set('saveSettingsError', null);
return {
...state,
fetchingSettings: true,
saveSettingsError: null,
};
case actions.SAVE_SETTINGS_SUCCESS:
return state.merge({
return {
...state,
fetchingSettings: false,
fetchSettingsError: null,
...action.settings
});
};
case actions.SAVE_SETTINGS_FAILED:
return state
.set('fetchingSettings', false)
.set('fetchSettingsError', action.error);
return {
...state,
fetchingSettings: false,
fetchSettingsError: action.error,
};
case actions.WORDLIST_UPDATED:
return state
.setIn(['wordlist', action.listName], action.list);
return update(state, {
wordlist: {
[action.listName]: {
$set: action.list
}
}
});
case actions.DOMAINLIST_UPDATED:
return state
.setIn(['domains', action.listName], action.list);
return update(state, {
domains: {
[action.listName]: {$set: action.list},
}
});
default:
return state;
}
@@ -0,0 +1,47 @@
import * as actions from '../constants/userDetail';
const initialState = {
userId: null,
activeTab: 'all',
statuses: ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'],
selectedCommentIds: [],
};
export default function banUserDialog(state = initialState, action) {
switch (action.type) {
case actions.VIEW_USER_DETAIL:
return {
...state,
userId: action.userId,
};
case actions.HIDE_USER_DETAIL:
return {
...state,
userId: null,
selectedCommentIds: [],
};
case actions.CLEAR_USER_DETAIL_SELECTIONS:
return {
...state,
selectedCommentIds: [],
};
case actions.CHANGE_USER_DETAIL_STATUSES:
return {
...state,
activeTab: action.tab,
statuses: action.statuses,
};
case actions.SELECT_USER_DETAIL_COMMENT:
return {
...state,
selectedCommentIds: [...state.selectedCommentIds, action.id],
};
case actions.UNSELECT_USER_DETAIL_COMMENT:
return {
...state,
selectedCommentIds: state.selectedCommentIds.filter((id) => id !== action.id),
};
default:
return state;
}
}
@@ -0,0 +1,4 @@
.actionButton {
transform: scale(.8);
margin: 0;
}
@@ -1,10 +1,11 @@
import React from 'react';
import styles from './Community.css';
import styles from './ActionButton.css';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../../Moderation/helpers/moderationQueueActionsMap';
import {menuActionsMap} from '../../../utils/moderationQueueActionsMap';
import t from 'coral-framework/services/i18n';
// TODO: Needs refactoring.
const ActionButton = ({type = '', user, ...props}) => {
return (
<Button
@@ -1,342 +0,0 @@
@custom-media --big-viewport (min-width: 780px);
.container {
padding: 10px;
display: flex;
padding-bottom: 200px;
}
.leftColumn {
padding: 42px 56px;
width: 234px;
}
.mainContent {
width: calc(100% - 300px);
padding: 34px 14px;
box-sizing: border-box;
}
.mainFlaggedContent {
width: 100%;
padding: 34px 14px;
box-sizing: border-box;
}
.roleButton {
display: block;
}
.searchBox {
width: 100%;
padding: 9px;
border: 1px solid #ccc;
border-radius: 2px;
display: flex;
background: white;
box-sizing: border-box;
height: 40px;
i {
color: #A1A1A1
}
input {
display: block;
width: 100%;
height: 100%;
border: none;
font-size: 16px;
padding: 0 2px 0 15px;
box-sizing: border-box;
}
}
.email {
display: block;
}
.dataTable {
width: 100%;
border-left: none;
border-right: none;
th {
font-size: 1.1em;
}
th:nth-child(2), th:nth-child(3) {
width: 100px;
}
}
.selectField {
position: relative;
width: 150px;
height: 36px;
background: #2c2c2c;
padding: 10px 15px;
box-sizing: border-box;
color: white;
border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
> div {
padding: 0;
}
i {
position: absolute;
top: 7px;
right: 7px;
}
input {
padding: 0;
font-size: 13px;
letter-spacing: 0.7px;
font-weight: 400;
}
&:hover {
cursor: pointer;
}
}
.list {
padding: 8px 0;
list-style: none;
display: block;
&.singleView .listItem {
display: none;
}
&.singleView .listItem.activeItem {
display: block;
height: 100%;
font-size: 1.5em;
line-height: 1.5em;
border: none;
.actions {
position: fixed;
bottom: 60px;
left: 25%;
margin: 0 auto;
display: flex;
justify-content: space-around;
width: 50%;
margin: 0;
}
.actionButton {
transform: scale(1.4);
}
}
}
.listItem {
border-bottom: 1px solid #e0e0e0;
font-size: 18px;
width: 100%;
max-width: 660px;
min-width: 400px;
margin: 0 auto;
position: relative;
transition: all 200ms;
padding: 10px 0 0;
min-height: 220px;
.container {
display: block;
padding: 0 14px;
min-height: 180px;
}
&:hover {
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
&:last-child {
border-bottom: none;
}
.context {
a {
color: #f36451;
text-decoration: underline;
float: right;
}
}
.sideActions {
position: absolute;
right: 0;
height: 100%;
top: 0;
padding: 50px 18px;
box-sizing: border-box;
}
.itemHeader {
display: flex;
align-items: center;
justify-content: space-between;
.author {
font-size: 16px;
font-weight: bold;
min-width: 230px;
display: flex;
align-items: center;
}
}
.itemBody {
display: flex;
justify-content: space-between;
}
.avatar {
margin-right: 16px;
height: 40px;
width: 40px;
border-radius: 50%;
background-color: #757575;
font-size: 40px;
color: #fff;
}
.created {
color: #666;
font-size: 13px;
margin-left: 40px;
}
.actionButton {
transform: scale(.8);
margin: 0;
}
.body {
margin-top: 0px;
flex: 1;
color: black;
max-width: 500px;
word-wrap: break-word;
}
.flagged {
color: rgba(255, 0, 0, .5);
padding-top: 15px;
padding-left: 10px;
}
.flagCount{
font-size: 12px;
color: #d32f2f;
}
}
.empty {
color: #444;
margin-top: 50px;
text-align: center;
}
@media (--big-viewport) {
.listItem {
border: 1px solid #e0e0e0;
margin-bottom: 30px;
&:last-child {
border-bottom: 1px solid #e0e0e0;
}
&.activeItem {
border: 2px solid #333;
}
}
}
.hasLinks {
color: #f00;
text-align: right;
display: flex;
align-items: center;
i {
margin-right: 5px;
}
}
.banned {
color: #f00;
text-align: left;
display: flex;
align-items: center;
i {
margin-right: 5px;
}
}
.ban {
display: block;
text-align: center;
margin-top: 5px;
}
.banButton {
width: 114px;
letter-spacing: 1px;
i {
vertical-align: middle;
margin-right: 10px;
font-size: 14px;
}
}
.actionButton {
transform: scale(.8);
margin: 0;
}
.flaggedByCount {
display: block;
text-align: left;
margin-top: 5px;
}
.flaggedByCount i {
font-size: 14px;
margin-right: 10px;
}
.flaggedBy {
display: inline;
padding: 3px;
font-size: 14px;
margin-left: 5px;
}
.flaggedByLabel {
font-weight: 600;
font-size: 14px;
}
.flaggedReasons {
margin-left: 24px;
margin-top: 10px;
}
p.flaggedByReason {
font-size: 1tpx;
margin: 0px;
line-height: 1.4;
}
@@ -2,7 +2,7 @@ import React, {Component} from 'react';
import CommunityMenu from './CommunityMenu';
import People from './People';
import FlaggedAccounts from './FlaggedAccounts';
import FlaggedAccounts from '../containers/FlaggedAccounts';
import RejectUsernameDialog from './RejectUsernameDialog';
export default class Community extends Component {
@@ -54,7 +54,7 @@ export default class Community extends Component {
}
getTabContent(searchValue, props) {
const {community, root: {users}} = props;
const {community} = props;
const activeTab = props.route.path === ':id' ? 'flagged' : props.route.path;
if (activeTab === 'people') {
@@ -76,15 +76,7 @@ export default class Community extends Component {
return (
<div>
<FlaggedAccounts
commenters={users}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
showRejectUsernameDialog={props.showRejectUsernameDialog}
approveUser={props.approveUser}
rejectUsername={props.rejectUsername}
currentUser={this.props.currentUser}
/>
<FlaggedAccounts />
<RejectUsernameDialog
open={community.rejectUsernameDialog}
handleClose={props.hideRejectUsernameDialog}
@@ -0,0 +1,21 @@
.tab {
flex: 1;
color: white;
text-transform: capitalize;
font-weight: 500;
font-size: 15px;
letter-spacing: 1px;
transition: border-bottom 200ms;
}
.tabBar {
background-color: rgba(44, 44, 44, 0.89);
z-index: 5;
}
.active {
color: white;
box-sizing: border-box;
border-bottom: solid 5px #F36451;
}
@@ -1,6 +1,6 @@
import React from 'react';
import styles from './styles.css';
import styles from './CommunityMenu.css';
import t from 'coral-framework/services/i18n';
import {Link} from 'react-router';
@@ -0,0 +1,34 @@
.container {
padding: 10px;
display: flex;
padding-bottom: 200px;
}
.mainFlaggedContent {
width: 100%;
padding: 34px 14px;
box-sizing: border-box;
}
.list {
margin: 0;
padding: 0;
}
.userLeave {
opacity: 1.0;
}
.userLeaveActive {
opacity: 0;
transition: opacity 800ms;
}
.userEnter {
opacity: 0;
}
.userEnterActive {
opacity: 1.0;
transition: opacity 800ms;
}
@@ -1,37 +1,74 @@
import React from 'react';
import t from 'coral-framework/services/i18n';
import styles from './Community.css';
import EmptyCard from 'coral-admin/src/components/EmptyCard';
import User from './User';
import LoadMore from '../../../components/LoadMore';
import FlaggedUser from '../containers/FlaggedUser';
import {CSSTransitionGroup} from 'react-transition-group';
import styles from './FlaggedAccounts.css';
const FlaggedAccounts = ({...props}) => {
const {commenters} = props;
const hasResults = commenters && !!commenters.length;
class FlaggedAccounts extends React.Component {
render() {
const {
users,
loadMore,
showBanUserDialog,
showSuspendUserDialog,
showRejectUsernameDialog,
approveUser,
me,
viewUserDetail,
} = this.props;
return (
<div className={styles.container}>
<div className={styles.mainFlaggedContent}>
{
hasResults
? commenters.map((commenter, index) => {
return <User
user={commenter}
key={index}
index={index}
modActionButtons={['APPROVE', 'REJECT']}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
showRejectUsernameDialog={props.showRejectUsernameDialog}
approveUser={props.approveUser}
currentUser={props.currentUser}
/>;
})
: <EmptyCard>{t('community.no_flagged_accounts')}</EmptyCard>
}
const hasResults = users.nodes && !!users.nodes.length;
return (
<div className={styles.container}>
<div className={styles.mainFlaggedContent}>
{
hasResults
? <CSSTransitionGroup
component={'ul'}
className={styles.list}
transitionName={{
enter: styles.userEnter,
enterActive: styles.userEnterActive,
leave: styles.userLeave,
leaveActive: styles.userLeaveActive,
}}
transitionEnter={true}
transitionLeave={true}
transitionEnterTimeout={1000}
transitionLeaveTimeout={1000}
>
{
users.nodes.map((user) => {
return (
<FlaggedUser
user={user}
key={user.id}
modActionButtons={['APPROVE', 'REJECT']}
showBanUserDialog={showBanUserDialog}
showSuspendUserDialog={showSuspendUserDialog}
showRejectUsernameDialog={showRejectUsernameDialog}
approveUser={approveUser}
me={me}
viewUserDetail={viewUserDetail}
/>
);
})
}
</CSSTransitionGroup>
: <EmptyCard>{t('community.no_flagged_accounts')}</EmptyCard>
}
<LoadMore
loadMore={loadMore}
showLoadMore={users.hasNextPage}
/>
</div>
</div>
</div>
);
};
);
}
}
export default FlaggedAccounts;
@@ -0,0 +1,104 @@
@custom-media --big-viewport (min-width: 780px);
.root {
composes: mdl-shadow--2dp mdl-card from global;
border-bottom: 1px solid #e0e0e0;
font-size: 18px;
width: 100%;
max-width: 660px;
min-width: 400px;
margin: 0 auto;
position: relative;
transition: box-shadow 200ms, margin-bottom 200ms;
padding: 10px 0 0;
min-height: 220px;
&:hover {
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
}
.rootSelected {
composes: mdl-shadow--16dp from global;
}
.container {
display: block;
padding: 0 14px;
min-height: 180px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
}
.author {
display: flex;
justify-content: flex-start;
}
.body {
display: flex;
align-items: center;
justify-content: space-between;
}
.flaggedByCount {
display: block;
text-align: left;
margin-top: 5px;
}
.flagIcon {
font-size: 14px;
margin-right: 10px;
}
.flaggedBy {
display: inline;
padding: 3px;
font-size: 14px;
margin-left: 5px;
}
.flaggedByLabel {
font-weight: 600;
font-size: 14px;
}
.flaggedReasons {
margin-left: 24px;
margin-top: 10px;
}
.flaggedByReason {
font-size: 1tpx;
margin: 0px;
line-height: 1.4;
}
@media (--big-viewport) {
.root {
border: 1px solid #e0e0e0;
margin-bottom: 30px;
&:last-child {
border-bottom: 1px solid #e0e0e0;
}
}
&.rootSelected {
border: 2px solid #333;
}
}
.button {
composes: buttonReset from 'coral-framework/styles/reset.css';
vertical-align: text-bottom;
&:hover {
background-color: #E0E0E0;
}
}
@@ -0,0 +1,145 @@
import React from 'react';
import styles from './FlaggedUser.css';
import ActionButton from './ActionButton';
// TODO: Should not rely on plugin.
import {username} from 'talk-plugin-flags/helpers/flagReasons';
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
import cn from 'classnames';
import t from 'coral-framework/services/i18n';
// TODO: Should work with custom flags too.
const shortReasons = {
[username.other]: t('community.other'),
[username.spam]: t('community.spam_ads'),
[username.offensive]: t('community.offensive'),
[username.nolike]: t('community.dont_like_username'),
[username.impersonating]: t('community.impersonating'),
};
// Render a single user for the list
class User extends React.Component {
showSuspenUserDialog = () => this.props.showSuspendUserDialog({
userId: this.props.user.id,
username: this.props.user.username,
});
showBanUserDialog = () => this.props.showBanUserDialog({
userId: this.props.user.id,
username: this.props.user.username,
});
viewAuthorDetail = () => this.props.viewUserDetail(this.props.user.id);
render() {
const {
user,
modActionButtons,
viewUserDetail,
selected,
approveUser,
showRejectUsernameDialog,
me,
className,
} = this.props;
return (
<li
tabIndex={0}
className={cn(className, styles.root, {[styles.rootSelected]: selected})}
>
<div className={styles.container}>
<div className={styles.header}>
<div className={styles.author}>
<button
onClick={this.viewAuthorDetail}
className={styles.button}>
{user.username}
</button>
{me.id !== user.id &&
<ActionsMenu icon="not_interested">
<ActionsMenuItem
disabled={user.status === 'BANNED'}
onClick={this.showSuspenUserDialog}>
Suspend User
</ActionsMenuItem>
<ActionsMenuItem
disabled={user.status === 'BANNED'}
onClick={this.showBanUserDialog}>
Ban User
</ActionsMenuItem>
</ActionsMenu>
}
</div>
</div>
<div className={styles.body}>
<div className={styles.flagged}>
<div className={styles.flaggedByCount}>
<i className={cn('material-icons', styles.flagIcon)}>flag</i>
<span className={styles.flaggedByLabel}>
{t('community.flags')}({ user.actions.length })
</span>:
{ user.action_summaries.map(
(action, i) => {
return <span className={styles.flaggedBy} key={i}>
{shortReasons[action.reason]} ({action.count})
</span>;
}
)}
</div>
<div className={styles.flaggedReasons}>
{ user.action_summaries.map(
(action_sum, i) => {
return <div key={i}>
<span className={styles.flaggedByLabel}>
{shortReasons[action_sum.reason]} ({action_sum.count})
</span>
{user.actions.map(
// find the action by action_sum.reason
(action, j) => {
if (action.reason === action_sum.reason) {
return <p className={styles.flaggedByReason} key={j}>
{action.user &&
<button onClick={() => {viewUserDetail(action.user.id);}} className={styles.button}>
{action.user.username}
</button>
}
: {action.message ? action.message : 'n/a'}
</p>;
}
return null;
}
)}
</div>;
}
)}
</div>
</div>
<div className={styles.sideActions}>
<div className={styles.actions}>
{modActionButtons.map((action, i) =>
<ActionButton key={i}
type={action.toUpperCase()}
user={user}
approveUser={approveUser}
showRejectUsernameDialog={showRejectUsernameDialog}
/>
)}
</div>
</div>
</div>
</div>
</li>
);
}
}
export default User;
@@ -1,6 +1,6 @@
import React from 'react';
import styles from './Community.css';
import styles from './styles.css';
import Table from '../containers/Table';
import {Pager, Icon} from 'coral-ui';
import EmptyCard from '../../../components/EmptyCard';
@@ -45,12 +45,12 @@ const People = ({commenters, searchValue, onSearchChange, ...props}) => {
<div className={styles.mainContent}>
{
hasResults
? <Table
? <Table
headers={tableHeaders}
commenters={commenters}
onHeaderClickHandler={props.onHeaderClickHandler}
/>
: <EmptyCard>{t('community.no_results')}</EmptyCard>
: <EmptyCard>{t('community.no_results')}</EmptyCard>
}
<Pager
totalPages={props.totalPages}
@@ -50,9 +50,9 @@ class RejectUsernameDialog extends Component {
const next = () => this.setState({stage: stage + 1});
const suspend = () => {
rejectUsername({id: user.user.id, message: this.state.email})
.then(() => {
this.props.handleClose();
});
.then(() => {
this.props.handleClose();
});
};
const suspendModalActions = [
@@ -71,21 +71,21 @@ class RejectUsernameDialog extends Component {
const {stage} = this.state;
return <Dialog
className={styles.suspendDialog}
id="rejectUsernameDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={t('reject_username.suspend_user')}>
<div className={styles.title}>
{t(stages[stage].title, t('reject_username.username'))}
</div>
<div className={styles.container}>
<div className={styles.description}>
{t(stages[stage].description, t('reject_username.username'))}
</div>
{
stage === 1 &&
className={styles.suspendDialog}
id="rejectUsernameDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={t('reject_username.suspend_user')}>
<div className={styles.title}>
{t(stages[stage].title, t('reject_username.username'))}
</div>
<div className={styles.container}>
<div className={styles.description}>
{t(stages[stage].description, t('reject_username.username'))}
</div>
{
stage === 1 &&
<div className={styles.writeContainer}>
<div className={styles.emailMessage}>{t('reject_username.write_message')}</div>
<div className={styles.emailContainer}>
@@ -96,16 +96,16 @@ class RejectUsernameDialog extends Component {
onChange={this.onEmailChange}/>
</div>
</div>
}
<div className={styles.modalButtons}>
{Object.keys(stages[stage].options).map((key, i) => (
<Button key={i} onClick={this.onActionClick(stage, i)}>
{t(stages[stage].options[key], t('reject_username.username'))}
</Button>
))}
</div>
</div>
</Dialog>;
}
<div className={styles.modalButtons}>
{Object.keys(stages[stage].options).map((key, i) => (
<Button key={i} onClick={this.onActionClick(stage, i)}>
{t(stages[stage].options[key], t('reject_username.username'))}
</Button>
))}
</div>
</div>
</Dialog>;
}
}
@@ -0,0 +1,58 @@
.dataTable {
width: 100%;
border-left: none;
border-right: none;
th {
font-size: 1.1em;
}
th:nth-child(2), th:nth-child(3) {
width: 100px;
}
}
.button {
composes: buttonReset from 'coral-framework/styles/reset.css';
&:hover {
background-color: #D0D0D0;
}
}
.email {
display: block;
}
.selectField {
position: relative;
width: 150px;
height: 36px;
background: #2c2c2c;
padding: 10px 15px;
box-sizing: border-box;
color: white;
border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
> div {
padding: 0;
}
i {
position: absolute;
top: 7px;
right: 7px;
}
input {
padding: 0;
font-size: 13px;
letter-spacing: 0.7px;
font-weight: 400;
}
&:hover {
cursor: pointer;
}
}
@@ -1,17 +1,17 @@
import React from 'react';
import {SelectField, Option} from 'react-mdl-selectfield';
import styles from '../components/Community.css';
import styles from '../components/Table.css';
import t from 'coral-framework/services/i18n';
export default ({headers, commenters, onHeaderClickHandler, onRoleChange, onCommenterStatusChange}) => (
export default ({headers, commenters, onHeaderClickHandler, onRoleChange, onCommenterStatusChange, viewUserDetail}) => (
<table className={`mdl-data-table ${styles.dataTable}`}>
<thead>
<tr>
{headers.map((header, i) =>(
<th
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
{header.title}
</th>
))}
@@ -21,7 +21,7 @@ export default ({headers, commenters, onHeaderClickHandler, onRoleChange, onComm
{commenters.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.username}
<button onClick={() => {viewUserDetail(row.id);}} className={styles.button}>{row.username}</button>
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
@@ -1,114 +0,0 @@
import React from 'react';
import styles from './Community.css';
import ActionButton from './ActionButton';
import {username} from 'coral-plugin-flags/helpers/flagReasons';
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
import t from 'coral-framework/services/i18n';
const shortReasons = {
[username.other]: t('community.other'),
[username.spam]: t('community.spam_ads'),
[username.offensive]: t('community.offensive'),
[username.nolike]: t('community.dont_like_username'),
[username.impersonating]: t('community.impersonating'),
};
// Render a single user for the list
const User = (props) => {
const {user, modActionButtons} = props;
let userStatus = user.status;
const showSuspenUserDialog = () => props.showSuspendUserDialog({
userId: user.id,
username: user.username,
});
const showBanUserDialog = () => props.showBanUserDialog({
userId: user.id,
username: user.username,
});
// Do not display unless the user status is 'pending' or 'banned'.
// This means that they have already been reviewed and approved.
return (userStatus === 'PENDING' || userStatus === 'BANNED') &&
<li tabIndex={props.index} className={`mdl-card ${props.selected ? 'mdl-shadow--8dp' : 'mdl-shadow--2dp'} ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.container}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span>
{user.username}
</span>
{props.currentUser.id !== user.id &&
<ActionsMenu icon="not_interested">
<ActionsMenuItem
disabled={user.status === 'BANNED'}
onClick={showSuspenUserDialog}>
Suspend User</ActionsMenuItem>
<ActionsMenuItem
disabled={user.status === 'BANNED'}
onClick={showBanUserDialog}>
Ban User
</ActionsMenuItem>
</ActionsMenu>
}
</div>
</div>
<div className={styles.itemBody}>
<div className={styles.body}>
<div className={styles.flaggedByCount}>
<i className="material-icons">flag</i><span className={styles.flaggedByLabel}>{t('community.flags')}({ user.actions.length })</span>:
{ user.action_summaries.map(
(action, i) => {
return <span className={styles.flaggedBy} key={i}>
{shortReasons[action.reason]} ({action.count})
</span>;
}
)}
</div>
<div className={styles.flaggedReasons}>
{ user.action_summaries.map(
(action_sum, i) => {
return <div key={i}>
<span className={styles.flaggedByLabel}>
{shortReasons[action_sum.reason]} ({action_sum.count})
</span>
{user.actions.map(
// find the action by action_sum.reason
(action, j) => {
if (action.reason === action_sum.reason) {
return <p className={styles.flaggedByReason} key={j}>
{action.user && action.user.username}: {action.message ? action.message : 'n/a'}
</p>;
}
return null;
}
)}
</div>;
}
)}
</div>
</div>
<div className={styles.sideActions}>
<div className={`actions ${styles.actions}`}>
{modActionButtons.map((action, i) =>
<ActionButton key={i}
type={action.toUpperCase()}
user={user}
approveUser={props.approveUser}
showRejectUsernameDialog={props.showRejectUsernameDialog}
/>
)}
</div>
</div>
</div>
</div>
</li>;
};
export default User;
@@ -1,119 +1,56 @@
/**
* deprecated as this file contains styles from multiple components. Please remove this file
* when styles have been refactored.
*/
@custom-media --big-viewport (min-width: 780px);
.listContainer {
max-width: 860px;
margin: 0 auto;
}
.tabBar {
background-color: rgba(44, 44, 44, 0.89);
z-index: 5;
}
.tab {
flex: 1;
color: white;
text-transform: capitalize;
font-weight: 500;
font-size: 15px;
letter-spacing: 1px;
transition: border-bottom 200ms;
}
.active {
color: white;
box-sizing: border-box;
border-bottom: solid 5px #F36451;
}
.active > span {
color: white;
}
.active:after {
background: transparent !important;
}
.showShortcuts {
position: absolute;
right: 130px;
.container {
padding: 10px;
display: flex;
align-items: center;
font-size: 13px;
span {
margin-left: 7px;
}
padding-bottom: 200px;
}
@media (--big-viewport) {
.tab {
flex: none;
}
.leftColumn {
padding: 42px 56px;
width: 234px;
}
.approve {
margin-top: 10px;
.mainContent {
width: calc(100% - 300px);
padding: 34px 14px;
box-sizing: border-box;
}
.notFound {
position: relative;
margin: 20px auto;
text-align: center;
padding: 68px 45px;
vertical-align: middle;
min-width: 500px;
a {
color: rgb(244, 126, 107);
font-weight: 500;
&.goToStreams {
position: absolute;
right: 10px;
bottom: 10px;
}
}
.roleButton {
display: block;
}
.header {
background-color: #2c2c2c;
color: white;
margin-bottom: -1px;
.searchBox {
width: 100%;
padding: 9px;
border: 1px solid #ccc;
border-radius: 2px;
display: flex;
background: white;
box-sizing: border-box;
height: 40px;
.settingsButton {
i {
vertical-align: middle;
margin-left: 10px;
margin-top: -4px;
}
i {
color: #A1A1A1
}
.moderateAsset {
a {
text-align: center;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
color: white;
text-transform: capitalize;
font-weight: 500;
font-size: 15px;
letter-spacing: 1px;
transition: opacity 200ms;
opacity: 1;
&:hover {
opacity: .8;
cursor: pointer;
}
}
input {
display: block;
width: 100%;
height: 100%;
border: none;
font-size: 16px;
padding: 0 2px 0 15px;
box-sizing: border-box;
}
}
@custom-media --big-viewport (min-width: 780px);
.list {
padding: 8px 0;
list-style: none;
@@ -134,15 +71,12 @@ span {
position: fixed;
bottom: 60px;
left: 25%;
margin: 0 auto;
display: flex;
justify-content: space-around;
width: 50%;
margin: 0;
}
.actionButton {
transform: scale(1.4);
}
}
}
@@ -159,6 +93,7 @@ span {
min-height: 220px;
.container {
display: block;
padding: 0 14px;
min-height: 180px;
}
@@ -184,9 +119,9 @@ span {
right: 0;
height: 100%;
top: 0;
padding: 40px 18px;
padding: 50px 18px;
box-sizing: border-box;
}
}
.itemHeader {
display: flex;
@@ -194,6 +129,8 @@ span {
justify-content: space-between;
.author {
font-size: 16px;
font-weight: bold;
min-width: 230px;
display: flex;
align-items: center;
@@ -221,18 +158,12 @@ span {
margin-left: 40px;
}
.actionButton {
transform: scale(.8);
margin: 0;
}
.body {
margin-top: 0px;
flex: 1;
color: black;
max-width: 500px;
word-wrap: break-word;
font-weight: 300;
}
.flagged {
@@ -245,11 +176,8 @@ span {
font-size: 12px;
color: #d32f2f;
}
}
.empty {
color: #444;
margin-top: 50px;
@@ -301,34 +229,14 @@ span {
margin-top: 5px;
}
.Comment {
.moderateArticle {
font-size: 12px;
a {
display: inline-block;
color: #679af3;
text-decoration: none;
font-size: 1em;
font-weight: 400;
letter-spacing: .5px;
font-size: 12px;
margin-left: 10px;
.banButton {
width: 114px;
letter-spacing: 1px;
&:hover {
text-decoration: underline;
opacity: .9;
cursor: pointer;
}
}
}
}
.flagBox {
max-width: 480px;
border-top: 1px solid rgba(66, 66, 66, 0.12);
h3 {
i {
vertical-align: middle;
margin-right: 10px;
font-size: 14px;
margin: 0;
font-weight: 500;
}
}
@@ -1,20 +1,13 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose, gql} from 'react-apollo';
import withQuery from 'coral-framework/hocs/withQuery';
import {Spinner} from 'coral-ui';
import {compose} from 'react-apollo';
import {withSetUserStatus, withRejectUsername} from 'coral-framework/graphql/mutations';
import {showBanUserDialog} from 'actions/banUserDialog';
import {showSuspendUserDialog} from 'actions/suspendUserDialog';
import {
fetchAccounts,
updateSorting,
newPage,
showRejectUsernameDialog,
hideRejectUsernameDialog
} from '../../../actions/community';
@@ -26,76 +19,19 @@ class CommunityContainer extends Component {
this.props.fetchAccounts({});
}
approveUser = ({userId}) => {
return this.props.setUserStatus({userId, status: 'APPROVED'});
}
banUser = ({userId}) => {
return this.props.setUserStatus({userId, status: 'BANNED'});
}
render() {
if (this.props.data.error) {
return <div>{this.props.data.error.message}</div>;
}
if (!('users' in this.props.root)) {
return <div><Spinner/></div>;
}
return (
<Community {...this.props} approveUser={this.approveUser} banUser={this.banUser}/>
<Community {...this.props} />
);
}
}
export const withCommunityQuery = withQuery(gql`
query CoralAdmin_Community($action_type: ACTION_TYPE) {
users(query:{action_type: $action_type}){
id
username
status
roles
actions{
id
created_at
... on FlagAction {
reason
message
user {
id
username
}
}
}
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
}
`, {
options: ({params: {action_type = 'FLAG'}}) => {
return {
variables: {
action_type: action_type
}
};
}
});
const mapStateToProps = (state) => ({
community: state.community.toJS(),
currentUser: state.auth.toJS().user,
community: state.community,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
fetchAccounts,
showBanUserDialog,
showSuspendUserDialog,
showRejectUsernameDialog,
hideRejectUsernameDialog,
updateSorting,
newPage,
@@ -103,7 +39,6 @@ const mapDispatchToProps = (dispatch) =>
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withCommunityQuery,
withSetUserStatus,
withRejectUsername,
)(CommunityContainer);
@@ -0,0 +1,128 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose, gql} from 'react-apollo';
import withQuery from 'coral-framework/hocs/withQuery';
import {Spinner} from 'coral-ui';
import {withSetUserStatus} from 'coral-framework/graphql/mutations';
import {showBanUserDialog} from 'actions/banUserDialog';
import {showSuspendUserDialog} from 'actions/suspendUserDialog';
import {showRejectUsernameDialog} from '../../../actions/community';
import {viewUserDetail} from '../../../actions/userDetail';
import {getDefinitionName} from 'coral-framework/utils';
import {appendNewNodes} from 'plugin-api/beta/client/utils';
import update from 'immutability-helper';
import FlaggedAccounts from '../components/FlaggedAccounts';
import FlaggedUser from '../containers/FlaggedUser';
class FlaggedAccountsContainer extends Component {
constructor(props) {
super(props);
}
approveUser = ({userId}) => {
return this.props.setUserStatus({userId, status: 'APPROVED'});
}
loadMore = () => {
return this.props.data.fetchMore({
query: LOAD_MORE_QUERY,
variables: {
limit: 5,
cursor: this.props.root.users.endCursor,
},
updateQuery: (previous, {fetchMoreResult:{users}}) => {
const updated = update(previous, {
users: {
nodes: {
$apply: (nodes) => appendNewNodes(nodes, users.nodes),
},
hasNextPage: {$set: users.hasNextPage},
endCursor: {$set: users.endCursor},
},
});
return updated;
},
});
};
render() {
if (this.props.data.error) {
return <div>{this.props.data.error.message}</div>;
}
if (this.props.data.loading) {
return <div><Spinner/></div>;
}
return (
<FlaggedAccounts
showBanUserDialog={this.props.showBanUserDialog}
showSuspendUserDialog={this.props.showSuspendUserDialog}
showRejectUsernameDialog={this.props.showRejectUsernameDialog}
viewUserDetail={this.props.viewUserDetail}
approveUser={this.approveUser}
loadMore={this.loadMore}
data={this.props.data}
root={this.props.root}
users={this.props.root.users}
me={this.props.root.me}
/>
);
}
}
const LOAD_MORE_QUERY = gql`
query TalkAdmin_LoadMoreFlaggedAccounts($limit: Int, $cursor: Cursor) {
users(query:{action_type: FLAG, statuses: [PENDING], limit: $limit, cursor: $cursor}){
hasNextPage
endCursor
nodes {
__typename
...${getDefinitionName(FlaggedUser.fragments.user)}
}
}
}
${FlaggedUser.fragments.user}
`;
export const withFlaggedAccountsyQuery = withQuery(gql`
query TalkAdmin_FlaggedAccounts {
...${getDefinitionName(FlaggedUser.fragments.root)}
users(query:{action_type: FLAG, statuses: [PENDING], limit: 10}){
hasNextPage
endCursor
nodes {
__typename
...${getDefinitionName(FlaggedUser.fragments.user)}
}
}
me {
__typename
...${getDefinitionName(FlaggedUser.fragments.me)}
}
}
${FlaggedUser.fragments.root}
${FlaggedUser.fragments.user}
${FlaggedUser.fragments.me}
`, {
options: {
fetchPolicy: 'network-only',
},
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
showBanUserDialog,
showSuspendUserDialog,
showRejectUsernameDialog,
viewUserDetail,
}, dispatch);
export default compose(
connect(null, mapDispatchToProps),
withFlaggedAccountsyQuery,
withSetUserStatus,
)(FlaggedAccountsContainer);
@@ -0,0 +1,42 @@
import {gql} from 'react-apollo';
import FlaggedUser from '../components/FlaggedUser';
import {withFragments} from 'plugin-api/beta/client/hocs';
export default withFragments({
root: gql`
fragment TalkAdminCommunity_FlaggedUser_root on RootQuery {
__typename
}
`,
me: gql`
fragment TalkAdminCommunity_FlaggedUser_me on User {
id
}
`,
user: gql`
fragment TalkAdminCommunity_FlaggedUser_user on User {
id
username
status
roles
actions{
id
created_at
... on FlagAction {
reason
message
user {
id
username
}
}
}
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
`
})(FlaggedUser);
@@ -4,6 +4,7 @@ import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import {setRole, setCommenterStatus} from '../../../actions/community';
import Table from '../components/Table';
import {viewUserDetail} from '../../../actions/userDetail';
class TableContainer extends Component {
@@ -22,13 +23,14 @@ class TableContainer extends Component {
}
const mapStateToProps = (state) => ({
commenters: state.community.get('accounts'),
commenters: state.community.accounts,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
setCommenterStatus,
setRole,
viewUserDetail,
}, dispatch);
export default compose(
@@ -1,3 +1,7 @@
/**
* @TODO: deprecated as this file contains styles from multiple components. Please refactor.
*/
.container {
display: flex;
@@ -111,20 +111,20 @@ export default class Configure extends Component {
(bool, error) => this.state.errors[error] ? false : bool, this.state.changed);
return (
<div className={styles.container}>
<div className={styles.leftColumn}>
<List onChange={this.changeSection} activeItem={activeSection}>
<Item itemId='stream' icon='speaker_notes'>
{t('configure.stream_settings')}
</Item>
<Item itemId='moderation' icon='thumbs_up_down'>
{t('configure.moderation_settings')}
</Item>
<Item itemId='tech' icon='code'>
{t('configure.tech_settings')}
</Item>
</List>
<div className={styles.saveBox}>
<div className={styles.container}>
<div className={styles.leftColumn}>
<List onChange={this.changeSection} activeItem={activeSection}>
<Item itemId='stream' icon='speaker_notes'>
{t('configure.stream_settings')}
</Item>
<Item itemId='moderation' icon='thumbs_up_down'>
{t('configure.moderation_settings')}
</Item>
<Item itemId='tech' icon='code'>
{t('configure.tech_settings')}
</Item>
</List>
<div className={styles.saveBox}>
{
showSave ?
<Button
@@ -136,25 +136,25 @@ export default class Configure extends Component {
>
{t('configure.save_changes')}
</Button>
:
:
<Button
raised
disabled
icon='check'
full
>
{t('configure.save_changes')}
</Button>
{t('configure.save_changes')}
</Button>
}
</div>
</div>
</div>
<div className={styles.mainContent}>
{ this.props.saveFetchingError }
{ this.props.fetchSettingsError }
{ section }
</div>
</div>
<div className={styles.mainContent}>
{ this.props.saveFetchingError }
{ this.props.fetchSettingsError }
{ section }
</div>
</div>
);
}
}
@@ -1,7 +1,9 @@
import React, {Component} from 'react';
import t from 'coral-framework/services/i18n';
import {join} from 'path';
import styles from './Configure.css';
import {Button, Card} from 'coral-ui';
import {BASE_URL} from 'coral-framework/constants/url';
class EmbedLink extends Component {
@@ -24,21 +26,14 @@ class EmbedLink extends Component {
}
render () {
const location = window.location;
const talkBaseUrl = [
location.protocol,
'//',
location.hostname,
location.port ? (`:${window.location.port}`) : ''
].join('');
const coralJsUrl = [talkBaseUrl, '/embed.js'].join('');
const coralJsUrl = join(BASE_URL, '/embed.js');
const nonce = String(Math.random()).slice(2);
const streamElementId = `coral_talk_${nonce}`;
const embedText = `
<div id="${streamElementId}"></div>
<script src="${coralJsUrl}" async onload="
Coral.Talk.render(document.getElementById('${streamElementId}'), {
talk: '${talkBaseUrl}'
talk: '${BASE_URL}'
});
"></script>
`.trim();
@@ -4,7 +4,7 @@ import t from 'coral-framework/services/i18n';
import styles from './Configure.css';
import {Checkbox, Textfield} from 'react-mdl';
import {Card, Icon, TextArea} from 'coral-ui';
import MarkdownEditor from 'coral-admin/src/components/MarkdownEditor';
import MarkdownEditor from 'coral-framework/components/MarkdownEditor';
const TIMESTAMPS = {
weeks: 60 * 60 * 24 * 7,
@@ -93,15 +93,15 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
value={settings.charCount}
disabled={settings.charCountEnable ? '' : 'disabled'}
/>
<span>{t('configure.comment_count_text_post')}</span>
{
errors.charCount &&
<span>{t('configure.comment_count_text_post')}</span>
{
errors.charCount &&
<span className={styles.settingsError}>
<br/>
<Icon name="error_outline"/>
{t('configure.comment_count_error')}
</span>
}
}
</p>
</div>
</Card>
@@ -23,8 +23,8 @@ class ConfigureContainer extends Component {
}
const mapStateToProps = (state) => ({
auth: state.auth.toJS(),
settings: state.settings.toJS()
auth: state.auth,
settings: state.settings
});
const mapDispatchToProps = (dispatch) =>
@@ -14,19 +14,19 @@ const ActivityWidget = ({assets}) => {
<div className={styles.widgetTable}>
{
assets.length
? assets.map((asset) => {
return (
<div className={styles.rowLinkify} key={asset.id}>
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
<p className={styles.widgetCount}>{asset.commentCount}</p>
<Link className={styles.linkToAsset} to={`${asset.url}#coralStreamEmbed_iframe`} target="_blank">
<p className={styles.assetTitle}>{asset.title}</p>
</Link>
<p className={styles.lede}>{asset.author} Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</div>
);
})
: <div className={styles.rowLinkify}>{t('dashboard.no_activity')}</div>
? assets.map((asset) => {
return (
<div className={styles.rowLinkify} key={asset.id}>
<Link className={styles.linkToModerate} to={`/admin/moderate/${asset.id}`}>Moderate</Link>
<p className={styles.widgetCount}>{asset.commentCount}</p>
<a className={styles.linkToAsset} href={`${asset.url}`} target="_blank">
<p className={styles.assetTitle}>{asset.title}</p>
</a>
<p className={styles.lede}>{asset.author} Published: {new Date(asset.created_at).toLocaleDateString()}</p>
</div>
);
})
: <div className={styles.rowLinkify}>{t('dashboard.no_activity')}</div>
}
</div>
</div>
@@ -5,17 +5,24 @@ import {Icon} from 'coral-ui';
import t from 'coral-framework/services/i18n';
const refreshIntervalSeconds = 60 * 5;
// TODO: refactor out storage code into redux.
class CountdownTimer extends React.Component {
static contextTypes = {
storage: PropTypes.object,
};
static propTypes = {
handleTimeout: PropTypes.func.isRequired
}
constructor (props) {
super(props);
constructor (props, context) {
super(props, context);
const {storage} = context;
try {
if (window.localStorage.getItem('coral:dashboardNote') === null) {
window.localStorage.setItem('coral:dashboardNote', 'show');
if (storage && storage.getItem('coral:dashboardNote') === null) {
storage.setItem('coral:dashboardNote', 'show');
}
} catch (e) {
@@ -24,7 +31,7 @@ class CountdownTimer extends React.Component {
this.state = {
secondsUntilRefresh: refreshIntervalSeconds,
dashboardNote: window.localStorage.getItem('coral:dashboardNote') || 'show'
dashboardNote: (storage && storage.getItem('coral:dashboardNote')) || 'show'
};
}
@@ -54,8 +61,11 @@ class CountdownTimer extends React.Component {
}
dismissNote = () => {
const {storage} = this.context;
try {
window.localStorage.setItem('coral:dashboardNote', 'hide');
if (storage) {
storage.setItem('coral:dashboardNote', 'hide');
}
} catch (e) {
// when setItem fails in Safari Private mode
@@ -64,7 +74,8 @@ class CountdownTimer extends React.Component {
}
render () {
const hideReloadNote = window.localStorage.getItem('coral:dashboardNote') === 'hide' ||
const {storage} = this.context;
const hideReloadNote = (storage && storage.getItem('coral:dashboardNote') === 'hide') ||
this.state.dashboardNote === 'hide'; // for Safari Incognito
return (
<p

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