Compare commits

...
784 Commits
Author SHA1 Message Date
Kim Gardner 1d986e7a0b Merge pull request #1003 from coralproject/fix-edited-marker
Fix styling of edited marker
2017-10-03 16:36:32 +01:00
Chi Vinh Le 79d312df50 Fix styling of edited marker 2017-10-03 22:25:37 +07:00
Kim Gardner eb969f8050 Merge pull request #999 from coralproject/subscriber-badge
Subscriber Badge Plugin and User Tags bug
2017-10-03 05:00:00 +01:00
Belen Curcio dde1f7b5be Assigned by removed 2017-10-02 16:26:21 -03:00
Belen Curcio 343d4418b0 eslint updated 2017-10-02 16:23:56 -03:00
Kim Gardner 4c28e43e62 Merge branch 'master' into subscriber-badge 2017-10-02 18:31:15 +01:00
Kim Gardner 1030ca1fa2 Merge pull request #1001 from coralproject/toxicity-detail
Revamp comment details and implement toxicity detail
2017-10-02 18:25:53 +01:00
Chi Vinh Le b1bc97c8d5 Improve styling 2017-10-02 22:34:12 +07:00
Chi Vinh Le 18b802c555 Merge branch 'master' into toxicity-detail 2017-10-02 22:30:49 +07:00
Chi Vinh Le 1c16a75f6b Merge branch 'master' into toxicity-detail
Conflicts:
	plugin-api/beta/client/components/index.js
2017-10-02 22:27:57 +07:00
Chi Vinh Le 8724cfe325 Add talk-plugin-flag-details as default plugins 2017-10-02 22:22:55 +07:00
Chi Vinh Le d3d7f909d8 Translations 2017-10-02 22:22:00 +07:00
Chi Vinh Le c2f22bbf98 Implement user flag details as slot component 2017-10-02 21:37:47 +07:00
Chi Vinh Le 9b39017f25 Export viewUserDetail in plugin-api 2017-10-02 20:54:49 +07:00
Chi Vinh Le 075d62a32a Move CommentDetails to plugin-api 2017-10-02 20:50:43 +07:00
Kim Gardner 2d92284622 Merge branch 'master' into subscriber-badge 2017-10-02 13:00:45 +01:00
Kim Gardner e99b79687f Merge pull request #1000 from coralproject/shorcuts
Admin Shortcuts working
2017-10-02 13:00:29 +01:00
Kim Gardner 9182d5b09c Merge branch 'master' into subscriber-badge 2017-10-02 11:59:21 +01:00
Kim Gardner 89b87b01df Merge branch 'master' into shorcuts 2017-10-02 11:58:07 +01:00
Kim Gardner 257f361998 Merge pull request #997 from vickychijwani/patch-1
Fix double preposition in string
2017-10-02 11:32:13 +01:00
Belen Curcio 02dff0c726 linting 2017-10-02 01:32:36 -03:00
Belen Curcio 17f5800363 Shorcuts working again! 2017-10-02 01:24:59 -03:00
Belen Curcio 7aaad6ccd9 Adds subscriber plugin 2017-10-02 01:06:08 -03:00
Belen Curcio 43cb0efe7e bug: usertags 2017-10-02 01:03:20 -03:00
Vicky Chijwani 06ced883ba Fix double preposition in French and Portuguese translations 2017-10-01 05:16:43 +05:30
Vicky Chijwani f7e6f67f4e Fix double preposition in string
This string is followed by a relative time duration with a preposition included, like "in 1 week".
2017-10-01 05:06:10 +05:30
Wyatt Johnson 307fe58cf3 Merge pull request #989 from coralproject/use-talk-linting-preset
Use eslint-config-talk preset
2017-09-29 11:21:06 -06:00
Wyatt Johnson 48f2cce893 Merge branch 'master' into use-talk-linting-preset 2017-09-29 11:02:18 -06:00
Wyatt Johnson b597f730f6 Merge pull request #996 from coralproject/new-docs
Updated docs
2017-09-29 11:02:06 -06:00
Chi Vinh Le d396f43fe3 Merge branch 'master' into use-talk-linting-preset
Conflicts:
	package.json
2017-09-29 23:55:33 +07:00
Chi Vinh Le 799a21fecd Upgrade eslint-config-talk 2017-09-29 23:52:41 +07:00
Chi Vinh Le 7d41421526 adminCommentMoreFlagDetails slot and more 2017-09-29 23:34:40 +07:00
Chi Vinh Le 9bd55b267e excludeIf now returns a new component 2017-09-29 23:33:37 +07:00
Chi Vinh Le 90470b8331 Make IfSlotIsEmpty and IfSloIsNotEmpty accept an array of slot names 2017-09-29 23:33:12 +07:00
Kim Gardner df737ad201 Merge branch 'master' into new-docs 2017-09-29 16:56:44 +01:00
Kim Gardner 09ee854302 Merge pull request #984 from ryanyun/add-class-names
introduce classes for easier CSS selection for custom styling
2017-09-29 16:56:03 +01:00
Wyatt Johnson f88bd8b485 Merge branch 'master' into new-docs 2017-09-29 08:59:36 -06:00
Ryan Yun 36ba7367d1 add talk-embed-stream- prefix to classes 2017-09-29 10:57:23 -04:00
Chi Vinh Le f9693c7154 Refactor and pluginize comment details 2017-09-29 19:42:44 +07:00
Kim Gardner 445107ef64 Merge branch 'master' into add-class-names 2017-09-29 13:36:12 +01:00
Kim Gardner 1c3be913d3 Merge pull request #981 from coralproject/bump-version-3-6-0
Bump version to 3.6.0
2017-09-29 13:35:31 +01:00
Kim Gardner 09f9782505 Merge branch 'master' into new-docs 2017-09-29 12:21:37 +01:00
Kim Gardner 1d6e9527e5 Merge branch 'master' into bump-version-3-6-0 2017-09-29 12:21:16 +01:00
Kim Gardner d0ce832319 Merge pull request #990 from coralproject/update-translation-pt
Add translation of "new" queue in Portuguese
2017-09-29 12:21:05 +01:00
Wyatt Johnson 2e7ee6bb8b Updated docs 2017-09-28 23:38:01 -06:00
Wyatt Johnson ad3958a2e0 Merge branch 'master' into update-translation-pt 2017-09-28 09:45:12 -06:00
Wyatt Johnson 58acdef043 Merge branch 'master' into bump-version-3-6-0 2017-09-28 09:44:09 -06:00
Ryan Yun 99aad0f74b Merge branch 'master' into add-class-names 2017-09-28 11:38:14 -04:00
Chi Vinh Le 5d718f6dd4 Only shw user flags in detail 2017-09-28 22:02:06 +07:00
Kim Gardner 44ce71ef14 Merge pull request #974 from coralproject/redis-cluster-support
Redis Cluster Support
2017-09-28 15:44:02 +01:00
Kim Gardner 47cde9a842 Merge branch 'master' into redis-cluster-support 2017-09-28 15:38:51 +01:00
Chi Vinh Le 219399a231 Refactor ComemntDetail and implement ToxicDetail 2017-09-27 22:50:45 +07:00
Ryan Yun 95114bc847 Merge branch 'add-class-names' of github.com:ryanyun/talk into add-class-names 2017-09-27 11:21:19 -04:00
Ryan Yun 12047d1ae1 fix typo for dynamic class names for like and respect buttons 2017-09-27 11:20:57 -04:00
Kim Gardner 0362dad14b Merge branch 'master' into add-class-names 2017-09-27 16:06:21 +01:00
Kim Gardner 97b81cc1cc Merge pull request #991 from coralproject/featured-comment-content-slot
Use commentContent slot in Featured Comments
2017-09-27 16:05:51 +01:00
Kim Gardner 8d6b9dd23c Merge branch 'master' into featured-comment-content-slot 2017-09-27 10:54:37 +01:00
Kim Gardner 9adf11e1d1 Merge branch 'master' into update-translation-pt 2017-09-27 10:53:52 +01:00
Kim Gardner ef49d9a3d2 Merge pull request #988 from coralproject/password-reset-fix
Password reset fixes
2017-09-26 17:42:06 +01:00
Wyatt Johnson 921eea59c8 Merge branch 'master' into password-reset-fix 2017-09-26 10:36:47 -06:00
Kim Gardner c5cd88f4c0 Merge pull request #986 from coralproject/cleanup-dependencies
Cleanup dependencies
2017-09-26 17:25:10 +01:00
Kim Gardner 762f4f7898 Merge branch 'master' into cleanup-dependencies 2017-09-26 17:15:13 +01:00
Kiwi e400ae37f4 Merge branch 'master' into password-reset-fix 2017-09-26 18:14:20 +02:00
Kiwi 45b0ec48de Merge branch 'master' into featured-comment-content-slot 2017-09-26 18:11:21 +02:00
Kim Gardner f3f8c6b24e Merge pull request #985 from coralproject/mod-indicator
Moderate Indicator
2017-09-26 17:03:34 +01:00
Belen Curcio baa5bddd19 Merge branch 'mod-indicator' of github.com:coralproject/talk into mod-indicator 2017-09-26 12:43:05 -03:00
Belen Curcio bea7d75db7 Polling to update the Header Indicator 2017-09-26 12:42:39 -03:00
Belén Curcio 8e5edd65c7 Merge branch 'master' into mod-indicator 2017-09-26 12:36:59 -03:00
Wyatt Johnson 9ae59ef998 added parent url to the login popup 2017-09-26 09:35:33 -06:00
Chi Vinh Le 0f382bd742 Use commentContent slot in Featured Comments 2017-09-26 22:31:54 +07:00
Kim Gardner 55b1f4831a Add translation of "new" queue in Portuguese 2017-09-26 16:30:09 +01:00
Wyatt Johnson 3d2575413d Merge branch 'master' into password-reset-fix 2017-09-26 09:07:07 -06:00
Kiwi c40393347a Merge branch 'master' into use-talk-linting-preset 2017-09-26 14:39:25 +02:00
Chi Vinh Le 8c05e3df87 Use eslint-config-talk preset 2017-09-26 19:35:24 +07:00
Kim Gardner fa4adda61e Merge branch 'master' into redis-cluster-support 2017-09-26 12:26:24 +01:00
Kim Gardner ea06b334f6 Merge pull request #987 from coralproject/fix-contains-link-styling
Fix contains links styling
2017-09-26 12:25:30 +01:00
Wyatt Johnson 203d3c7ee9 bug fixes 2017-09-25 16:12:40 -06:00
Wyatt Johnson 825e1b5e5c Fixes to password reset flow 2017-09-25 14:50:33 -06:00
Chi Vinh Le 3db406bf26 Rename Community Query 2017-09-26 03:31:04 +07:00
Belén Curcio c5e078f1e1 Merge branch 'master' into mod-indicator 2017-09-25 13:49:56 -03:00
Belen Curcio e6f18d8558 Updates 2017-09-25 13:49:26 -03:00
Ryan Yun 76f259a2aa Merge branch 'master' into add-class-names 2017-09-25 12:34:20 -04:00
Ryan Yun 3f90f72bee update classes to follow talk naming convention 2017-09-25 12:33:59 -04:00
Chi Vinh Le f211bdf34e Fix contains links styling 2017-09-25 23:20:32 +07:00
Kiwi d2e5633d91 Merge branch 'master' into cleanup-dependencies 2017-09-25 18:20:07 +02:00
Kiwi b01e919029 Merge pull request #983 from coralproject/modqueue-live-new-comments
Modqueue live new comments
2017-09-25 18:19:46 +02:00
Wyatt Johnson 62b407179f Merge branch 'master' into redis-cluster-support 2017-09-25 10:10:09 -06:00
Belén Curcio 536ef70415 Merge branch 'master' into mod-indicator 2017-09-25 12:50:29 -03:00
Chi Vinh Le 981c319810 Clean dependencies 2017-09-25 21:34:35 +07:00
Belen Curcio 19e23479ee Showing the indicators 2017-09-22 17:45:28 -03:00
Belen Curcio 86687f10d8 Working indicators 2017-09-22 17:38:46 -03:00
Belen Curcio 3b6bcba055 Indicator for Community 2017-09-22 17:25:59 -03:00
Belen Curcio acdfaeee9f Moving the initial query up 2017-09-22 16:58:06 -03:00
Chi Vinh Le 792d0b9a2d Add comments 2017-09-23 02:18:45 +07:00
Chi Vinh Le 9be2379f30 Merge branch 'modqueue-live-new-comments' of ssh://github.com/coralproject/talk into modqueue-live-new-comments 2017-09-23 02:03:49 +07:00
Chi Vinh Le 1658b2594b PropType fixes 2017-09-23 02:03:34 +07:00
Kiwi d9e2fba569 Merge branch 'master' into modqueue-live-new-comments 2017-09-22 20:59:04 +02:00
Chi Vinh Le d9cabeafe2 Add translation and style fixes 2017-09-23 01:57:20 +07:00
Chi Vinh Le 171a6d506b Add proptypes 2017-09-23 01:09:32 +07:00
Chi Vinh Le a19de7bfb4 Don't scroll smooth when switching view mode 2017-09-23 00:53:27 +07:00
Ryan Yun 6138768588 add class names to moderation link 2017-09-22 12:34:27 -04:00
Ryan Yun 4c5f8b3359 add class name to submit button on configure comment stream form 2017-09-22 12:31:55 -04:00
Ryan Yun 605c87a799 featured and all comments have same left and right action container class names 2017-09-22 12:16:31 -04:00
Chi Vinh Le 1e0451ceba More styling refactor 2017-09-22 22:58:49 +07:00
Ryan Yun d60b869937 add class name to featured tag 2017-09-22 11:48:04 -04:00
Chi Vinh Le 9f4c97e138 Improve single view, keyboard navigation and refactor styles 2017-09-22 22:42:28 +07:00
Ryan Yun 66518f50dd add class name to off topic tag 2017-09-22 11:41:54 -04:00
Ryan Yun 7328926ad7 link button needs a span tag around button label 2017-09-22 11:20:06 -04:00
Ryan Yun a736c1620e add static class names for respected and liked buttons 2017-09-22 11:16:56 -04:00
Ryan Yun d359d915fe add class name for configure container 2017-09-22 11:11:22 -04:00
Ryan Yun 235b2e1068 add class name for my profile container 2017-09-22 11:10:39 -04:00
Kim Gardner f183aeb748 Merge pull request #979 from coralproject/pluginize-timestamp
Allow replacing comment timestamp
2017-09-22 14:24:15 +01:00
Chi Vinh Le 143adc7c6f Live update modqueue on new comments 2017-09-21 23:39:04 +07:00
Kim Gardner 91714705ea Merge branch 'master' into bump-version-3-6-0 2017-09-21 14:20:38 +01:00
Kim Gardner 64cc85eb24 Merge branch 'master' into pluginize-timestamp 2017-09-21 14:20:22 +01:00
Kim Gardner 7a043dac5b Merge pull request #982 from coralproject/remove-count
Remove count from Replies button and Change copy from Load More button
2017-09-21 14:19:57 +01:00
Belen Curcio edd3e7f6fe More copuy 2017-09-21 09:20:15 -03:00
Belen Curcio 3ef9bd5e52 Removing Count from replies button 2017-09-21 09:14:02 -03:00
Kim Gardner b60196e455 Bump version 2017-09-21 12:42:45 +01:00
Kim Gardner d315458e0e Merge branch 'master' into pluginize-timestamp 2017-09-21 12:41:42 +01:00
Wyatt Johnson abdb4cbe3e Merge pull request #980 from coralproject/version-bump
Update package.json
2017-09-20 16:27:33 -06:00
Wyatt Johnson 20380cd62f Update package.json 2017-09-20 16:23:07 -06:00
Wyatt Johnson 271f0cc237 Merge branch 'master' into redis-cluster-support 2017-09-20 13:54:41 -06:00
Kiwi 864aa2be28 Merge branch 'master' into pluginize-timestamp 2017-09-20 20:51:25 +02:00
Wyatt Johnson 4c8894d6e9 Merge pull request #978 from coralproject/fix-userdetail-all-tab
Fix empty user detail all tab
2017-09-20 12:48:46 -06:00
Chi Vinh Le dd5d06aa95 Request status=null 2017-09-21 01:43:04 +07:00
Chi Vinh Le 7e536cfb31 Rename PubDate to CommentTimestamp 2017-09-21 01:37:57 +07:00
Chi Vinh Le 5fa4b4d734 Remove styling that affects the slot.. 2017-09-21 01:24:59 +07:00
Chi Vinh Le e7c78a2465 Pluginize timestamp 2017-09-20 22:53:04 +07:00
Kim Gardner 95fadbd05a Merge branch 'master' into redis-cluster-support 2017-09-20 16:28:08 +01:00
Wyatt Johnson 525da57419 Merge pull request #971 from coralproject/new-flags
Flags: Implement Labels, bugfixes and improvements
2017-09-19 17:17:50 -06:00
Wyatt Johnson 4913eda974 Merge branch 'master' into new-flags 2017-09-19 17:12:56 -06:00
Wyatt Johnson cee247f065 Merge pull request #965 from coralproject/flagged-count
Count for Flagged Usernames
2017-09-19 17:12:41 -06:00
Chi Vinh Le 961111c3f7 Allow priviledged user to query for all statuses 2017-09-20 04:36:58 +07:00
Chi Vinh Le 2d96163b76 Disallow querying for all comments unpriviledgly 2017-09-20 04:35:14 +07:00
Chi Vinh Le c3e24a0451 Apply suggenstions 2017-09-20 04:30:29 +07:00
Wyatt Johnson 6d04b4add3 Redis Cluster Support
- Deprecated unused `redis` options in favour of options from `ioredis`.
- Added support for ioredis Cluster.
- Enabled more extended redis client configuration.
2017-09-19 15:28:03 -06:00
Chi Vinh Le 42954fb84a Return all actions in CreateCommentResponse 2017-09-20 04:17:15 +07:00
Chi Vinh Le 5f7c4f82e7 Return flags when creating comment, restrict access to actions in comment resolver 2017-09-20 02:50:43 +07:00
Chi Vinh Le b482976c58 Allow querying for all statuses 2017-09-20 02:39:31 +07:00
Chi Vinh Le 6e89b8c1aa Merge branch 'missing-comments' into new-flags 2017-09-19 23:53:46 +07:00
Chi Vinh Le e046a9652d Fix SYSTEM_WITHHELD comments missing in all queue and user detail 2017-09-19 23:52:25 +07:00
Belen Curcio 7a74d5ee83 missing prop 2017-09-19 13:26:04 -03:00
Belen Curcio b9086244e2 More proptypes 2017-09-19 13:24:36 -03:00
Chi Vinh Le 7a4aae93b4 Introduce adminCommentLabels and implement Toxic comment label 2017-09-19 23:14:34 +07:00
Belen Curcio a5350c44ae Adding PropTypes 2017-09-19 13:14:20 -03:00
Belen Curcio a211f94a91 Adding status to the FE Query 2017-09-19 13:11:23 -03:00
Belen Curcio bccad5585e Adding statuses 2017-09-19 13:10:35 -03:00
Belen Curcio b3cb99925d Adding PropTypes 2017-09-19 13:10:14 -03:00
Belen Curcio a96d8c2b3c Copy, removing log, and adding PropTypes 2017-09-19 12:45:47 -03:00
Chi Vinh Le b0219948a0 Added core labels 2017-09-19 22:29:51 +07:00
Chi Vinh Le 563a6d23dd First pass adding labels 2017-09-19 21:43:58 +07:00
Belen Curcio bc5d348c98 Merge branch 'flagged-count' of github.com:coralproject/talk into flagged-count 2017-09-19 11:43:31 -03:00
Belen Curcio 818ad07eac lint fixes 2017-09-19 11:43:10 -03:00
Belén Curcio b8f6ab9237 Merge branch 'master' into flagged-count 2017-09-19 11:15:11 -03:00
Belen Curcio f1f9868028 Cleaning console.logs 2017-09-19 11:13:06 -03:00
Chi Vinh Le b5deac7b48 Bugfixes and show different notification for toxic comments
Bugfixes:
- Toxic comment flag was not added
- GraphQL warnings for missing fields
2017-09-19 19:20:38 +07:00
Kim Gardner ae430245ec Merge pull request #970 from coralproject/default-in-stream-mod
Make in-stream moderation a default plugin
2017-09-19 11:47:25 +01:00
Kim Gardner ebe7ccfd10 Make in-stream moderation a default plugin 2017-09-19 11:09:22 +01:00
Kim Gardner f00e8dc335 Merge branch 'master' into flagged-count 2017-09-19 09:44:33 +01:00
Kim Gardner 262213a520 Merge pull request #968 from coralproject/all-comments-translation
Replace hard-coded string on all comments tab with translation
2017-09-19 09:41:03 +01:00
Kim Gardner 00f98520f7 Merge branch 'master' into flagged-count 2017-09-19 09:12:15 +01:00
Kim Gardner 0dfcbc9927 Merge branch 'master' into all-comments-translation 2017-09-19 09:11:44 +01:00
Kim Gardner a68e1c9c0b Merge pull request #969 from coralproject/helmet-config
Expose Helmet configuration
2017-09-19 09:11:14 +01:00
Belen Curcio 75635aff2b Merge branch 'flagged-count' of github.com:coralproject/talk into flagged-count 2017-09-18 15:52:06 -03:00
Belen Curcio ab2e036047 withFragments and parent withQuery added 2017-09-18 15:49:23 -03:00
Wyatt Johnson 52c1989780 Expose Helmet configuration
- Fixes #962
2017-09-18 11:57:57 -06:00
Kiwi abd458ab82 Merge branch 'master' into all-comments-translation 2017-09-18 19:48:09 +02:00
Chi Vinh Le 41afe9fae7 Replace hard-coded string on all comments tab with translation 2017-09-19 00:41:38 +07:00
Kim Gardner e6fa0a6d12 Merge pull request #950 from coralproject/fix-notify-bugs
Fix error notification bugs
2017-09-18 18:38:09 +01:00
Belén Curcio a0fb6030ca Merge branch 'master' into fix-notify-bugs 2017-09-18 14:27:21 -03:00
Kim Gardner c95d0e45f8 Merge pull request #952 from coralproject/instream-ban
In-Stream Ban
2017-09-18 18:26:59 +01:00
Belén Curcio 7144f171ef Merge branch 'master' into fix-notify-bugs 2017-09-18 14:20:33 -03:00
Kim Gardner 23e1b56c6e Merge branch 'master' into instream-ban 2017-09-18 18:19:53 +01:00
Wyatt Johnson 9b999c6249 Merge pull request #967 from coralproject/fix-safari-rendering-issues
Fix safari rendering issues
2017-09-18 11:19:32 -06:00
Kiwi 7a81a125f5 Merge branch 'master' into fix-safari-rendering-issues 2017-09-18 19:14:07 +02:00
Belén Curcio 79cdd65e30 Merge branch 'master' into flagged-count 2017-09-18 14:12:23 -03:00
Chi Vinh Le ed22bc34fc Add a comment 2017-09-19 00:11:49 +07:00
Belen Curcio d7ea88c367 matching master 2017-09-18 14:10:44 -03:00
Belen Curcio afec9932f0 Merge branch 'master' of github.com:coralproject/talk into instream-ban 2017-09-18 14:08:41 -03:00
Chi Vinh Le 2c297d7c2f Promote modqueue comment into own layer to fix safari issues 2017-09-19 00:08:09 +07:00
Kim Gardner 2814070808 Merge pull request #955 from coralproject/static-url
Added support for changing the static url
2017-09-18 18:02:40 +01:00
Kim Gardner 6ed6726daf Merge branch 'master' into static-url 2017-09-18 17:57:49 +01:00
Kim Gardner 21936ae467 Merge pull request #963 from coralproject/react-linting-rules
Linting pass
2017-09-18 17:57:17 +01:00
Kim Gardner 613a7f7073 Merge branch 'master' into static-url 2017-09-18 17:56:38 +01:00
Kim Gardner 9258347886 Merge branch 'master' into instream-ban 2017-09-18 17:56:15 +01:00
Belén Curcio 959193f8b5 Merge branch 'master' into flagged-count 2017-09-18 13:19:41 -03:00
Wyatt Johnson 212c6cd0ce Merge branch 'master' into react-linting-rules 2017-09-18 10:02:49 -06:00
Wyatt Johnson 5f3931f34b Merge pull request #966 from coralproject/toxic-comment-group-id
Turn toxic comment group_id into a constant
2017-09-18 09:49:22 -06:00
Belen Curcio aa3327945e Styles and copy 2017-09-18 12:42:21 -03:00
Chi Vinh Le c8dc5d4cd9 Turn toxic comment group_id into a constant 2017-09-18 22:39:30 +07:00
Wyatt Johnson 11c46439a2 corrected view error 2017-09-18 09:19:52 -06:00
Belen Curcio a07d661655 Adding CountBadge and flaggedUsernamesCount 2017-09-18 11:53:52 -03:00
Belen Curcio 24391de61d userCount 2017-09-18 11:06:31 -03:00
Chi Vinh Le 33822ff6c0 More linting error fixes 2017-09-18 20:38:32 +07:00
Belen Curcio 5d153e764d Linting 2017-09-18 10:30:39 -03:00
Belen Curcio 597d67f5d2 Actions renamed 2017-09-18 10:28:02 -03:00
Belen Curcio 100dd519ee Replacing excludeIf 2017-09-18 10:12:28 -03:00
Belen Curcio eb03d62e51 Merge branch 'instream-ban' of github.com:coralproject/talk into instream-ban 2017-09-18 10:05:12 -03:00
Belen Curcio 8bfc7e942c Renaming 2017-09-18 10:04:39 -03:00
Chi Vinh Le 1a349eb0cd Turn on prop-types linter 2017-09-18 19:03:07 +07:00
Chi Vinh Le f19b333537 Fix some linter warnings 2017-09-18 18:56:52 +07:00
Kim Gardner c6c2d545f8 Merge branch 'master' into static-url 2017-09-18 12:55:28 +01:00
Kim Gardner ebe9945492 Merge branch 'master' into instream-ban 2017-09-18 12:54:54 +01:00
Kim Gardner c6403b2da3 Merge branch 'master' into fix-notify-bugs 2017-09-18 12:54:46 +01:00
Wyatt Johnson cd100a23cb Merge pull request #932 from pdehaan/eslint-react
Add react/recommended ESLint rules
2017-09-15 17:29:28 -06:00
Wyatt Johnson 25761f9e61 Merge branch 'master' into eslint-react 2017-09-15 17:22:51 -06:00
Wyatt Johnson 1a76a83806 Merge pull request #959 from coralproject/view-context-no-tombstone
Don't show tombstone during "view context"
2017-09-15 17:20:29 -06:00
Wyatt Johnson 55ebd1df8e Merge branch 'master' into view-context-no-tombstone 2017-09-15 17:16:07 -06:00
Wyatt Johnson c427691bef Merge pull request #958 from coralproject/detect-n-display-stream-errors
Detect and display stream errors
2017-09-15 17:15:34 -06:00
Wyatt Johnson 93afd793cb Merge branch 'master' into detect-n-display-stream-errors 2017-09-15 17:10:46 -06:00
Wyatt Johnson 35232cf015 Merge pull request #957 from coralproject/fix-config
Fix config parsing bug
2017-09-15 16:56:24 -06:00
Wyatt Johnson f864ebf2e3 Merge branch 'master' into fix-config 2017-09-15 15:56:46 -06:00
Wyatt Johnson a9cc78b61d Merge pull request #943 from coralproject/story/150848457
Added support for deep comment counts
2017-09-15 15:55:35 -06:00
Wyatt Johnson 2c3a52b48c Merge branch 'master' into story/150848457 2017-09-15 15:49:30 -06:00
Wyatt Johnson 0b1a75726c Merge pull request #956 from coralproject/story/150636425
Status SYSTEM_WITHHELD
2017-09-15 15:49:11 -06:00
Wyatt Johnson 384c126d9e added docs 2017-09-15 15:29:20 -06:00
Wyatt Johnson f97094177a revamped configuration for moderation phases 2017-09-15 14:23:18 -06:00
Kiwi 4739f86739 Merge branch 'master' into eslint-react 2017-09-15 20:43:49 +02:00
Chi Vinh Le f386909748 Don't show tombstone during "view context" 2017-09-16 01:36:15 +07:00
Chi Vinh Le aa4f781d25 Better error detection 2017-09-16 01:30:26 +07:00
Chi Vinh Le 4567b4565f Detect and display stream errors 2017-09-16 01:19:59 +07:00
Chi Vinh Le 3133922100 Fix config parsing bug 2017-09-16 01:02:19 +07:00
Kiwi e9bd5e4476 Merge branch 'master' into story/150848457 2017-09-15 19:53:00 +02:00
Wyatt Johnson f2e06d6d27 Merge branch 'master' into story/150636425 2017-09-15 09:57:57 -06:00
Chi Vinh Le e31cdebc12 Make it appear in reported queue 2017-09-15 22:52:19 +07:00
Wyatt Johnson e03e7de82f Merge branch 'master' into static-url 2017-09-15 09:46:05 -06:00
Wyatt Johnson c7b755d2ff Merge pull request #954 from coralproject/url-fix
Fixed embed url link
2017-09-15 08:40:55 -06:00
Chi Vinh Le dc7013187d Merge branch 'master' into story/150636425 2017-09-15 19:11:31 +07:00
Belen Curcio 20c4148301 Close and styling 2017-09-14 17:37:47 -03:00
Belen Curcio ba24af015c Merge branch 'instream-ban' of github.com:coralproject/talk into instream-ban 2017-09-14 17:37:31 -03:00
Wyatt Johnson 94456674df fixed embed url link 2017-09-14 14:32:16 -06:00
Belen Curcio 5231c2d627 Removing perms mods 2017-09-14 17:28:54 -03:00
Wyatt Johnson f74e8dda16 added support for changing the static url 2017-09-14 14:27:45 -06:00
Wyatt Johnson 825d53afcd Merge branch 'master' into eslint-react 2017-09-14 13:28:53 -06:00
Wyatt Johnson 029877080d Merge branch 'master' into fix-notify-bugs 2017-09-14 13:28:34 -06:00
Wyatt Johnson 83f10a4863 Merge branch 'master' into story/150848457 2017-09-14 13:27:53 -06:00
Wyatt Johnson d820d2d2c3 moved support for deepReplyCount to seperate field 2017-09-14 13:08:01 -06:00
Belén Curcio a6443f2bcc Merge branch 'master' into instream-ban 2017-09-14 13:05:58 -03:00
Kim Gardner 7ace5b369a Merge pull request #951 from coralproject/update-readme
Update README
2017-09-14 16:49:40 +01:00
Kim Gardner cf66c10262 Update README 2017-09-14 16:45:19 +01:00
Kiwi 99296ebe69 Merge branch 'master' into fix-notify-bugs 2017-09-14 17:25:50 +02:00
Chi Vinh Le 7600ce8d26 Fix error notification bugs 2017-09-14 22:22:16 +07:00
Kim Gardner 26256b979e Merge branch 'master' into story/150848457 2017-09-14 16:20:20 +01:00
Kim Gardner 0113f02994 Merge pull request #944 from coralproject/new-regexp-highlighter
Better wordlist service and highlighter
2017-09-14 16:20:06 +01:00
Wyatt Johnson b3eb3361bb Merge branch 'master' into new-regexp-highlighter 2017-09-14 08:47:40 -06:00
Wyatt Johnson fce65e616d Merge pull request #949 from coralproject/fix-moderator-rest-permission
Allow Moderator to have read access on assets and settings
2017-09-14 08:47:28 -06:00
Wyatt Johnson 6a4a0b7f6b Merge branch 'master' into eslint-react 2017-09-14 08:33:42 -06:00
Chi Vinh Le c41ab58109 Allow Moderator to have read access on assets and settings 2017-09-14 21:11:40 +07:00
Belen Curcio 837a586234 Ban User confirmation box 2017-09-14 09:26:33 -03:00
Belen Curcio 2facde3894 Adding dialog 2017-09-14 09:14:15 -03:00
Chi Vinh Le 76a6a0e82b Remove natural package 2017-09-14 18:10:24 +07:00
Chi Vinh Le bea25f2a11 Merge branch 'new-regexp-highlighter' of ssh://github.com/coralproject/talk into new-regexp-highlighter 2017-09-14 17:40:01 +07:00
Chi Vinh Le ad361413c7 Reimplement wordlist on server using same regexp solution 2017-09-14 17:36:04 +07:00
Kim Gardner cf61d9e2ca Merge branch 'master' into story/150848457 2017-09-14 10:07:04 +01:00
Kim Gardner 77ef9ff111 Merge branch 'master' into new-regexp-highlighter 2017-09-14 10:06:43 +01:00
Kim Gardner c8cc5e6a34 Merge pull request #948 from coralproject/docs-update
Documentation Update
2017-09-14 09:16:17 +01:00
Belen Curcio bdd5e47427 Icon, translations 2017-09-13 17:38:04 -03:00
Wyatt Johnson 1574e08197 fixes #947 2017-09-13 12:02:20 -06:00
Chi Vinh Le abc651c55d Add some optimizations 2017-09-14 00:50:01 +07:00
Chi Vinh Le fa335564ed Generate RegExp from phrases 2017-09-14 00:10:29 +07:00
Belen Curcio 0ae2919def Perms and excludeIf 2017-09-13 13:42:32 -03:00
Belen Curcio 838a3bad2e Ban In-Stream 2017-09-13 13:21:27 -03:00
Chi Vinh Le 6487072be1 Merge branch 'new-regexp-highlighter' of ssh://github.com/coralproject/talk into new-regexp-highlighter 2017-09-13 22:23:41 +07:00
Chi Vinh Le a16f101a12 Match phrases not just words 2017-09-13 22:20:32 +07:00
Kim Gardner 647502d701 Merge branch 'master' into new-regexp-highlighter 2017-09-13 15:11:35 +01:00
Kim Gardner d728065fe5 Merge branch 'master' into story/150848457 2017-09-13 15:11:30 +01:00
Kim Gardner 18b8a18f1d Merge pull request #946 from coralproject/typo-fix-en-yml
Fix typo
2017-09-13 15:05:20 +01:00
Kim Gardner 1089807086 Fix typo 2017-09-13 15:00:00 +01:00
Kim Gardner 0c1c64fdf9 Merge branch 'master' into eslint-react 2017-09-13 13:19:35 +01:00
Kim Gardner 90a649db9b Merge branch 'master' into story/150848457 2017-09-13 13:18:56 +01:00
Kim Gardner 17ec666d97 Merge branch 'master' into new-regexp-highlighter 2017-09-13 13:18:47 +01:00
Kim Gardner cda86a67ab Merge pull request #942 from coralproject/instream-updates
In-Stream Moderation Updates
2017-09-13 13:18:28 +01:00
Kiwi 32d72129e5 Merge branch 'master' into instream-updates 2017-09-13 14:11:35 +02:00
Kim Gardner 2905ffb5cb Merge branch 'master' into new-regexp-highlighter 2017-09-13 12:43:29 +01:00
Kim Gardner 433b0d310e Merge branch 'master' into story/150848457 2017-09-13 12:43:16 +01:00
Kim Gardner 08848057a5 Merge pull request #941 from coralproject/stable-keys
Provide stable keys for slot elements
2017-09-13 12:43:02 +01:00
Kim Gardner 7f517ed4fb Merge branch 'master' into stable-keys 2017-09-13 12:35:12 +01:00
Kim Gardner c364b3e307 Merge pull request #945 from coralproject/fix-edit-animation
Fix edit animation
2017-09-13 12:34:42 +01:00
Kim Gardner 008df99100 Merge branch 'master' into story/150848457 2017-09-13 11:29:20 +01:00
Belen Curcio bfdfd98135 Linting 2017-09-12 15:11:01 -03:00
Belen Curcio 64be131047 Final 2017-09-12 15:09:11 -03:00
Belen Curcio 7115d4c31f Missing async 2017-09-12 15:02:29 -03:00
Belen Curcio a944cd2608 Correct branch 2017-09-12 15:01:15 -03:00
Belen Curcio 0e7d74559f Notify errors 2017-09-12 14:08:19 -03:00
Belen Curcio 297b905163 Hiding after action 2017-09-12 13:42:49 -03:00
Belen Curcio 9c08b586ec Hiding after action 2017-09-12 13:40:26 -03:00
Belen Curcio dbeca830a2 Closing after featuring and unfeaturing 2017-09-12 13:30:15 -03:00
Belen Curcio 0042767fad No approve button for Approved comments 2017-09-12 13:12:35 -03:00
Wyatt Johnson 110a0093af Merge branch 'master' into eslint-react 2017-09-12 10:00:11 -06:00
Belen Curcio 6bac6ee5c5 Removed old code 2017-09-12 12:59:48 -03:00
Belen Curcio aa364e1abe Renaming Fragment and variable name 2017-09-12 12:58:25 -03:00
Belen Curcio 6e88cc94e0 Merge branch 'reject-instream' of github.com:coralproject/talk into reject-instream 2017-09-12 12:56:07 -03:00
Chi Vinh Le 7976aace00 Typo 2017-09-12 22:46:24 +07:00
Kiwi 1261e4e25c Merge branch 'master' into instream-updates 2017-09-12 17:20:08 +02:00
Chi Vinh Le 2a655331fc Use same tlds list on server 2017-09-12 21:46:03 +07:00
Chi Vinh Le 98b95cab86 Reintroduce linkify 2017-09-12 21:31:59 +07:00
Chi Vinh Le d8ca9dfb07 Fix edit animation 2017-09-12 20:58:33 +07:00
Chi Vinh Le ee3cee2a50 Typo 2017-09-12 20:55:31 +07:00
Chi Vinh Le 65a6c408a7 Add some docs 2017-09-12 20:54:30 +07:00
Chi Vinh Le d18a9a057b Implement simple regexp based highlighter 2017-09-12 20:47:05 +07:00
Kiwi b27e3e0a7f Merge branch 'master' into stable-keys 2017-09-12 11:11:08 +02:00
Kiwi f2804f2394 Merge pull request #930 from coralproject/fix-graphql-warnings
Fix graphql warnings
2017-09-12 11:10:44 +02:00
Chi Vinh Le d1c30ab982 Remove talkUuid and use element key instead 2017-09-12 15:57:40 +07:00
Wyatt Johnson ca411a3227 added support for deep comment counts 2017-09-11 18:01:01 -06:00
Belen Curcio 5e4a48f4cc Updates 2017-09-11 15:38:42 -03:00
Belen Curcio c93b53c25b pdates 2017-09-11 15:11:22 -03:00
Belen Curcio 51f6c0738d Containers 2017-09-11 15:02:01 -03:00
Wyatt Johnson 49f09b87a0 Added new SYSTEM_WITHHELD comment status 2017-09-11 11:09:21 -06:00
Belen Curcio 06c45faeb0 Read/Write Fragments to update the cache 2017-09-11 13:57:24 -03:00
Kiwi 70eee3d4b2 Merge branch 'master' into stable-keys 2017-09-11 18:51:18 +02:00
Chi Vinh Le ecd4267ea9 Remove getSlotComponents completely 2017-09-11 23:46:16 +07:00
Chi Vinh Le 4f0e8d9e47 Provide stable keys for slot elements 2017-09-11 23:43:00 +07:00
Wyatt Johnson 1c630835c1 Merge branch 'master' into eslint-react 2017-09-11 09:07:31 -06:00
Kiwi a9c4c12824 Merge branch 'master' into fix-graphql-warnings 2017-09-11 16:49:10 +02:00
Belen Curcio a22ee3fefe Removing can since thats already checked 2017-09-11 11:44:01 -03:00
Belen Curcio 1e9cc82b17 Using redux to handle the tooltip state 2017-09-11 11:31:59 -03:00
Belen Curcio 65085fea65 No need to handle errors here 2017-09-11 11:02:02 -03:00
Belen Curcio 908b3c20b4 Adding FeaturedButton (we already have Button with Coral-UI) 2017-09-11 10:59:16 -03:00
Belen Curcio 59e21cccba Container for Button 2017-09-11 10:51:27 -03:00
Kim Gardner ab1f1dee3e Merge pull request #936 from coralproject/remember-sort
Init, Introspection service, pymStorage, and talk-plugin-remember-sort
2017-09-11 12:05:41 +01:00
Kim Gardner 04ad649f01 Merge branch 'master' into remember-sort 2017-09-11 12:00:18 +01:00
Kim Gardner 18e36af50a Merge pull request #940 from bocoup/improve-docs
improve docker compose getting started docs
2017-09-11 11:59:40 +01:00
Kim Gardner 8f1ce9cef6 Merge branch 'master' into remember-sort 2017-09-11 11:53:07 +01:00
Kim Gardner e47cb9732e Merge branch 'master' into fix-graphql-warnings 2017-09-11 11:49:45 +01:00
Kim Gardner 5159ecc64e Merge branch 'master' into eslint-react 2017-09-11 11:49:24 +01:00
Kim Gardner 851c5fbfbc Merge branch 'master' into improve-docs 2017-09-11 11:48:35 +01:00
Kim Gardner 0bc9ede8bc Merge pull request #938 from coralproject/story/150381206
Comment Status Fixes
2017-09-11 11:46:45 +01:00
Kim Gardner d8d758255d Merge branch 'master' into story/150381206 2017-09-11 11:39:38 +01:00
Kim Gardner c1e52ba494 Merge pull request #937 from coralproject/reject-instream
In-Stream Moderation - Accept, Reject and Feature!
2017-09-11 11:39:11 +01:00
Kiwi b7e501f7a3 Merge branch 'master' into story/150381206 2017-09-11 10:37:08 +02:00
Boaz Sender ee41cc690c add link to new location for config vars, and include min needed docker env vars in example 2017-09-09 18:05:08 -04:00
Kim Gardner 965b8f9bda Merge branch 'master' into reject-instream 2017-09-09 14:31:54 +01:00
Kim Gardner 823c570539 Merge pull request #939 from coralproject/losowsky-patch-1
Fixed broken link on docs homepage
2017-09-09 14:31:26 +01:00
Andrew Losowsky 221db29539 Fixed broken link on docs homepage
also changed the phones detail so that we don't have to keep updating it with new phones
2017-09-08 21:10:14 -05:00
Wyatt Johnson 9af21b4226 test fixes 2017-09-08 16:23:31 -06:00
Belen Curcio 7f85ceb566 Featuring from Featured Tab 2017-09-08 18:55:11 -03:00
Wyatt Johnson d20bd20005 linting 2017-09-08 15:46:04 -06:00
Wyatt Johnson ce3e29813b implemented status switch when mismatched 2017-09-08 15:41:43 -06:00
Belen Curcio 1f6da44b25 Merge branch 'reject-instream' of github.com:coralproject/talk into reject-instream 2017-09-08 18:07:24 -03:00
Belen Curcio 0ea951f3d9 wrong translation 2017-09-08 17:50:30 -03:00
Belén Curcio b75539fb57 Merge branch 'master' into reject-instream 2017-09-08 17:47:09 -03:00
Belen Curcio 72530e7501 Linting and Indenting 2017-09-08 17:17:03 -03:00
Belen Curcio 790c20e7ac Indent 2017-09-08 17:14:15 -03:00
Belen Curcio 2cbd59b4b5 Featured comments error in permalink view, handling permaview moderation and handling replies moderation 2017-09-08 17:05:20 -03:00
Kiwi d88919e27b Merge branch 'master' into remember-sort 2017-09-08 19:29:08 +02:00
Kiwi 7ea22c84ee Merge pull request #935 from coralproject/kill-the-zombies
Kill the zombies
2017-09-08 19:28:35 +02:00
Kiwi be86a6f3dc Merge branch 'master' into remember-sort 2017-09-08 19:28:26 +02:00
Chi Vinh Le 4046b6e0a6 Remove zombie files 2017-09-09 00:20:18 +07:00
Chi Vinh Le 6b74877d01 Implement and use pymStorage 2017-09-09 00:15:19 +07:00
Chi Vinh Le 9f33a77745 Implement and use pymStorage 2017-09-09 00:11:15 +07:00
Belen Curcio f33c31c70e Merge branch 'master' of github.com:coralproject/talk into reject-instream 2017-09-08 13:49:54 -03:00
Belen Curcio e28f1caaef Styles 2017-09-08 13:33:53 -03:00
Belen Curcio fefacf039f Featured comments should have ACCEPTED status 2017-09-08 13:25:23 -03:00
Belen Curcio 6494669262 Working for permalink 2017-09-08 13:15:17 -03:00
Peter deHaan 4971ba4722 Add react/recommended ESLint rules 2017-09-08 09:02:54 -07:00
Kim Gardner a9626ad12c Merge branch 'master' into fix-graphql-warnings 2017-09-08 14:37:17 +01:00
Belen Curcio fd71acf7e0 With Featured Comments :) 2017-09-08 09:06:33 -03:00
Belen Curcio 05d643d3c3 Styling 2017-09-08 08:35:57 -03:00
Chi Vinh Le 244a4b108d Correctly detect subsequent change 2017-09-08 18:16:49 +07:00
Kim Gardner 1221c6043f Merge pull request #931 from coralproject/prop-types
PropTypes
2017-09-08 12:09:53 +01:00
Chi Vinh Le 0cfd8afce7 Merge branch 'master' into remember-sort
Conflicts:
	.eslintignore
	.gitignore
2017-09-08 17:56:18 +07:00
Chi Vinh Le c8d1da5715 Remember sort plugin 2017-09-08 17:54:02 +07:00
Chi Vinh Le e5bd8e2a3b No need to check for loading config anymore 2017-09-08 17:47:02 +07:00
Chi Vinh Le 7051c76b40 Init step for plugins and bootstrap, introspection service 2017-09-08 17:37:27 +07:00
Kim Gardner 76fd261141 Merge branch 'master' into prop-types 2017-09-08 10:56:35 +01:00
Kim Gardner f2ecfa0a0d Merge branch 'master' into fix-graphql-warnings 2017-09-08 10:56:33 +01:00
Kim Gardner 5e295fc337 Merge pull request #803 from coralproject/feature/talk-plugin-toxic-comments
Toxic Comments Plugin
2017-09-08 10:51:14 +01:00
Kim Gardner 299c6f1ffd Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-09-08 10:16:40 +01:00
Kim Gardner 42e15bd630 Merge branch 'master' into prop-types 2017-09-08 09:37:24 +01:00
Kim Gardner d16066a981 Merge branch 'master' into fix-graphql-warnings 2017-09-08 09:33:59 +01:00
Kim Gardner 51077b689d Merge pull request #926 from coralproject/private-npm
Marking Talk as a Private Repo
2017-09-08 09:33:38 +01:00
Wyatt Johnson f69703a8ba fixed missing 2017-09-08 00:47:08 -06:00
Wyatt Johnson 2e350c2244 Merge branch 'master' into prop-types 2017-09-08 00:44:09 -06:00
Wyatt Johnson b43a57328c Merge branch 'master' into private-npm 2017-09-08 00:40:22 -06:00
Wyatt Johnson d5fcdd6a76 Merge branch 'master' into fix-graphql-warnings 2017-09-08 00:40:15 -06:00
Kim Gardner 589189023a Merge pull request #928 from coralproject/fix-linting
Fix linting
2017-09-08 06:53:08 +01:00
Chi Vinh Le 9c5821f6a2 Fix graphql warnings 2017-09-08 12:06:43 +07:00
Wyatt Johnson 671c1d7449 Merge pull request #927 from pdehaan/issue-923
Tweak ESLint config to check .js files
2017-09-07 14:04:29 -06:00
Wyatt Johnson 22cea681a7 Merge branch 'fix-linting' into issue-923 2017-09-07 14:03:21 -06:00
Wyatt Johnson 56bd3f23c7 fixed broken query 2017-09-07 13:53:37 -06:00
Wyatt Johnson 2b91bcffda fixes #923 2017-09-07 13:49:22 -06:00
Peter deHaan 2b6a5bfe6e Tweak ESLint config to check .js files 2017-09-07 12:46:52 -07:00
Wyatt Johnson d57c4b253a fixes #924 2017-09-07 13:25:15 -06:00
Belen Curcio 462fcb6b94 Approve actions and styles 2017-09-07 15:02:38 -03:00
Belen Curcio 8e96ef4692 Approve 2017-09-07 14:33:53 -03:00
Wyatt Johnson 94b9c4b2df Merge pull request #921 from coralproject/change-websocket-uri
Change Websocket URI
2017-09-07 11:21:54 -06:00
Wyatt Johnson 96e37ac725 moved config loading to bootstrap 2017-09-07 10:58:58 -06:00
Kiwi bd7f81fb35 Merge branch 'master' into change-websocket-uri 2017-09-07 18:36:21 +02:00
Kiwi 53372ca1e8 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-09-07 18:22:04 +02:00
Kim Gardner 856ae456f4 Merge pull request #920 from coralproject/bump-version
Bump version to 3.5.0
2017-09-07 17:18:06 +01:00
Belen Curcio f6b02a52fe Cache and Tombstoning rejected comment working 2017-09-07 13:08:52 -03:00
Kim Gardner aa52063fa4 Merge branch 'master' into bump-version 2017-09-07 16:55:10 +01:00
Kim Gardner b72bba96fb Merge branch 'master' into change-websocket-uri 2017-09-07 16:54:59 +01:00
Belen Curcio 32d9dc6646 Permissions to moderate comments 2017-09-07 11:27:12 -03:00
Belen Curcio a31a63e1c8 Working Tooltip 2017-09-07 10:12:49 -03:00
Chi Vinh Le f82a0c80bf Handle timeout and failure of perspective api 2017-09-07 19:37:45 +07:00
Belen Curcio 7b59323b26 Moderation Actions 2017-09-07 08:05:39 -03:00
Chi Vinh Le f1c5d479ad Merge branch 'master' into feature/talk-plugin-toxic-comments
Conflicts:
	graph/resolvers/root_mutation.js
	graph/typeDefs.graphql
2017-09-07 16:59:46 +07:00
Chi Vinh Le d8a956d09c Remove dependency on wrapResponse 2017-09-07 16:04:01 +07:00
Chi Vinh Le a78be6874b Remove response helpers 2017-09-07 15:49:51 +07:00
Chi Vinh Le c95c20c9ad Don't put in default plugins 2017-09-07 15:41:00 +07:00
Chi Vinh Le 43b7326db4 Cleanup and apply pr suggestions 2017-09-07 15:38:39 +07:00
Chi Vinh Le b1b3db51a6 Typo 2017-09-07 03:59:15 +07:00
Chi Vinh Le 41cd5b320f Refactor & cleanup toxic-comments plugin 2017-09-07 03:50:49 +07:00
Chi Vinh Le 9b0caa42cd Put toxic comment into premod and add flag 2017-09-06 22:58:52 +07:00
Kim Gardner aeb8852588 Merge pull request #922 from coralproject/story-search
Fixed old story search query
2017-09-06 16:39:00 +01:00
Chi Vinh Le b3fe3afd4c Add todo 2017-09-06 22:20:19 +07:00
Wyatt Johnson e96e9c0d5c Fixed old story search query 2017-09-06 09:18:22 -06:00
Chi Vinh Le a11f149c5a Refactor 2017-09-06 22:15:57 +07:00
Chi Vinh Le a6b517b3dd Remove unused code 2017-09-06 22:14:58 +07:00
Chi Vinh Le 6f82fd76f5 First draft of toxic-comments 2017-09-06 22:11:48 +07:00
Wyatt Johnson 4a9b24f096 added more docs related to cookies 2017-09-05 14:08:43 -06:00
Wyatt Johnson 48875294c8 initial support for changing the uri 2017-09-05 13:57:35 -06:00
Belen Curcio 12be487783 excludeIf no featured tags 2017-09-05 16:19:46 -03:00
Kim Gardner 5953ef1695 Merge pull request #919 from coralproject/staff-badge-bug
Missing permission for staff members
2017-09-05 17:36:08 +01:00
Kim Gardner 17ae62c66b Bump version to 3.5.0 2017-09-05 17:28:23 +01:00
Wyatt Johnson bb66d7220b Merge branch 'master' into staff-badge-bug 2017-09-05 10:26:11 -06:00
Belen Curcio 0cf7d19d4d Missing permission for staff members 2017-09-05 13:20:36 -03:00
Kim Gardner 24a8712159 Merge pull request #918 from coralproject/graph-typo
Graph Typo
2017-09-05 16:58:44 +01:00
Wyatt Johnson 7b27feee96 fixed typo 2017-09-05 09:45:31 -06:00
Kim Gardner 0b1e95e66d Merge pull request #894 from coralproject/asset-graph-api
Added graph API for assets
2017-09-05 16:20:21 +01:00
Wyatt Johnson 997a336483 Merge branch 'master' into asset-graph-api 2017-09-05 08:56:49 -06:00
Kim Gardner 51a4f0e02e Merge pull request #899 from coralproject/settings-graph-api
Added graph API for settings
2017-09-05 13:23:51 +01:00
Kim Gardner 869bf10c96 Merge branch 'master' into asset-graph-api 2017-09-05 11:57:08 +01:00
Kim Gardner 0375e7e039 Merge branch 'master' into settings-graph-api 2017-09-05 11:56:57 +01:00
Kim Gardner 9b29261f3d Merge pull request #917 from coralproject/fix-offtopic-checkbox
OffTopic checkbox should follow state
2017-09-05 11:56:33 +01:00
Kiwi fea5f75aa6 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-09-05 12:56:27 +02:00
Chi Vinh Le 345d183bc4 OffTopic checkbox should follow state 2017-09-05 17:21:11 +07:00
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 52490375e4 Merge branch 'master' into asset-graph-api 2017-09-04 11:34:03 +01:00
Kim Gardner 53871cb2c6 Merge branch 'master' into settings-graph-api 2017-09-04 11:33:59 +01: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
Wyatt Johnson 9a549a2b63 fixed some bugs from review 2017-09-01 11:21:30 -06: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 4dbcc65126 Merge branch 'master' into asset-graph-api 2017-08-31 11:57:50 -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
Wyatt Johnson 10693f3e5c Merge branch 'master' into settings-graph-api 2017-08-31 11:50:06 -06:00
Wyatt Johnson dfd77dbf63 updated based on cvle's feedback 2017-08-31 11:48:38 -06:00
Belen Curcio 36c4c6a59f Ready 2017-08-31 14:22:59 -03:00
Wyatt Johnson 7b71511cae removed ! 2017-08-31 10:37:52 -06: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
Wyatt Johnson f260e41274 Merge branch 'master' into asset-graph-api 2017-08-31 08:59:51 -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
Kiwi 2631d9338a Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-31 12:31:44 +02: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 94bb223f0e Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-31 08:51:53 +01:00
Kim Gardner 12c54c39db Merge branch 'master' into ioredis 2017-08-31 08:51:42 +01:00
Kim Gardner 0486d2926d Merge branch 'master' into asset-graph-api 2017-08-31 08:51:29 +01:00
Kim Gardner cb655f15b3 Merge branch 'master' into settings-graph-api 2017-08-31 08:51:16 +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 2eb0637e61 Merge branch 'master' into settings-graph-api 2017-08-30 16:25:51 -06:00
Wyatt Johnson 079a20f13e Merge branch 'master' into asset-graph-api 2017-08-30 16:11:27 -06: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 f0dcef7b37 finished connection, improved graph 2017-08-30 14:23:47 -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
Kim Gardner a2fdc11451 Fix merge conflict 2017-08-30 16:23:17 +01: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 6b15c335b5 Merge branch 'master' into settings-graph-api 2017-08-30 12:36:09 +01:00
Kim Gardner c246caa520 Merge branch 'master' into asset-graph-api 2017-08-30 12:36:03 +01: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
Wyatt Johnson 8ca32c5808 added missing fields 2017-08-29 13:54:14 -06:00
Wyatt Johnson 5a48705d49 Merge branch 'master' into settings-graph-api 2017-08-29 13:44:20 -06:00
Wyatt Johnson 173c257126 Added Graph API for Settings 2017-08-29 13:43:48 -06:00
Kiwi 9e65f1b00d Merge branch 'master' into asset-graph-api 2017-08-29 21:04:30 +02: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 bf7dfefcdf Merge branch 'master' into asset-graph-api 2017-08-29 18:52:17 +01: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 d4c9749133 Merge branch 'master' into asset-graph-api 2017-08-29 12:59:26 +02: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 90a8a87eaf Added graph API for assets 2017-08-28 18:57:32 -06: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
Wyatt Johnson deb69d4e8c fixed prop-types deprecation 2017-08-28 12:03:20 -06: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 f528fb4345 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-21 15:43:28 +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
Chi Vinh Le 84e4091ea6 Implement TalkProvider 2017-08-21 21:23:59 +07:00
Belen Curcio 0ea2660c97 Adding new Slot: authorName - Deleting Old Code 2017-08-21 10:44:03 -03:00
Kim Gardner df79530982 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-21 12:03:06 +01:00
Kim Gardner 1a1e3d2d4a Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-21 09:36:54 +01: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
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 6c19b90652 Merge branch 'master' into user-sort 2017-08-18 09:14:19 -06:00
Kim Gardner 7517679c5e Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-18 13:11:14 +01:00
Kim Gardner c6d7b714a1 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-18 09:35:33 +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
Kim Gardner d12d0cfe4f Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-17 10:04:51 +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
Kim Gardner 2c6447cd07 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-16 10:38:36 +01:00
Kim Gardner ce63113c5d Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-15 11:28:00 +01:00
Kim Gardner 3636bb71c4 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-09 11:38:33 +01:00
Kim Gardner 788950fed2 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-04 07:04:52 -04:00
Kim Gardner 4086fe8684 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-03 09:31:05 -04:00
Kim Gardner 6e6d999c9b Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-08-02 12:48:44 -04:00
Kim Gardner c2f866a077 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-07-31 19:22:57 -04:00
Kim Gardner 0d037c3acb Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-07-31 18:44:05 -04:00
Jeff Nelson 88f06744b4 unignore 2017-07-31 11:59:15 -04:00
Jeff Nelson 2c1cd50828 turn router method from get to post 2017-07-31 11:59:15 -04:00
Jeff Nelson 8c4612a4dd remove unused import 2017-07-31 11:59:15 -04:00
Jeff Nelson 4b13ccc5c1 enforce API key 2017-07-31 11:59:15 -04:00
Jeff Nelson b62ab9bff0 pass comment to presubmit hooks 2017-07-31 11:59:15 -04:00
Belén Curcio 3740dd98e8 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-07-28 13:22:29 -03:00
Kim Gardner 4db1cf2656 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-07-28 10:58:34 -04:00
Belén Curcio eb44c55e05 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-07-28 10:02:03 -03:00
Kim Gardner 75da26ff7a Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-07-27 13:02:53 -04:00
Kim Gardner 93d0a265b5 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-07-27 11:05:17 -04:00
Kim Gardner 494d52ca36 Merge branch 'master' into feature/talk-plugin-toxic-comments 2017-07-26 11:39:31 -04:00
Jeff Nelson c67f92eb44 add toxic comments plugin to main api 2017-07-25 12:08:36 -04:00
898 changed files with 17239 additions and 34310 deletions
+2 -1
View File
@@ -6,7 +6,7 @@ node_modules
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.
@@ -14,6 +14,7 @@ dist
# tests are not run in the docker container.
test
__tests__
# we won't use the .git folder in production.
.git
+15 -1
View File
@@ -1,6 +1,5 @@
dist
docs
client/lib
**/*.html
plugins/*
!plugins/talk-plugin-facebook-auth
@@ -14,5 +13,20 @@ plugins/*
!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
!plugins/talk-plugin-moderation-actions
!plugins/talk-plugin-toxic-comments
!plugins/talk-plugin-remember-sort
!plugins/talk-plugin-deep-reply-count
!plugins/talk-plugin-subscriber
!plugins/talk-plugin-flag-details
node_modules
+1 -64
View File
@@ -1,66 +1,3 @@
{
"env": {
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2017
},
"rules": {
"indent": ["error",
2
],
"no-console": [
0
],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "single"],
"semi": ["error", "always"],
"no-template-curly-in-string": [1],
"no-unsafe-negation": [1],
"array-callback-return": [1],
"arrow-parens": ["warn", "always"],
"template-curly-spacing": "warn",
"eqeqeq": [2, "smart"],
"no-eval": [2],
"no-global-assign": [2],
"no-implied-eval": [2],
"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],
"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],
"comma-spacing": ["error", {
"after": true
}],
"no-var": [2],
"no-lonely-if": [2],
"curly": [2],
"no-unused-vars": ["error", {
"argsIgnorePattern": "^_|next",
"varsIgnorePattern": "^_"
}],
"no-multiple-empty-lines": ["error", {
"max": 1
}],
"newline-per-chained-call": ["error", {
"ignoreChainWithDepth": 2
}]
}
"extends": "@coralproject/eslint-config-talk"
}
+16 -1
View File
@@ -14,7 +14,6 @@ client/coral-framework/graphql/introspection.json
*.swp
*.DS_STORE
test/e2e/reports
coverage/
plugins.json
@@ -30,5 +29,21 @@ plugins/*
!plugins/talk-plugin-comment-content
!plugins/talk-plugin-permalink
!plugins/talk-plugin-featured-comments
!plugins/talk-plugin-toxic-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
!plugins/talk-plugin-moderation-actions
!plugins/talk-plugin-toxic-comments
!plugins/talk-plugin-remember-sort
!plugins/talk-plugin-deep-reply-count
!plugins/talk-plugin-subscriber
!plugins/talk-plugin-flag-details
**/node_modules/*
@@ -1,7 +1,4 @@
---
title: Contributor Covenant Code of Conduct
permalink: /docs/development/code-of-conduct/
---
# Contributor Covenant Code of Conduct
## Our Pledge
@@ -46,7 +43,7 @@ Other inappropriate behavior:
* Impersonation of someone else
* Violating someones privacy
If you feel that someone has harassed you or otherwise treated you or someone else inappropriately, please alert the project lead at [andrewl@mozillafoundation.org](mailto:andrewl@mozillafoundation.org).
If you feel that someone has harassed you or otherwise treated you or someone else inappropriately, please alert the project lead at [andrewl@mozillafoundation.org](mailto:andrewl@mozillafoundation.org).
## Our Responsibilities
@@ -71,11 +68,11 @@ If youre too frustrated to have a direct conversation, there are a number of
* Contact the project lead, Andrew Losowsky, [andrewl@mozillafoundation.org](mailto:andrewl@mozillafoundation.org), or the technical lead. We will work with you to help you figure out how to ensure that any conflict doesnt interfere with your work, in confidence if you would prefer.
* Talk to Chris Lawrence. Chris oversees the project. He can be contacted at [clawrence@mozillafoundation.org](mailto:clawrence@mozillafoundation.org).
* Talk to Chris Lawrence. Chris oversees the project. He can be contacted at [clawrence@mozillafoundation.org](mailto:clawrence@mozillafoundation.org).
If you feel you have been unfairly accused of violating this code of conduct, you should contact Chris with a concise description of your grievance.
## Conclusion
We welcome your feedback on this and every other aspect of what we do as The Coral Project, and we thank you for working with us to make it a safe, enjoyable, and friendly experience for everyone involved in the project and what we do.
Above text is licensed CC BY-SA 4.0, adapted from the SRCCON code of conduct, FreeBSDs code of conduct, Vox Medias product team code of conduct, Mediums code of conduct, as well as adapted from the Contributor Covenant.
Above text is licensed CC BY-SA 4.0, adapted from the SRCCON code of conduct, FreeBSDs code of conduct, Vox Medias product team code of conduct, Mediums code of conduct, as well as adapted from the Contributor Covenant.
@@ -1,13 +1,10 @@
---
title: Contributor's Guide
permalink: /docs/development/contributing/
---
# Contributor's Guide
Welcome! We are very excited that you are interested in contributing to Talk.
This document is a companion to help you approach contributing. If it does not do so, please [let us know how we can improve it](https://github.com/coralproject/talk/issues)!
By contributing to this project you agree to the [Code of Conduct](https://coralproject.net/code-of-conduct).
By contributing to this project you agree to the [Code of Conduct](CODE_OF_CONDUCT.md).
## What should I Contribute?
@@ -33,7 +30,7 @@ Please file issues if:
### What should I include?
Coral has adopted an iterative, agile development philosophy. All contributions that make it into the Talk repo should start with a story or this form:
Coral has adopted an iterative, agile development philosophy. All contributions that make it into the Talk repository should start with a story or this form:
> As a [type of person] I'd like to be able to [do something] so that I can [get some result].
@@ -56,15 +53,24 @@ We are looking for _documentarians_ to:
* create new / missing sections, and
* take the lead in making sections, or the over all structure better.
Information about how to update docs can be found in our [FAQ]({{ "/docs/faq" | absolute_url }}#how-do-i-contribute-to-these-docs).
Our documentation is stored in markdown files in the [docs](docs) directory. We
use Jekyll to provide our docs. To preview:
```shell
cd docs
bundle install
bundle exec jekyll serve
```
Then visit http://127.0.0.1:4000/talk/.
If you'd like to discuss a contribution, please [file an issue](https://github.com/coralproject/talk/issues) describing the changes you would like to see.
## Contributing Translations
Talk's tranlations are stored in `.yml` files [here](https://github.com/coralproject/talk/tree/master/locales).
Talk's translations are stored in `.yml` files [here](https://github.com/coralproject/talk/tree/master/locales).
Translations can be submitted via pull request. If you do not use github, you can use 'en.yml' as a template and [email](https://coralproject.net/contact) the translations to us. We can import it into the repo.
Translations can be submitted via pull request. If you do not use github, you can use 'en.yml' as a template and [email](https://coralproject.net/contact) the translations to us. We can import it into the repository.
## I want to contribute but I'm not sure what to do!
@@ -84,4 +90,4 @@ Have a favorite analytics engine? Data science service? CMS? Auth platform? Depl
### Favorite Features?
Do you have a favorite feature of an existing platform that's not yet been done in Talk? Sounds like Talk needs that feature.
Do you have a favorite feature of an existing platform that's not yet been done in Talk? Sounds like Talk needs that feature.
+4 -4
View File
@@ -1,4 +1,4 @@
FROM node:8
FROM node:8-alpine
# Create app directory
RUN mkdir -p /usr/src/app
@@ -12,6 +12,9 @@ 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 && \
@@ -19,7 +22,4 @@ RUN yarn global add node-gyp && \
yarn build && \
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
+9 -15
View File
@@ -1,25 +1,19 @@
# 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)
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.
## Documentation
## Important Links
Developer Documentation & Setup Guides https://coralproject.github.io/talk/.
- Developer Documentation & Setup Guides: https://coralproject.github.io/talk/
- Pivotal Tracker Backlog & Release Schedule: https://www.pivotaltracker.com/n/projects/1863625
## Learn More about Coral
## Relevant Links
- Blog: https://blog.coralproject.net/
- Community Forums: https://community.coralproject.net/
- Website: https://coralproject.net
- Blog: https://blog.coralproject.net
- Community Guides for Journalism: https://guides.coralproject.net/
- Community Guides for Journalism: https://guides.coralproject.net/
- Project: https://coralproject.net/
- Roadmap: https://www.pivotaltracker.com/n/projects/1863625
## License
+8 -8
View File
@@ -2,10 +2,13 @@ const express = require('express');
const bodyParser = require('body-parser');
const morgan = require('morgan');
const path = require('path');
const merge = require('lodash/merge');
const helmet = require('helmet');
const compression = require('compression');
const cookieParser = require('cookie-parser');
const {BASE_URL, BASE_PATH, MOUNT_PATH} = require('./url');
const {HELMET_CONFIGURATION} = require('./config');
const {MOUNT_PATH} = require('./url');
const {applyLocals} = require('./services/locals');
const routes = require('./routes');
const debug = require('debug')('talk:app');
@@ -26,9 +29,9 @@ app.set('trust proxy', 1);
// Enable a suite of security good practices through helmet. We disable
// frameguard to allow crossdomain injection of the embed.
app.use(helmet({
app.use(helmet(merge(HELMET_CONFIGURATION, {
frameguard: false,
}));
})));
// Compress the responses if appropriate.
app.use(compression());
@@ -50,11 +53,8 @@ app.set('view engine', 'ejs');
// 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;
// Add the locals to the app renderer.
applyLocals(app.locals);
debug(`mounting routes on the ${MOUNT_PATH} path`);
+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);
}
}
//==============================================================================
+1 -1
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 = [];
+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();
}
+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
View File
@@ -40,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:
+2 -1
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 -21
View File
@@ -1,23 +1,3 @@
{
"env": {
"browser": true,
"es6": true,
"mocha": true
},
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
}
},
"parser": "babel-eslint",
"plugins": [
"react"
],
"rules": {
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
}
"extends": "@coralproject/eslint-config-talk/client"
}
+12 -4
View File
@@ -1,12 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Router, Route, IndexRedirect, IndexRoute} from 'react-router';
import {history} from 'coral-framework/helpers/router';
import Configure from 'routes/Configure';
import Dashboard from 'routes/Dashboard';
import Install from 'routes/Install';
import Stories from 'routes/Stories';
import {CommunityLayout, Community} from 'routes/Community';
import Community from 'routes/Community/containers/Community';
import {ModerationLayout, Moderation} from 'routes/Moderation';
import Layout from 'containers/Layout';
@@ -22,7 +22,7 @@ const routes = (
{/* Community Routes */}
<Route path='community' component={CommunityLayout}>
<Route path='community'>
<Route path='flagged' components={Community}>
<Route path=':id' components={Community} />
</Route>
@@ -47,6 +47,14 @@ const routes = (
</div>
);
const AppRouter = () => <Router history={history} routes={routes}/>;
class AppRouter extends React.Component {
static contextTypes = {
history: PropTypes.object,
};
render() {
return <Router history={this.context.history} routes={routes} />;
}
}
export default AppRouter;
+4 -5
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) => {
export const updateAssetState = (id, closedAt) => (dispatch, _, {rest}) => {
dispatch({type: UPDATE_ASSET_STATE_REQUEST, id, closedAt});
return coralApi(`/assets/${id}/status`, {method: 'PUT', body: {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'});
};
+13 -15
View File
@@ -1,4 +1,4 @@
import qs from 'qs';
import queryString from 'query-string';
import {
FETCH_COMMENTERS_REQUEST,
@@ -14,13 +14,11 @@ 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?${queryString.stringify(query)}`)
.then(({result, page, count, limit, totalPages}) =>{
dispatch({
type: FETCH_COMMENTERS_SUCCESS,
@@ -51,18 +49,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
+25 -26
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());
@@ -106,10 +105,10 @@ export const submitUser = () => (dispatch, getState) => {
});
};
export const finishInstall = () => (dispatch, getState) => {
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,18 +128,18 @@ 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) => async (dispatch, _, {rest}) => {
dispatch(checkInstallRequest());
coralApi('/setup')
.then(({installed}) => {
dispatch(checkInstallSuccess(installed));
if (installed) {
next();
}
})
.catch((error) => {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch(checkInstallFailure(errorMessage));
});
try {
const {installed} = await rest('/setup');
dispatch(checkInstallSuccess(installed));
if (installed) {
next();
}
} catch (error) {
console.error(error);
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch(checkInstallFailure(errorMessage));
}
};
+5 -3
View File
@@ -4,15 +4,17 @@ 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 setSortOrder = (order) => ({
+4 -5
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) => {
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});
})
+2 -4
View File
@@ -4,10 +4,8 @@ export const viewUserDetail = (userId) => ({type: actions.VIEW_USER_DETAIL, user
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') {
let statuses = null;
if (tab === 'rejected') {
statuses = ['REJECTED'];
}
return {type: actions.CHANGE_USER_DETAIL_STATUSES, tab, statuses};
+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,32 +0,0 @@
import React, {PropTypes} from 'react';
import styles from './ModerationList.css';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../utils/moderationQueueActionsMap';
import t from 'coral-framework/services/i18n';
const ActionButton = ({type = '', active, ...props}) => {
const typeName = type.toLowerCase();
let text = menuActionsMap[type].text;
if (text === 'approve' && active) {
text = 'approved';
} else if (text === 'reject' && active) {
text = 'rejected';
}
return (
<Button
className={`${typeName} ${styles.actionButton} ${props.minimal ? styles.minimal : ''} ${active ? styles[`${typeName}__active`] : ''}`}
cStyle={typeName}
icon={menuActionsMap[type].icon}
onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment}
>{props.minimal ? '' : t(`modqueue.${text}`)}</Button>
);
};
ActionButton.propTypes = {
active: PropTypes.bool
};
export default ActionButton;
@@ -1,4 +1,5 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {Button, Icon} from 'coral-ui';
import {Menu} from 'react-mdl';
import cn from 'classnames';
@@ -57,6 +58,7 @@ class ActionsMenu extends React.Component {
ActionsMenu.propTypes = {
icon: PropTypes.string,
children: PropTypes.node,
};
export default ActionsMenu;
@@ -1,9 +1,14 @@
import React from 'react';
import cn from 'classnames';
import {MenuItem} from 'react-mdl';
import PropTypes from 'prop-types';
import styles from './ActionsMenu.css';
const ActionsMenuItem = (props) =>
<MenuItem className={cn(styles.menuItem, props.className)} {...props} />;
ActionsMenuItem.propTypes = {
className: PropTypes.string,
};
export default ActionsMenuItem;
+19 -16
View File
@@ -1,4 +1,5 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import Layout from 'coral-admin/src/components/ui/Layout';
import styles from './NotFound.css';
import {Button, TextField, Alert, Success} from 'coral-ui';
@@ -69,23 +70,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}>
@@ -104,7 +105,9 @@ AdminLogin.propTypes = {
handleLogin: PropTypes.func.isRequired,
passwordRequestSuccess: PropTypes.string,
loginError: PropTypes.string,
recaptchaPublic: PropTypes.string
recaptchaPublic: PropTypes.string,
requestPasswordReset: PropTypes.func,
errorMessage: PropTypes.string,
};
export default AdminLogin;
@@ -0,0 +1,44 @@
.root {
display: block;
color: #519954;
border: solid 2px rgba(81, 153, 84, 0.75);
background: white;
padding: 10px 12px;
box-sizing: border-box;
vertical-align: middle;
line-height: 24px;
font-size: 17px;
height: 47px;
border-radius: 3px;
text-transform: capitalize;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.03), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.09);
width: 129px;
transform: scale(.8);
margin: 0;
&:hover {
box-shadow: none;
color: white;
background-color: #519954;
cursor: pointer;
}
}
.active {
box-shadow: none;
color: white;
background-color: #519954;
&:hover {
cursor: not-allowed;
}
}
.minimal {
width: 45px;
min-width: 0;
}
.icon {
margin-right: 5px;
}
@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './ApproveButton.css';
import {Icon} from 'coral-ui';
import t from 'coral-framework/services/i18n';
const ApproveButton = ({active, minimal, onClick}) => {
const text = active ? t('modqueue.approved') : t('modqueue.approve');
return (
<button
className={cn(styles.root, {[styles.minimal]: minimal, [styles.active]: active})}
onClick={onClick}
>
<Icon name={'done'} className={styles.icon} />
{!minimal && text}
</button>
);
};
ApproveButton.propTypes = {
active: PropTypes.bool,
minimal: PropTypes.bool,
onClick: PropTypes.func,
};
export default ApproveButton;
@@ -1,4 +1,5 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
@@ -1,3 +1,7 @@
.root {
position: relative;
}
.bodyLeave {
position: absolute;
width: 100%;
@@ -2,11 +2,13 @@ import React from 'react';
import {murmur3} from 'murmurhash-js';
import {CSSTransitionGroup} from 'react-transition-group';
import styles from './CommentAnimatedEdit.css';
import PropTypes from 'prop-types';
export default ({children, body}) => {
const CommentBodyHighlighter = ({children, body}) => {
return (
<CSSTransitionGroup
component={'div'}
className={styles.root}
transitionName={{
enter: styles.bodyEnter,
enterActive: styles.bodyEnterActive,
@@ -22,3 +24,10 @@ export default ({children, body}) => {
</CSSTransitionGroup>
);
};
CommentBodyHighlighter.propTypes = {
children: PropTypes.node,
body: PropTypes.string,
};
export default CommentBodyHighlighter;
@@ -1,24 +1,78 @@
import React from 'react';
import Highlighter from 'react-highlight-words';
import Linkify from 'react-linkify';
const linkify = new Linkify();
import {matchLinks} from '../utils';
import memoize from 'lodash/memoize';
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
// generate a regulare expression that catches the `phrases`.
function generateRegExp(phrases) {
const inner = phrases
.map((phrase) =>
phrase.split(/\s+/)
.map((word) => escapeRegExp(word))
.join('[\\s"?!.]+')
).join('|');
return new RegExp(`(^|[^\\w])(${inner})(?=[^\\w]|$)`, 'iu');
}
// Generate a regular expression detecting `suspectWords` and `bannedWords` phrases.
function getPhrasesRegexp(suspectWords, bannedWords) {
return generateRegExp([...suspectWords, ...bannedWords]);
}
// Memoized version as arguments rarely change.
const getPhrasesRegexpMemoized = memoize(getPhrasesRegexp);
// markPhrases looks for `supsectWords` and `bannedWords` inside `body` and highlights them by returning
// an array of React Elements.
function markPhrases(body, suspectWords, bannedWords, keyPrefix) {
const regexp = getPhrasesRegexpMemoized(suspectWords, bannedWords);
const tokens = body.split(regexp);
return tokens.map((token, i) =>
i % 3 === 2
? <mark key={`${keyPrefix}_${i}`}>{token}</mark>
: token
);
}
// markLinks looks for links inside `body` and highlights them by returning
// an array of React Elements.
function markLinks(body) {
const matches = matchLinks(body);
const content = [];
let index = 0;
if (matches) {
matches
.forEach((match, i) => {
content.push(body.substring(index, match.index));
content.push(<mark key={i}>{match.text}</mark>);
index = match.lastIndex;
});
}
content.push(body.substring(index));
return content;
}
export default ({suspectWords, bannedWords, body, ...rest}) => {
const links = linkify.getMatches(body);
const linkText = links ? links.map((link) => link.raw) : [];
// First highlight links.
const content = markLinks(body)
.map((element, index) => {
const searchWords = [
...suspectWords,
...bannedWords,
...linkText
];
// Keep highlighted links.
if (typeof element !== 'string') {
return element;
}
// Highlight suspect and banned phrase inside this part of text.
return markPhrases(element, suspectWords, bannedWords, index);
});
return (
<Highlighter
{...rest}
searchWords={searchWords}
textToHighlight={body}
/>
<div {...rest}>
{content}
</div>
);
};
@@ -1,4 +0,0 @@
.textareaContainer {
width: 100%;
}
@@ -1,35 +0,0 @@
import React from 'react';
import styles from './CommentBox.css';
import {Button} from 'react-mdl';
// Renders a comment box for creating a new comment
export default class CommentBox extends React.Component {
constructor (props) {
super(props);
this.state = {name: '', body: ''};
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit () {
const {name, body} = this.state;
this.props.onSubmit({name, body});
this.setState({body: '', name: ''});
}
render (props, {name, body}) {
return (
<div>
<div class={`${styles.textareaContainer} mdl-textfield mdl-js-textfield`}>
<input type='text' value={name} onInput={this.linkState('name')} class='mdl-textfield__input' id='name' />
<label class='mdl-textfield__label' for='name'>Your name</label>
</div>
<div class={`${styles.textareaContainer} mdl-textfield mdl-js-textfield`}>
<textarea value={body} onInput={this.linkState('body')} class='mdl-textfield__input' type='text' rows='5' id='comment' />
<label class='mdl-textfield__label' for='comment'>Write your comment</label>
</div>
<Button onClick={this.onSubmit} raised>Post</Button>
</div>
);
}
}
@@ -0,0 +1,18 @@
.root {
min-height: 25px;
padding: 0 16px;
}
.moreDetail {
position: absolute;
font-size: 12px;
font-weight: 500;
color: black;
right: 16px;
&:hover {
opacity: 0.9;
cursor: pointer;
}
}
@@ -0,0 +1,66 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import styles from './CommentDetails.css';
import t from 'coral-framework/services/i18n';
import Slot from 'coral-framework/components/Slot';
import IfSlotIsNotEmpty from 'coral-framework/components/IfSlotIsNotEmpty';
class CommentDetails extends Component {
state = {
showDetail: false
};
constructor () {
super();
this.state = {
showDetail: false
};
}
toggleDetail = () => {
this.setState((state) => ({
showDetail: !state.showDetail
}));
}
render() {
const {data, root, comment} = this.props;
const {showDetail} = this.state;
const queryData = {
root,
comment,
};
return (
<div className={styles.root}>
<IfSlotIsNotEmpty
queryData={queryData}
slot={['adminCommentMoreDetails', 'adminCommentMoreFlagDetails']}
>
<a onClick={this.toggleDetail} className={styles.moreDetail}>
{showDetail ? t('modqueue.less_detail') : t('modqueue.more_detail')}
</a>
</IfSlotIsNotEmpty>
<Slot
fill="adminCommentDetailArea"
data={data}
queryData={queryData}
more={showDetail}
/>
{showDetail && <Slot
fill="adminCommentMoreDetails"
data={data}
queryData={queryData}
/>}
</div>
);
}
}
CommentDetails.propTypes = {
data: PropTypes.object.isRequired,
root: PropTypes.object.isRequired,
comment: PropTypes.object.isRequired,
};
export default CommentDetails;
@@ -0,0 +1,27 @@
.root {
display: flex;
justify-content: flex-end;
}
.coreLabels {
> *:not(:last-child) {
margin-right: 3px;
}
}
.slot {
&:not(:empty) {
padding-left: 3px;
}
> *:not(:last-child) {
margin-right: 3px;
}
}
.replyLabel {
background-color: #3D73D5;
}
.premodLabel {
background-color: #063B9A;
}
@@ -0,0 +1,45 @@
import React from 'react';
import PropTypes from 'prop-types';
import Label from 'coral-ui/components/Label';
import Slot from 'coral-framework/components/Slot';
import FlagLabel from 'coral-ui/components/FlagLabel';
import cn from 'classnames';
import styles from './CommentLabels.css';
function isUserFlagged(actions) {
return actions.some((action) => action.__typename === 'FlagAction' && action.user);
}
function hasSuspectedWords(actions) {
return actions.some((action) => action.__typename === 'FlagAction' && action.reason === 'Matched suspect word filter');
}
function hasHistoryFlag(actions) {
return actions.some((action) => action.__typename === 'FlagAction' && action.reason === 'TRUST');
}
const CommentLabels = ({comment, comment: {className, status, actions, hasParent}}) => {
return (
<div className={cn(className, styles.root)}>
<div className={styles.coreLabels}>
{hasParent && <Label iconName="reply" className={styles.replyLabel}>reply</Label>}
{status === 'PREMOD' && <Label iconName="query_builder" className={styles.premodLabel}>Pre-Mod</Label>}
{isUserFlagged(actions) && <FlagLabel iconName="person">User</FlagLabel>}
{hasSuspectedWords(actions) && <FlagLabel iconName="sms_failed">Suspect</FlagLabel>}
{hasHistoryFlag(actions) && <FlagLabel iconName="sentiment_very_dissatisfied">History</FlagLabel>}
</div>
<Slot className={styles.slot} fill="adminCommentLabels" queryData={{comment}} />
</div>
);
};
CommentLabels.propTypes = {
comment: PropTypes.shape({
className: PropTypes.string,
status: PropTypes.string,
actions: PropTypes.array,
hasParent: PropTypes.bool,
}),
};
export default CommentLabels;
@@ -1,31 +0,0 @@
.commentType {
display: inline-block;
color: white;
background: grey;
box-sizing: border-box;
padding: 0px 5px;
border-radius: 2px;
font-size: 12px;
height: 24px;
letter-spacing: 0.4px;
margin-bottom: 1px;
> i {
font-size: 14px;
vertical-align: text-top;
margin: 0;
margin-right: 4px;
}
&.premod {
background: #063B9A;
}
&.flagged {
background: #d03235;
}
&.no-type {
display: none;
}
}
@@ -1,31 +0,0 @@
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={cn(styles.commentType, styles[typeData.className], props.className)}>
<Icon name={typeData.icon}/>{typeData.text}
</span>
);
};
const getTypeData = (type) => {
switch (type) {
case 'premod':
return {icon: 'query_builder', text: 'Pre-Mod', className: 'premod'};
case 'flagged':
return {icon: 'flag', text: 'Flagged', className: 'flagged'};
default:
return {icon: 'flag', className: 'no-type'};
}
};
CommentType.propTypes = {
type: PropTypes.string.isRequired
};
export default CommentType;
@@ -5,7 +5,7 @@
vertical-align: middle;
padding: 1px 5px;
border-radius: 2px;
margin-left: 2px;
margin-left: 5px;
line-height: 18px;
box-sizing: border-box;
height: 18px;
@@ -1,9 +1,10 @@
import React, {PropTypes} from 'react';
import styles from './CommentCount.css';
import React from 'react';
import PropTypes from 'prop-types';
import styles from './CountBadge.css';
import t from 'coral-framework/services/i18n';
const CommentCount = ({count}) => {
const CountBadge = ({count}) => {
let number = count;
// shorten large counts to abbreviations
@@ -20,8 +21,8 @@ const CommentCount = ({count}) => {
);
};
CommentCount.propTypes = {
CountBadge.propTypes = {
count: PropTypes.number.isRequired
};
export default CommentCount;
export default CountBadge;
@@ -1,4 +1,5 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {Card} from 'coral-ui';
const EmptyCard = (props) => (
@@ -1,83 +0,0 @@
.flagBox {
border-top: 1px solid rgba(66, 66, 66, 0.12);
margin-top: 10px;
.container {
padding: 0 14px;
}
.detail {
padding: 0 20px 16px;
ul {
padding: 0;
list-style: none;
font-size: 12px;
font-weight: 500;
}
}
.header {
position: relative;
.moreDetail {
float: right;
font-size: 12px;
font-weight: 500;
margin-right: 10px;
margin-top: 8px;
color: black;
&:hover {
opacity: 0.9;
cursor: pointer;
}
}
i {
vertical-align: middle;
font-size: 12px;
}
ul {
vertical-align: middle;
list-style: none;
display: inline-block;
padding: 0;
margin-left: 10px;
font-size: 12px;
}
}
h3 {
vertical-align: middle;
margin: 0;
font-weight: 500;
display: inline-block;
margin-left: 7px;
font-size: 12px;
}
}
.lessDetail {
display: inline-block;
margin-right: 10px;
}
.subDetail {
font-weight: normal;
color: #888;
span {
color: black;
}
}
.username {
color: #393B44;
text-decoration: none;
cursor: pointer;
font-weight: 600;
padding: 2px 5px;
border-radius: 2px;
margin-left: -5px;
transition: background-color 200ms ease;
&:hover {
background-color: #E0E0E0;
}
}
@@ -1,96 +0,0 @@
import React, {Component, PropTypes} from 'react';
import {Icon} from 'coral-ui';
import styles from './FlagBox.css';
import t from 'coral-framework/services/i18n';
const shortReasons = {
'This comment is offensive': t('modqueue.offensive'),
'This looks like an ad/marketing': t('modqueue.spam_ads'),
'This user is impersonating': t('modqueue.impersonating'),
'I don\'t like this username': t('modqueue.dont_like_username'),
'Other': t('modqueue.other')
};
class FlagBox extends Component {
constructor () {
super();
this.state = {
showDetail: false
};
}
toggleDetail = () => {
this.setState((state) => ({
showDetail: !state.showDetail
}));
}
reasonMap = (reason) => {
const shortReason = shortReasons[reason];
// if the short reason isn't found, just return the long one.
return shortReason ? shortReason : reason;
}
render() {
const {actionSummaries, actions, viewUserDetail} = this.props;
const {showDetail} = this.state;
return (
<div className={styles.flagBox}>
<div className={styles.container}>
<div className={styles.header}>
<Icon name='flag'/><h3>{t('community.flags')} ({actionSummaries.length}):</h3>
<ul>
{actionSummaries.map((action, i) =>
<li key={i} className={styles.lessDetail}> {this.reasonMap(action.reason)} (<strong>{action.count}</strong>)</li>
)}
</ul>
<a onClick={this.toggleDetail} className={styles.moreDetail}>{showDetail ? t('modqueue.less_detail') : t('modqueue.more_detail')}</a>
</div>
{showDetail && (
<div className={styles.detail}>
<ul>
{actionSummaries.map((summary, i) => {
const actionList = actions.filter((a) => a.reason === summary.reason);
return (
<li key={i}>
{this.reasonMap(summary.reason)} (<strong>{summary.count}</strong>)
<ul>
{actionList.map((action, j) =>
<li key={`${i}_${j}`} className={styles.subDetail}>
{action.user &&
<a className={styles.username} onClick={() => viewUserDetail(action.user.id)}>
{action.user.username}
</a>
}
{action.message}
</li>
)}
</ul>
</li>
);
})}
</ul>
</div>
)}
</div>
</div>
);
}
}
FlagBox.propTypes = {
actionSummaries: PropTypes.arrayOf(PropTypes.shape({
reason: PropTypes.string,
count: PropTypes.number
})).isRequired,
actions: PropTypes.arrayOf(PropTypes.shape({
message: PropTypes.string,
user: PropTypes.shape({username: PropTypes.string})
})).isRequired
};
export default FlagBox;
@@ -1,9 +1,8 @@
import React from 'react';
import Linkify from 'react-linkify';
const linkify = new Linkify();
import {matchLinks} from '../utils';
export default ({text, children}) => {
const hasLinks = !!linkify.getMatches(text);
const hasLinks = !!matchLinks(text);
if (!hasLinks) {
return null;
@@ -1,4 +1,5 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {Button} from 'coral-ui';
import styles from './LoadMore.css';
import cn from 'classnames';
@@ -1,4 +1,5 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import Modal from 'components/Modal';
import styles from './ModerationKeysModal.css';
import t from 'coral-framework/services/i18n';
@@ -1,208 +0,0 @@
@custom-media --big-viewport (min-width: 780px);
.list {
padding: 8px 0;
list-style: none;
display: block;
&.singleView .listItem {
display: none;
}
&.singleView .listItem.activeItem {
display: block;
height: 100%;
font-size: 1.5em;
line-height: 1.5em;
border: none;
.actions {
position: fixed;
bottom: 60px;
left: 25%;
margin: 0 auto;
display: flex;
justify-content: space-around;
width: 50%;
margin: 0;
}
.actionButton {
transform: scale(1.4);
}
}
}
.listItem {
border-bottom: 1px solid #e0e0e0;
font-size: 16px;
width: 100%;
max-width: 660px;
min-width: 400px;
margin: 0 auto;
padding: 16px 14px;
position: relative;
transition: box-shadow 200ms;
&:hover {
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
&:last-child {
border-bottom: none;
}
.sideActions {
position: absolute;
right: 0;
height: 100%;
top: 0;
padding: 40px 18px;
box-sizing: border-box;
}
.itemHeader {
display: flex;
align-items: center;
justify-content: space-between;
.author {
min-width: 230px;
display: flex;
align-items: center;
}
}
.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;
}
.body {
margin-top: 20px;
flex: 1;
font-size: 0.88em;
color: black;
}
.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;
}
}
.selected {
border-radius: 10px;
}
.actionButton {
transform: scale(.8);
margin: 0;
}
.minimal {
width: 45px;
min-width: 0;
}
.approve__active {
box-shadow: none;
color: white;
background-color: #519954;
cursor: not-allowed;
}
.reject__active, .rejected__active {
color: white;
background-color: #D03235;
box-shadow: none;
cursor: not-allowed;
}
@@ -1,227 +0,0 @@
import React, {PropTypes} from 'react';
import styles from './ModerationList.css';
import key from 'keymaster';
import Hammer from 'hammerjs';
import Comment from './Comment';
import User from './User';
import SuspendUserModal from './SuspendUserModal';
// Each action has different meaning and configuration
const menuOptionsMap = {
'reject': {status: 'REJECTED', icon: 'close', key: 'f'},
'approve': {status: 'ACCEPTED', icon: 'done', key: 'd'},
'flag': {status: 'FLAGGED', icon: 'flag', filter: 'Untouched'},
'ban': {status: 'BANNED', icon: 'not interested'}
};
// Renders a comment list and allow performing actions
export default class ModerationList extends React.Component {
static propTypes = {
isActive: PropTypes.bool,
singleView: PropTypes.bool,
commentIds: PropTypes.arrayOf(PropTypes.string),
actionIds: PropTypes.arrayOf(PropTypes.string),
comments: PropTypes.object,
users: PropTypes.object.isRequired,
actions: PropTypes.object,
userStatusUpdate: PropTypes.func.isRequired,
suspendUser: PropTypes.func.isRequired,
// list of actions (flags, etc) associated with the comments
modActions: PropTypes.arrayOf(PropTypes.string).isRequired,
loading: PropTypes.bool,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired
}
state = {active: null, suspendUserModal: null, email: null};
// remove key handlers before leaving
componentWillUnmount () {
this.unbindKeyHandlers();
}
// add key handlers and gestures
componentDidMount () {
this.bindKeyHandlers();
// this.bindGestures() // need to check whether we're on a mobile device or this throws an Error
}
// If entering to singleview and no active, active is the first eleement
componentWillReceiveProps (nextProps) {
if (nextProps.singleView && !this.state.active) {
this.setState({active: nextProps.commentIds[0]});
}
}
// Add swipe to approve or reject
bindGestures () {
const {modActions} = this.props;
this._hammer = new Hammer(this.base);
this._hammer.get('swipe').set({direction: Hammer.DIRECTION_HORIZONTAL});
if (modActions.indexOf('reject') !== -1) {
this._hammer.on('swipeleft', () => this.props.singleView && this.actionKeyHandler('Rejected'));
}
if (modActions.indexOf('approve') !== -1) {
this._hammer.on('swiperight', () => this.props.singleView && this.actionKeyHandler('Approved'));
}
}
// Add key handlers. Each action has one and added j/k for moving around
bindKeyHandlers () {
const {modActions, isActive} = this.props;
modActions.filter((action) => menuOptionsMap[action].key).forEach((action) => {
key(menuOptionsMap[action].key, 'moderationList', () => isActive && this.actionKeyHandler(menuOptionsMap[action].status));
});
key('j', 'moderationList', () => isActive && this.moveKeyHandler('down'));
key('k', 'moderationList', () => isActive && this.moveKeyHandler('up'));
key.setScope('moderationList');
}
// Perform an action using the keys only if the comment is active
actionKeyHandler (action) {
if (this.props.isActive && this.state.active) {
this.onClickAction(action, this.state.active);
}
}
// move around with j/k
moveKeyHandler (direction) {
if (!this.props.isActive) {
return;
}
const {commentIds} = this.props;
const {active} = this.state;
// check boundaries
if (active === null || !commentIds.length) {
this.setState({active: commentIds[0]});
} else if (direction === 'up' && active !== commentIds[0]) {
this.setState({active: commentIds[commentIds.indexOf(active) - 1]});
} else if (direction === 'down' && active !== commentIds[commentIds.length - 1]) {
this.setState({active: commentIds[commentIds.indexOf(active) + 1]});
}
// scroll to the position
const index = Math.max(commentIds.indexOf(this.state.active), 0);
this.base.childNodes[index] && this.base.childNodes[index].focus();
}
unbindKeyHandlers () {
key.deleteScope('moderationList');
}
// If we are performing an action over a comment (aka removing from the list) we need to select a new active.
// TODO: In the future this can be improved and look at the actual state to
// resolve since the content of the list could change externally. For now it works as expected
onClickAction = (menuOption, id, action) => {
// activate the next comment
if (id === this.state.active) {
const moderationIds = this.getModerationIds();
if (moderationIds[moderationIds.length - 1] === this.state.active) {
this.setState({active: moderationIds[moderationIds.length - 2]});
} else {
this.setState({active: moderationIds[Math.min(moderationIds.indexOf(this.state.active) + 1, moderationIds.length - 1)]});
}
}
// Update the status right away if this is a comment
if (action.item_type === 'COMMENTS') {
this.props.updateCommentStatus(menuOption, id);
} else if (action.item_type === 'USERS') {
// If a user bio or name is rejected, bring up a dialog before suspending them.
if (menuOption === 'REJECTED') {
this.setState({suspendUserModal: action});
} else if (menuOption === 'ACCEPTED') {
this.props.userStatusUpdate('APPROVED', action.item_id);
}
}
}
onClickShowBanDialog = (userId, userName, commentId) => {
this.props.onClickShowBanDialog(userId, userName, commentId);
}
mapModItems = (itemId, index) => {
const {comments = {}, users, actions = {}, modActions, suspectWords, hideActive} = this.props;
const {active} = this.state;
// Because ids are unique, the id will either appear as an action or as a comment.
const item = comments[itemId] || actions[itemId];
let modItem;
if (item.body) {
// If the item is a comment...
const author = users[item.author_id];
modItem = <Comment
suspectWords={suspectWords}
comment={item}
author={author}
key={index}
index={index}
onClickAction={this.onClickAction}
onClickShowBanDialog={this.onClickShowBanDialog}
modActions={modActions}
menuOptionsMap={menuOptionsMap}
isActive={itemId === active}
hideActive={hideActive} />;
} else {
// If the item is an action...
const user = users[item.item_id];
modItem = user && <User
suspectWords={suspectWords}
action={item}
user={user}
key={index}
index={index}
onClickAction={this.onClickAction}
onClickShowBanDialog={this.onClickShowBanDialog}
modActions={modActions}
menuOptionsMap={menuOptionsMap}
isActive={itemId === active}
hideActive={hideActive} />;
}
return modItem;
}
getModerationIds = () => {
const {commentIds = [], actionIds = [], comments, actions} = this.props;
if (comments && actions) {
return [ ...commentIds, ...actionIds ].sort((a, b) => {
const itemA = comments[a] || actions[a];
const itemB = comments[b] || actions[b];
return itemB.updated_at - itemA.updated_at;
});
} else {
return comments ? commentIds : actionIds;
}
}
render () {
const {singleView, key, suspendUser} = this.props;
// Combine moderations and actions into a single stream and sort by most recently updated.
const moderationIds = this.getModerationIds();
return (
<ul
className={`${styles.list} ${singleView ? styles.singleView : ''}`} {...key}
id='moderationList'>
{moderationIds.map(this.mapModItems)}
<SuspendUserModal
action = {this.state.suspendUserModal}
onClose={() => this.setState({suspendUserModal:null})}
suspendUser={suspendUser} />
</ul>
);
}
}
@@ -0,0 +1,44 @@
.root {
display: block;
color: #D03235;
border: solid 1px #D03235;
background: white;
padding: 10px 11px;
box-sizing: border-box;
vertical-align: middle;
line-height: 24px;
font-size: 17px;
height: 47px;
border-radius: 3px;
text-transform: capitalize;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.03), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.09);
width: 129px;
transform: scale(.8);
margin: 0;
&:hover {
color: white;
background-color: #D03235;
box-shadow: none;
cursor: pointer;
}
}
.active {
color: white;
background-color: #D03235;
box-shadow: none;
&:hover {
cursor: not-allowed;
}
}
.minimal {
width: 45px;
min-width: 0;
}
.icon {
margin-right: 5px;
}
@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './RejectButton.css';
import {Icon} from 'coral-ui';
import t from 'coral-framework/services/i18n';
const RejectButton = ({active, minimal, onClick}) => {
const text = active ? t('modqueue.rejected') : t('modqueue.reject');
return (
<button
className={cn(styles.root, {[styles.minimal]: minimal, [styles.active]: active})}
onClick={onClick}
>
<Icon name={'close'} className={styles.icon} />
{!minimal && text}
</button>
);
};
RejectButton.propTypes = {
active: PropTypes.bool,
minimal: PropTypes.bool,
onClick: PropTypes.func,
};
export default RejectButton;
@@ -1,4 +1,5 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {Dialog} from 'coral-ui';
import {RadioGroup, Radio} from 'react-mdl';
import styles from './SuspendUserDialog.css';
@@ -65,7 +66,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 +104,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>
+44 -35
View File
@@ -5,7 +5,6 @@ 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';
@@ -28,16 +27,26 @@ export default class UserDetail extends React.Component {
bulkReject: PropTypes.func.isRequired,
}
rejectThenReload = (info) => {
this.props.rejectComment(info).then(() => {
rejectThenReload = async (info) => {
try {
await this.props.rejectComment(info);
this.props.data.refetch();
});
} catch (err) {
// TODO: handle error.
console.error(err);
}
}
acceptThenReload = (info) => {
this.props.acceptComment(info).then(() => {
acceptThenReload = async (info) => {
try {
await this.props.acceptComment(info);
this.props.data.refetch();
});
} catch (err) {
// TODO: handle error.
console.error(err);
}
}
showAll = () => {
@@ -60,6 +69,7 @@ export default class UserDetail extends React.Component {
renderLoaded() {
const {
data,
root,
root: {
user,
@@ -133,50 +143,49 @@ export default class UserDetail extends React.Component {
<Slot
fill="userProfile"
data={this.props.data}
queryData={root, user}
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>
)
? (
<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}
root={root}
data={data}
comment={comment}
selected={false}
suspectWords={suspectWords}
bannedWords={bannedWords}
actions={actionsMap[status]}
acceptComment={this.acceptThenReload}
rejectComment={this.rejectThenReload}
selected={selected}
@@ -190,7 +199,7 @@ export default class UserDetail extends React.Component {
className={styles.loadMore}
loadMore={loadMore}
showLoadMore={hasNextPage}
/>
/>
</Drawer>
</ClickOutside>
);
@@ -1,4 +1,5 @@
.root {
position: relative;
display: block;
margin: 0;
border-bottom: 1px solid #e0e0e0;
@@ -55,7 +56,7 @@
position: relative;
}
.commentType {
.labels {
position: absolute;
right: 0px;
}
@@ -1,40 +1,43 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {Link} from 'react-router';
import {Icon} from 'coral-ui';
import FlagBox from './FlagBox';
import CommentDetails from './CommentDetails';
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 CommentLabels from '../containers/CommentLabels';
import ApproveButton from './ApproveButton';
import RejectButton from 'coral-admin/src/components/RejectButton';
import t, {timeago} from 'coral-framework/services/i18n';
class UserDetailComment extends React.Component {
approve = () => (this.props.comment.status === 'ACCEPTED'
? null
: this.props.acceptComment({commentId: this.props.comment.id})
);
reject = () => (this.props.comment.status === 'REJECTED'
? null
: this.props.rejectComment({commentId: this.props.comment.id})
);
render() {
const {
actions = [],
comment,
viewUserDetail,
suspectWords,
bannedWords,
selected,
toggleSelect,
className,
user,
...props
data,
} = 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}
@@ -53,10 +56,13 @@ class UserDetailComment extends React.Component {
</span>
{
(comment.editing && comment.editing.edited)
? <span>&nbsp;<span className={styles.editedMarker}>({t('comment.edited')})</span></span>
: null
? <span>&nbsp;<span className={styles.editedMarker}>({t('comment.edited')})</span></span>
: null
}
<CommentType type={commentType} className={styles.commentType}/>
<div className={styles.labels}>
<CommentLabels comment={comment} />
</div>
</div>
<div className={styles.story}>
Story: {comment.asset.title}
@@ -64,7 +70,7 @@ class UserDetailComment extends React.Component {
</div>
<CommentAnimatedEdit body={comment.body}>
<div className={styles.bodyContainer}>
<p className={styles.body}>
<div className={styles.body}>
<CommentBodyHighlighter
suspectWords={suspectWords}
bannedWords={bannedWords}
@@ -78,7 +84,7 @@ class UserDetailComment extends React.Component {
>
<Icon name="open_in_new" /> {t('comment.view_context')}
</a>
</p>
</div>
<div className={styles.sideActions}>
<IfHasLink text={comment.body}>
<span className={styles.hasLinks}>
@@ -86,41 +92,26 @@ class UserDetailComment extends React.Component {
</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}))}
/>
);
})}
<ApproveButton
active={comment.status === 'ACCEPTED'}
onClick={this.approve}
minimal
/>
<RejectButton
active={comment.status === 'REJECTED'}
onClick={this.reject}
minimal
/>
</div>
</div>
</div>
</CommentAnimatedEdit>
</div>
{flagActions && flagActions.length
? <FlagBox
actions={flagActions}
actionSummaries={flagActionSummaries}
viewUserDetail={viewUserDetail}
/>
: null}
<CommentDetails
data={data}
root={root}
comment={comment}
/>
</li>
);
}
@@ -136,8 +127,9 @@ UserDetailComment.propTypes = {
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
toggleSelect: PropTypes.func,
comment: PropTypes.shape({
id: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
body: PropTypes.string.isRequired,
action_summaries: PropTypes.array,
actions: PropTypes.array,
created_at: PropTypes.string.isRequired,
asset: PropTypes.shape({
@@ -1,4 +1,5 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {Navigation, Drawer} from 'react-mdl';
import {IndexLink, Link} from 'react-router';
import styles from './Drawer.css';
+72 -62
View File
@@ -1,68 +1,76 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {Navigation, Header, IconButton, MenuItem, Menu} from 'react-mdl';
import {Link, IndexLink} from 'react-router';
import styles from './Header.css';
import t from 'coral-framework/services/i18n';
import {Logo} from './Logo';
import {can} from 'coral-framework/services/perms';
import Indicator from './Indicator';
const CoralHeader = ({
handleLogout,
showShortcuts = () => {},
auth
}) => (
<Header className={styles.header}>
<Logo className={styles.logo} />
<div>
auth,
root
}) => {
return (
<Header className={styles.header}>
<Logo className={styles.logo} />
<div>
{
auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
<Navigation className={styles.nav}>
<IndexLink
id='dashboardNav'
className={styles.navLink}
to="/admin/dashboard"
activeClassName={styles.active}>
{t('configure.dashboard')}
</IndexLink>
{
can(auth.user, 'MODERATE_COMMENTS') && (
<Link
id='moderateNav'
className={styles.navLink}
to="/admin/moderate"
activeClassName={styles.active}>
{t('configure.moderate')}
</Link>
)
}
<Link
id='streamsNav'
className={styles.navLink}
to="/admin/stories"
activeClassName={styles.active}>
{t('configure.stories')}
</Link>
<Link
id='communityNav'
className={styles.navLink}
to="/admin/community"
activeClassName={styles.active}>
{t('configure.community')}
</Link>
{
can(auth.user, 'UPDATE_CONFIG') && (
<Link
id='configureNav'
className={styles.navLink}
to="/admin/configure"
activeClassName={styles.active}>
{t('configure.configure')}
</Link>
)
}
</Navigation>
:
null
<Navigation className={styles.nav}>
<IndexLink
id='dashboardNav'
className={styles.navLink}
to="/admin/dashboard"
activeClassName={styles.active}>
{t('configure.dashboard')}
</IndexLink>
{
can(auth.user, 'MODERATE_COMMENTS') && (
<Link
id='moderateNav'
className={styles.navLink}
to="/admin/moderate"
activeClassName={styles.active}>
{t('configure.moderate')}
{(root.premodCount !== 0 || root.reportedCount !== 0) && <Indicator />}
</Link>
)
}
<Link
id='streamsNav'
className={styles.navLink}
to="/admin/stories"
activeClassName={styles.active}>
{t('configure.stories')}
</Link>
<Link
id='communityNav'
className={styles.navLink}
to="/admin/community"
activeClassName={styles.active}>
{t('configure.community')}
{root.flaggedUsernamesCount !== 0 && <Indicator />}
</Link>
{
can(auth.user, 'UPDATE_CONFIG') && (
<Link
id='configureNav'
className={styles.navLink}
to="/admin/configure"
activeClassName={styles.active}>
{t('configure.configure')}
</Link>
)
}
</Navigation>
:
null
}
<div className={styles.rightPanel}>
<ul>
@@ -72,13 +80,13 @@ const CoralHeader = ({
<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 href="https://github.com/coralproject/talk/releases" target="_blank" rel="noopener noreferrer">
View latest version
</a>
</MenuItem>
<MenuItem>
<a href="https://coralproject.net/contribute.html#other-ideas-and-bug-reports" target="_blank">
Report a bug or give feedback
<a href="https://coralproject.net/contribute.html#other-ideas-and-bug-reports" target="_blank" rel="noopener noreferrer">
Report a bug or give feedback
</a>
</MenuItem>
<MenuItem onClick={handleLogout}>
@@ -87,19 +95,21 @@ const CoralHeader = ({
</Menu>
</div>
</li>
<li>
{`v${process.env.VERSION}`}
<li>
{`v${process.env.VERSION}`}
</li>
</ul>
</div>
</div>
</Header>
);
</Header>
);
};
CoralHeader.propTypes = {
auth: PropTypes.object,
showShortcuts: PropTypes.func,
handleLogout: PropTypes.func.isRequired
handleLogout: PropTypes.func.isRequired,
root: PropTypes.object.isRequired
};
export default CoralHeader;
@@ -0,0 +1,10 @@
.indicator {
background-color: #E46D59;
border-radius: 10px;
position: absolute;
top: 50%;
width: 7px;
height: 7px;
margin-top: -4px;
margin-left: 7px;
}
@@ -0,0 +1,7 @@
import React from 'react';
import styles from './Indicator.css';
const Indicator = () =>
<span className={styles.indicator}></span>;
export default Indicator;
+14 -6
View File
@@ -1,21 +1,27 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {Layout as LayoutMDL} from 'react-mdl';
import Header from './Header';
import Header from '../../containers/Header';
import Drawer from './Drawer';
import styles from './Layout.css';
const Layout = ({
children,
handleLogout = () => {},
toggleShortcutModal,
toggleShortcutModal = () => {},
restricted = false,
...props}) => (
auth,
}) => (
<LayoutMDL className={styles.layout} fixedDrawer>
<Header
handleLogout={handleLogout}
showShortcuts={toggleShortcutModal}
{...props} />
<Drawer handleLogout={handleLogout} restricted={restricted} {...props} />
auth={auth}
/>
<Drawer
handleLogout={handleLogout}
restricted={restricted}
/>
<div className={styles.layout}>
{children}
</div>
@@ -23,6 +29,8 @@ const Layout = ({
);
Layout.propTypes = {
children: PropTypes.node,
auth: PropTypes.object,
handleLogout: PropTypes.func,
toggleShortcutModal: PropTypes.func,
restricted: PropTypes.bool // hide elements from a user that's logged out
@@ -1,6 +1,7 @@
import React from 'react';
import styles from './Logo.css';
import {CoralLogo} from 'coral-ui';
import PropTypes from 'prop-types';
export const Logo = ({className = ''}) => (
<div className={`${styles.logo} ${className}`}>
@@ -10,3 +11,7 @@ export const Logo = ({className = ''}) => (
</h1>
</div>
);
Logo.propTypes = {
className: PropTypes.string
};
@@ -6,15 +6,22 @@ import {hideBanUserDialog} from '../actions/banUserDialog';
import {withSetUserStatus, withSetCommentStatus} from 'coral-framework/graphql/mutations';
import {compose} from 'react-apollo';
import t from 'coral-framework/services/i18n';
import {getErrorMessages} from 'coral-framework/utils';
import {notify} from 'coral-framework/actions/notification';
class BanUserDialogContainer extends Component {
banUser = async () => {
const {userId, commentId, commentStatus, setUserStatus, setCommentStatus, hideBanUserDialog} = this.props;
await setUserStatus({userId, status: 'BANNED'});
hideBanUserDialog();
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
await setCommentStatus({commentId, status: 'REJECTED'});
const {userId, commentId, commentStatus, setUserStatus, setCommentStatus, hideBanUserDialog, notify} = this.props;
try {
await setUserStatus({userId, status: 'BANNED'});
hideBanUserDialog();
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
await setCommentStatus({commentId, status: 'REJECTED'});
}
}
catch(err) {
notify('error', getErrorMessages(err));
}
}
@@ -50,6 +57,7 @@ const mapStateToProps = ({banUserDialog: {open, userId, username, commentId, com
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
hideBanUserDialog,
notify,
}, dispatch),
});
@@ -0,0 +1,24 @@
import {gql} from 'react-apollo';
import CommentDetails from '../components/CommentDetails';
import {getSlotFragmentSpreads} from 'coral-framework/utils';
import withFragments from 'coral-framework/hocs/withFragments';
const slots = [
'adminCommentDetailArea',
'adminCommentMoreDetails',
];
export default withFragments({
root: gql`
fragment CoralAdmin_CommentDetails_root on RootQuery {
__typename
${getSlotFragmentSpreads(slots, 'root')}
}
`,
comment: gql`
fragment CoralAdmin_CommentDetails_comment on Comment {
__typename
${getSlotFragmentSpreads(slots, 'comment')}
}
`
})(CommentDetails);
@@ -0,0 +1,33 @@
import {gql} from 'react-apollo';
import CommentLabels from '../components/CommentLabels';
import withFragments from 'coral-framework/hocs/withFragments';
import {getSlotFragmentSpreads} from 'coral-framework/utils';
const slots = [
'adminCommentLabels',
];
export default withFragments({
root: gql`
fragment CoralAdmin_CommentLabels_root on RootQuery {
__typename
${getSlotFragmentSpreads(slots, 'root')}
}
`,
comment: gql`
fragment CoralAdmin_CommentLabels_comment on Comment {
hasParent
status
actions {
__typename
... on FlagAction {
reason
}
user {
id
}
}
${getSlotFragmentSpreads(slots, 'comment')}
}
`
})(CommentLabels);
@@ -0,0 +1,24 @@
import {gql} from 'react-apollo';
import withQuery from 'coral-framework/hocs/withQuery';
import Header from '../components/ui/Header';
export default withQuery(gql`
query TalkAdmin_Header {
__typename
premodCount: commentCount(query: {
statuses: [PREMOD]
})
reportedCount: commentCount(query: {
statuses: [NONE, PREMOD, SYSTEM_WITHHELD],
action_type: FLAG
})
flaggedUsernamesCount: userCount(query: {
action_type: FLAG,
statuses: [PENDING]
})
}
`, {
options: {
pollInterval: 5000
}
})(Header);
+42 -23
View File
@@ -1,25 +1,28 @@
import React, {Component} from 'react';
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from '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';
import PropTypes from 'prop-types';
class LayoutContainer extends Component {
class LayoutContainer extends React.Component {
componentWillMount() {
const {checkLogin, fetchConfig} = this.props;
checkLogin();
fetchConfig();
}
render() {
const {
user,
loggedIn,
@@ -30,13 +33,16 @@ class LayoutContainer extends Component {
} = this.props.auth;
const {
handleLogout,
children,
logout,
toggleShortcutModal,
TALK_RECAPTCHA_PUBLIC
TALK_RECAPTCHA_PUBLIC,
} = this.props;
if (loadingUser) {
return <FullLoading />;
}
if (!loggedIn) {
return (
<AdminLogin
@@ -49,17 +55,17 @@ class LayoutContainer extends Component {
/>
);
}
if (can(user, 'ACCESS_ADMIN') && loggedIn) {
return (
<Layout
handleLogout={handleLogout}
handleLogout={logout}
toggleShortcutModal={toggleShortcutModal}
{...this.props}
>
<BanUserDialog />
<SuspendUserDialog />
<UserDetail />
{this.props.children}
auth={this.props.auth} >
<BanUserDialog />
<SuspendUserDialog />
<UserDetail />
{children}
</Layout>
);
} else if (loggedIn) {
@@ -73,19 +79,32 @@ class LayoutContainer extends Component {
}
}
LayoutContainer.propTypes = {
children: PropTypes.node,
requestPasswordReset: PropTypes.func,
handleLogin: PropTypes.func,
auth: PropTypes.object,
handleLogout: PropTypes.func,
logout: PropTypes.func,
toggleShortcutModal: PropTypes.func,
TALK_RECAPTCHA_PUBLIC: PropTypes.string,
checkLogin: PropTypes.func,
fetchConfig: PropTypes.func
};
const mapStateToProps = (state) => ({
auth: state.auth,
TALK_RECAPTCHA_PUBLIC: state.config.data.TALK_RECAPTCHA_PUBLIC,
});
const mapDispatchToProps = (dispatch) => ({
checkLogin: () => dispatch(checkLogin()),
fetchConfig: () => dispatch(fetchConfig()),
handleLogin: (username, password, recaptchaResponse) =>
dispatch(handleLogin(username, password, recaptchaResponse)),
requestPasswordReset: (email) => dispatch(requestPasswordReset(email)),
toggleShortcutModal: (toggle) => dispatch(toggleShortcutModal(toggle)),
handleLogout: () => dispatch(logout())
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
checkLogin,
fetchConfig,
handleLogin,
requestPasswordReset,
toggleShortcutModal,
logout
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(LayoutContainer);
@@ -5,35 +5,29 @@ 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(
await suspendUser({id: userId, message, until});
notify(
'success',
t('suspenduser.notify_suspend_until', username, timeago(until)),
);
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
return setCommentStatus({commentId, status: 'REJECTED'})
.then((result) => {
if (result.data.setCommentStatus.errors) {
throw result.data.setCommentStatus.errors;
}
});
await setCommentStatus({commentId, status: 'REJECTED'});
}
}
catch(err) {
notification.showMutationErrors(err);
notify('error', getErrorMessages(err));
}
};
@@ -52,8 +46,9 @@ class SuspendUserDialogContainer extends Component {
const withOrganizationName = withQuery(gql`
query CoralAdmin_SuspendUserDialog {
__typename
settings {
organizationName
organizationName
}
}
`);
@@ -69,6 +64,7 @@ const mapStateToProps = ({suspendUserDialog: {open, userId, username, commentId,
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
hideSuspendUserDialog,
notify,
}, dispatch),
});
+18 -11
View File
@@ -36,14 +36,19 @@ class UserDetailContainer extends React.Component {
isLoadingMore = false;
// status can be 'ACCEPTED' or 'REJECTED'
bulkSetCommentStatus = (status) => {
bulkSetCommentStatus = async (status) => {
const changes = this.props.selectedCommentIds.map((commentId) => {
return this.props.setCommentStatus({commentId, status});
});
Promise.all(changes).then(() => {
try {
await Promise.all(changes);
this.props.clearUserDetailSelections(); // un-select everything
});
} catch (err) {
// TODO: handle error.
console.error(err);
}
}
bulkReject = () => {
@@ -88,13 +93,13 @@ class UserDetailContainer extends React.Component {
});
}
})
.then(() => {
this.isLoadingMore = false;
})
.catch((err) => {
this.isLoadingMore = false;
throw err;
});
.then(() => {
this.isLoadingMore = false;
})
.catch((err) => {
this.isLoadingMore = false;
throw err;
});
};
componentWillReceiveProps(next) {
@@ -124,7 +129,7 @@ class UserDetailContainer extends React.Component {
}
const LOAD_MORE_QUERY = gql`
query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Date, $author_id: ID!, $statuses: [COMMENT_STATUS!]) {
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
}
@@ -155,8 +160,10 @@ export const withUserDetailQuery = withQuery(gql`
}) {
...CoralAdmin_Moderation_CommentConnection
}
...${getDefinitionName(UserDetailComment.fragments.root)}
${getSlotFragmentSpreads(slots, 'root')}
}
${UserDetailComment.fragments.root}
${commentConnectionFragment}
`, {
options: ({userId, statuses}) => {
@@ -1,39 +1,39 @@
import {gql} from 'react-apollo';
import UserDetailComment from '../components/UserDetailComment';
import withFragments from 'coral-framework/hocs/withFragments';
import {getDefinitionName} from 'coral-framework/utils';
import CommentLabels from './CommentLabels';
import CommentDetails from './CommentDetails';
export default withFragments({
root: gql`
fragment CoralAdmin_UserDetailComment_root on RootQuery {
__typename
...${getDefinitionName(CommentLabels.fragments.root)}
...${getDefinitionName(CommentDetails.fragments.root)}
}
${CommentLabels.fragments.root}
${CommentDetails.fragments.root}
`,
comment: gql`
fragment CoralAdmin_UserDetail_comment on Comment {
fragment CoralAdmin_UserDetailComment_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
}
...${getDefinitionName(CommentLabels.fragments.comment)}
...${getDefinitionName(CommentDetails.fragments.comment)}
}
${CommentLabels.fragments.comment}
${CommentDetails.fragments.comment}
`
})(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_Community: (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_Community: (prev) => {
const updated = update(prev, {
users: {
nodes: {$apply: (nodes) => nodes.filter((node) => node.id !== userId)},
},
});
return updated;
}
}
}),
},
};
add(extension);
+25 -23
View File
@@ -1,32 +1,34 @@
import React from 'react';
import {render} from 'react-dom';
import {ApolloProvider} from 'react-apollo';
import smoothscroll from 'smoothscroll-polyfill';
import EventEmitter from 'eventemitter2';
import EventEmitterProvider from 'coral-framework/components/EventEmitterProvider';
import {getClient} from 'coral-framework/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';
const eventEmitter = new EventEmitter();
// TODO: pass redux actions through the emitter.
loadPluginsTranslations();
injectPluginsReducers();
smoothscroll.polyfill();
render(
<EventEmitterProvider eventEmitter={eventEmitter}>
<ApolloProvider client={getClient()} store={store}>
function init({store, storage}) {
if (storage && storage.getItem('coral:shortcutsNote') === 'hide') {
store.dispatch(hideShortcutsNote());
}
}
async function main() {
const notification = createNotificationService(toast);
const context = await createContext({reducers, graphqlExtension, pluginsConfig, notification, init});
render(
<TalkProvider {...context}>
<App />
</ApolloProvider>
</EventEmitterProvider>
, document.querySelector('#root')
);
</TalkProvider>
, document.querySelector('#root')
);
}
main();
@@ -5,14 +5,17 @@ const initialState = {
modalOpen: false,
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;
return {
...initialState,
shortcutsNoteVisible: state.shortcutsNoteVisible,
};
case actions.TOGGLE_MODAL:
return {
...state,
@@ -3,7 +3,7 @@ import * as actions from '../constants/userDetail';
const initialState = {
userId: null,
activeTab: 'all',
statuses: ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'],
statuses: null,
selectedCommentIds: [],
};
@@ -1,21 +0,0 @@
import React from 'react';
import styles from './Community.css';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../../../utils/moderationQueueActionsMap';
import t from 'coral-framework/services/i18n';
const ActionButton = ({type = '', user, ...props}) => {
return (
<Button
className={`${type.toLowerCase()} ${styles.actionButton}`}
cStyle={type.toLowerCase()}
icon={menuActionsMap[type].icon}
onClick={() => {
type === 'APPROVE' ? props.approveUser({userId: user.id}) : props.showRejectUsernameDialog({user: user});
}}
>{t(`modqueue.${menuActionsMap[type].text}`)}</Button>
);
};
export default ActionButton;
@@ -1,299 +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;
}
}
.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;
}
.button {
composes: buttonReset from 'coral-framework/styles/reset.css';
vertical-align: text-bottom;
&:hover {
background-color: #E0E0E0;
}
}
@@ -2,8 +2,9 @@ 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';
import PropTypes from 'prop-types';
export default class Community extends Component {
@@ -54,7 +55,7 @@ export default class Community extends Component {
}
getTabContent(searchValue, props) {
const {community, root: {users}, viewUserDetail} = props;
const {community} = props;
const activeTab = props.route.path === ':id' ? 'flagged' : props.route.path;
if (activeTab === 'people') {
@@ -77,14 +78,8 @@ 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}
viewUserDetail={viewUserDetail}
data={this.props.data}
root={this.props.root}
/>
<RejectUsernameDialog
open={community.rejectUsernameDialog}
@@ -99,15 +94,25 @@ export default class Community extends Component {
render() {
const {searchValue} = this.state;
const tab = this.getTabContent(searchValue, this.props);
const {root: {flaggedUsernamesCount}} = this.props;
return (
<div>
<CommunityMenu />
<div>
{ tab }
</div>
<CommunityMenu flaggedUsernamesCount={flaggedUsernamesCount} />
<div>{tab}</div>
</div>
);
}
}
Community.propTypes = {
community: PropTypes.object,
fetchAccounts: PropTypes.func,
hideRejectUsernameDialog: PropTypes.func,
updateSorting: PropTypes.func,
newPage: PropTypes.func,
route: PropTypes.object,
rejectUsername: PropTypes.func,
data: PropTypes.object,
root: PropTypes.object
};
@@ -1,9 +0,0 @@
import React from 'react';
const CommunityLayout = (props) => (
<div>
{props.children}
</div>
);
export default CommunityLayout;
@@ -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,18 +1,21 @@
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';
import PropTypes from 'prop-types';
import CountBadge from '../../../components/CountBadge';
const CommunityMenu = () => {
const CommunityMenu = ({flaggedUsernamesCount = 0}) => {
const flaggedPath = '/admin/community/flagged';
const peoplePath = '/admin/community/people';
return (
<div className='mdl-tabs'>
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<div>
<Link to={flaggedPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{t('community.flaggedaccounts')}
<CountBadge count={flaggedUsernamesCount} />
</Link>
<Link to={peoplePath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{t('community.people')}
@@ -23,4 +26,8 @@ const CommunityMenu = () => {
);
};
CommunityMenu.propTypes = {
flaggedUsernamesCount: PropTypes.number,
};
export default CommunityMenu;
@@ -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,38 +1,73 @@
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}
viewUserDetail={props.viewUserDetail}
/>;
})
: <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}
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,144 @@
import React from 'react';
import styles from './FlaggedUser.css';
// 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 ApproveButton from 'coral-admin/src/components/ApproveButton';
import RejectButton from 'coral-admin/src/components/RejectButton';
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);
showRejectUsernameDialog = () => this.props.showRejectUsernameDialog({id: this.props.user.id});
approveUser = () => this.props.approveUser({
userId: this.props.user.id,
});
render() {
const {
user,
viewUserDetail,
selected,
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}>
<ApproveButton
onClick={this.approveUser}
/>
<RejectButton
onClick={this.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}
@@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Dialog, Button} from 'coral-ui';
import styles from './RejectUsernameDialog.css';
@@ -48,11 +49,15 @@ class RejectUsernameDialog extends Component {
const cancel = this.props.handleClose;
const next = () => this.setState({stage: stage + 1});
const suspend = () => {
rejectUsername({id: user.user.id, message: this.state.email})
.then(() => {
const suspend = async () => {
try {
await rejectUsername({id: user.id, message: this.state.email});
this.props.handleClose();
});
} catch (err) {
// TODO: handle error.
console.error(err);
}
};
const suspendModalActions = [
@@ -71,21 +76,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 +101,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>;
}
}
@@ -9,9 +9,9 @@ export default ({headers, commenters, onHeaderClickHandler, onRoleChange, onComm
<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>
))}
@@ -28,7 +28,8 @@ export default ({headers, commenters, onHeaderClickHandler, onRoleChange, onComm
{row.created_at}
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.status || ''}
<SelectField
value={row.status || ''}
className={styles.selectField}
label={t('community.status')}
onChange={(status) => onCommenterStatusChange(row.id, status)}>
@@ -37,7 +38,8 @@ export default ({headers, commenters, onHeaderClickHandler, onRoleChange, onComm
</SelectField>
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.roles[0] || ''}
<SelectField
value={row.roles[0] || ''}
className={styles.selectField}
label={t('community.role')}
onChange={(role) => onRoleChange(row.id, role)}>
@@ -1,117 +0,0 @@
import React from 'react';
import styles from './Community.css';
import ActionButton from './ActionButton';
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 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}>
<button onClick={() => {props.viewUserDetail(user.id);}} className={styles.button}>{user.username}</button>
{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 &&
<button onClick={() => {props.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={`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,23 +1,20 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import PropTypes from 'prop-types';
import {compose, gql} from 'react-apollo';
import withQuery from 'coral-framework/hocs/withQuery';
import {Spinner} from 'coral-ui';
import {getDefinitionName} from 'coral-framework/utils';
import {withSetUserStatus, withRejectUsername} from 'coral-framework/graphql/mutations';
import {showBanUserDialog} from 'actions/banUserDialog';
import {showSuspendUserDialog} from 'actions/suspendUserDialog';
import FlaggedAccounts from '../containers/FlaggedAccounts';
import FlaggedUser from '../containers/FlaggedUser';
import {
fetchAccounts,
updateSorting,
newPage,
showRejectUsernameDialog,
hideRejectUsernameDialog
} from '../../../actions/community';
import {viewUserDetail} from '../../../actions/userDetail';
import Community from '../components/Community';
@@ -27,85 +24,69 @@ 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}/>
);
return <Community
fetchAccounts={this.props.fetchAccounts}
community={this.props.community}
hideRejectUsernameDialog={this.props.hideRejectUsernameDialog}
updateSorting={this.props.updateSorting}
newPage={this.props.newPage}
route={this.props.route}
rejectUsername={this.props.rejectUsername}
data={this.props.data}
root={this.props.root}
/>;
}
}
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,
currentUser: state.auth.user,
});
CommunityContainer.propTypes = {
community: PropTypes.object,
fetchAccounts: PropTypes.func,
hideRejectUsernameDialog: PropTypes.func,
updateSorting: PropTypes.func,
newPage: PropTypes.func,
route: PropTypes.object,
rejectUsername: PropTypes.func,
data: PropTypes.object,
root: PropTypes.object
};
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
fetchAccounts,
showBanUserDialog,
showSuspendUserDialog,
showRejectUsernameDialog,
hideRejectUsernameDialog,
updateSorting,
newPage,
viewUserDetail,
}, dispatch);
const withData = withQuery(gql`
query TalkAdmin_Community {
flaggedUsernamesCount: userCount(query: {
action_type: FLAG,
statuses: [PENDING]
})
...${getDefinitionName(FlaggedAccounts.fragments.root)}
...${getDefinitionName(FlaggedUser.fragments.root)}
me {
...${getDefinitionName(FlaggedUser.fragments.me)}
__typename
}
}
${FlaggedAccounts.fragments.root}
${FlaggedUser.fragments.root}
${FlaggedUser.fragments.me}
`, {
options: {
fetchPolicy: 'network-only',
},
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withCommunityQuery,
withSetUserStatus,
withRejectUsername,
withData
)(CommunityContainer);
@@ -0,0 +1,134 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose, gql} from 'react-apollo';
import {withFragments} from 'plugin-api/beta/client/hocs';
import {Spinner} from 'coral-ui';
import PropTypes from 'prop-types';
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}
/>
);
}
}
FlaggedAccountsContainer.propTypes = {
showBanUserDialog: PropTypes.func,
showSuspendUserDialog: PropTypes.func,
showRejectUsernameDialog: PropTypes.func,
viewUserDetail: PropTypes.func,
setUserStatus: PropTypes.func,
data: PropTypes.object,
root: PropTypes.object
};
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}
`;
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
showBanUserDialog,
showSuspendUserDialog,
showRejectUsernameDialog,
viewUserDetail,
}, dispatch);
export default compose(
connect(null, mapDispatchToProps),
withSetUserStatus,
withFragments({
root: gql`
fragment TalkAdminCommunity_FlaggedAccounts_root on RootQuery {
users(query:{action_type: FLAG, statuses: [PENDING], limit: 10}){
hasNextPage
endCursor
nodes {
__typename
...${getDefinitionName(FlaggedUser.fragments.user)}
}
}
me {
id
}
}
${FlaggedUser.fragments.user}
`,
}),
)(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);

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