Compare commits

...
305 Commits
Author SHA1 Message Date
Kim Gardner 9d25a4c14a Merge pull request #661 from coralproject/comment-stream-cleanup
Refinement of design for banned and suspend user messaging on comment stream.
2017-06-07 17:12:25 -04:00
Kim Gardner 801d0a92af Merge branch 'master' into comment-stream-cleanup 2017-06-07 17:07:39 -04:00
David Erwin 8bf86f34ee Merge pull request #654 from coralproject/off-topic-features
Off Topic Features
2017-06-07 15:43:03 -04:00
Kim Gardner 14a35033da Merge branch 'master' into comment-stream-cleanup 2017-06-07 14:33:29 -04:00
Chi Vinh Le 05da69d097 Remove falsely commited css styles 2017-06-08 01:23:22 +07:00
Chi Vinh Le ffce78ef60 Move Icon and translation service to plugin-api 2017-06-08 01:18:02 +07:00
Chi Vinh Le 6561f733b7 Use correct prefix 2017-06-08 01:17:36 +07:00
Chi Vinh Le b9758ba942 Fix overlap not clickable 2017-06-08 00:59:44 +07:00
Chi Vinh Le 4e56406c37 Fix tabsize in css 2017-06-08 00:49:18 +07:00
Chi Vinh Le 06858bf071 Move Slot and withReaction to beta 2017-06-08 00:42:21 +07:00
Chi Vinh Le 66e4f02a1a Selectors, resolving merge errors, better class names, removing comment reducer 2017-06-08 00:29:05 +07:00
IAmSamHankins c3d76c6a4f messaging style updates, and update to translation files. 2017-06-07 12:55:40 -04:00
Belen Curcio c8534a91a6 Merge branch 'off-topic-features' of github.com:coralproject/talk into off-topic-features 2017-06-07 13:53:54 -03:00
Belen Curcio 87ddfea689 indexes css 2017-06-07 13:53:33 -03:00
Belén Curcio 830441c531 Merge branch 'master' into off-topic-features 2017-06-07 13:24:58 -03:00
Riley Davis 7417d51973 Merge pull request #660 from coralproject/math-is-hard
percents have to be multiplied by 100
2017-06-07 10:22:45 -06:00
Belen Curcio 847a30ff92 Adding correct classNames 2017-06-07 13:19:36 -03:00
Riley Davis 9f4df35c3a percents have to be multiplied by 100 2017-06-07 10:15:50 -06:00
Belen Curcio f6ad9c9373 Adding camelcase for plugin reducers 🎉 <3 2017-06-07 13:14:52 -03:00
Belen Curcio 8872248d63 Addiing Stable API Architecture 2017-06-07 13:10:17 -03:00
Belen Curcio d780eea357 Merge branch 'off-topic-features' of github.com:coralproject/talk into off-topic-features 2017-06-07 12:38:31 -03:00
Belen Curcio 20aba92ad2 Linting 2017-06-07 12:37:59 -03:00
Chi Vinh Le 7a2fd95eca Merge branch 'master' into off-topic-features
Conflicts:
	client/coral-embed-stream/src/components/Comment.css
	client/coral-embed-stream/src/components/Comment.js
	client/coral-embed-stream/src/components/Stream.js
	client/coral-embed-stream/src/reducers/index.js
	client/coral-framework/helpers/plugins.js
2017-06-07 22:37:06 +07:00
Belen Curcio d7dfae89a3 Global classNames, and more 2017-06-07 12:05:53 -03:00
David Erwin fa33fe2ed2 Merge pull request #657 from coralproject/better-refetch-detection
Better refetch detection
2017-06-07 09:29:19 -04:00
Belen Curcio 456eedfd31 Clearing tags and naming convention 2017-06-07 10:04:52 -03:00
Belen Curcio 4725f07757 Viewing Options state and name conventions 2017-06-07 09:53:53 -03:00
Belen Curcio e24f6ca9f1 Renaming to commentClassNames 2017-06-07 09:28:12 -03:00
Chi Vinh Le ceb99cf205 Better refetch detection 2017-06-07 17:45:33 +07:00
Kiwi e47d3b085e Merge pull request #656 from coralproject/service-fix
Resolve facebook login issue
2017-06-07 16:57:19 +07:00
Belén Curcio 5e1fcc48e1 Merge branch 'master' into service-fix 2017-06-06 20:45:06 -03:00
Belen Curcio b5c9e92cc6 Adding streamBox slot and coral-plugin-viewing-options 🎉 2017-06-06 20:42:42 -03:00
Wyatt Johnson 71ff1c31f8 moved plugin to lazy load 2017-06-06 16:49:51 -06:00
IAmSamHankins cc76517ba8 minor copy changes in Configure tab on stream, and style updates to banned user message on stream 2017-06-06 17:37:14 -04:00
Belen Curcio 6f909c3451 Containers and Components structure 2017-06-06 18:21:03 -03:00
Belen Curcio 2202c8ae5b Fully working filter 2017-06-06 18:08:13 -03:00
Kim Gardner 6d8133b1b5 Merge pull request #655 from coralproject/move-config
move edit comment config
2017-06-06 16:57:48 -04:00
Riley Davis 2384ae4baa move edit comment config 2017-06-06 14:49:07 -06:00
Belen Curcio 1c7ad9a8ac Plugin reducers working again with reducer name 2017-06-06 17:29:56 -03:00
Belen Curcio c3360aa020 Plugin reducers working again 2017-06-06 17:23:35 -03:00
Belen Curcio 4aa7900769 Not connecting comment Component to the Store 2017-06-06 17:09:13 -03:00
Riley Davis 0dc61f33f5 Merge pull request #646 from coralproject/bulk-actions
bulk operations on User Detail comments
2017-06-06 13:26:28 -06:00
Riley Davis 8af0d929c2 Merge branch 'master' into bulk-actions 2017-06-06 13:15:04 -06:00
Riley Davis 4e19d5eecf don't have functions re-created every render 2017-06-06 13:11:31 -06:00
Riley Davis 5465563856 reload and clear selections 2017-06-06 12:52:26 -06:00
Belen Curcio b5a64bd2f9 Filter State 2017-06-06 15:45:56 -03:00
Kim Gardner b02782d532 Merge pull request #653 from coralproject/support-wss
Use wss when connection is secure
2017-06-06 14:39:35 -04:00
Chi Vinh Le 7cb05cebef Use wss when connection is secure 2017-06-07 01:32:37 +07:00
Kim Gardner 80d4ffa5bf Merge branch 'master' into bulk-actions 2017-06-06 14:13:47 -04:00
Riley Davis 9963f6d337 Merge pull request #651 from ebirving/active-tab-classnames
add semantic classname to active tab
2017-06-06 12:11:44 -06:00
Kim Gardner eff923424c Merge branch 'master' into bulk-actions 2017-06-06 14:11:16 -04:00
Erica Irving c3e32f37dc add coral- prefix as requested 2017-06-06 13:43:45 -04:00
Riley Davis 18f11a84cf Merge branch 'master' into active-tab-classnames 2017-06-06 11:14:47 -06:00
Riley Davis b0bdfff785 Merge pull request #649 from ebirving/respect-plugin-styles
add classnames to RespectButton component
2017-06-06 11:14:32 -06:00
Riley Davis dc55febac8 Merge branch 'master' into respect-plugin-styles 2017-06-06 11:09:36 -06:00
David Erwin 4d6e02228c Merge pull request #643 from coralproject/subscribe-to-comments
Subscribe to comment updates
2017-06-06 13:00:58 -04:00
Chi Vinh Le c50fe9f91d Merge branch 'master' into subscribe-to-comments
Conflicts:
	client/coral-embed-stream/src/components/Comment.js
2017-06-06 23:44:27 +07:00
Riley Davis 7d5a73bc95 Merge branch 'master' into respect-plugin-styles 2017-06-06 10:40:33 -06:00
Riley Davis 5f3220dba9 Merge pull request #652 from coralproject/css-override-classes
Css override classes + tab bugfix
2017-06-06 10:40:21 -06:00
Kim Gardner 700ab38cb0 Merge branch 'master' into css-override-classes 2017-06-06 12:34:13 -04:00
Riley Davis 6fcd003e76 remove console.log 2017-06-06 10:32:14 -06:00
Kiwi 0069d17672 Merge branch 'master' into subscribe-to-comments 2017-06-06 23:19:22 +07:00
Chi Vinh Le afdcdff51e Handle ignored user case 2017-06-06 23:18:37 +07:00
Erica Irving 3d8478dfb2 add semantic classname to active tab 2017-06-06 12:17:32 -04:00
Kim Gardner 71d2f44d38 Merge pull request #650 from coralproject/copy-updates
Copy changes
2017-06-06 12:07:40 -04:00
Kim Gardner 124ea19b52 Update en.yml 2017-06-06 12:03:12 -04:00
Erica Irving 512a463589 add classnames to RespectButton component 2017-06-06 11:28:28 -04:00
Belen Curcio e9cb61a534 Arrow for filtering 2017-06-06 12:13:26 -03:00
Belen Curcio a6db60096c Fully working, needs docs 2017-06-06 11:58:51 -03:00
Belen Curcio 9364272ffc Ádd classnames based on tags 2017-06-06 11:53:19 -03:00
Chi Vinh Le 1ceb72ca5c Fix error with tombstones 2017-06-06 21:26:43 +07:00
Belen Curcio 9e001f5ce9 addClassNames, removeClassNames, reducers and actions for Comment Component 2017-06-06 11:15:35 -03:00
Chi Vinh Le 0fcdd09f9f Lazily create apollo client 2017-06-06 20:50:07 +07:00
Belen Curcio 3ad3b3af1c Adding filters 2017-06-06 09:43:34 -03:00
Chi Vinh Le d3f7e6210c Simplify transition code 2017-06-06 19:06:42 +07:00
Chi Vinh Le 447ec7e524 Deeply pass commentIsIgnored 2017-06-06 19:05:18 +07:00
Chi Vinh Le f8ca14d3d3 Reset stream after refetching 2017-06-06 18:45:45 +07:00
Belen Curcio 5faa92e8b3 Label style, top level comments, no offtopic replies 2017-06-06 08:09:34 -03:00
Kim Gardner 17783c91c4 Merge branch 'master' into bulk-actions 2017-06-05 23:06:03 -04:00
Riley Davis ad4c4a0a5b fix for active tab not showing. user box classNames 2017-06-05 16:46:58 -06:00
Wyatt Johnson 7956c193c5 Merge branch 'master' into subscribe-to-comments 2017-06-05 16:38:28 -06:00
Wyatt Johnson 2aa3b7a5a0 Merge pull request #641 from coralproject/safari-cookie-time
Use same lifespan for cookies as for tokens
2017-06-05 16:38:13 -06:00
Wyatt Johnson 2f77842072 Merge branch 'master' into safari-cookie-time 2017-06-05 16:32:29 -06:00
Wyatt Johnson a595e94394 Removed parse-duration in favour of ms 2017-06-05 16:31:06 -06:00
riley d03647067d Merge branch 'master' into css-override-classes 2017-06-05 16:19:46 -06:00
Riley Davis fb537c9990 Merge pull request #648 from coralproject/StephanieDClark-master
Stephanie's changes to add classNames to Tab and TabBar
2017-06-05 16:18:19 -06:00
Riley Davis 8ea7ba9b69 Merge branch 'master' into css-override-classes 2017-06-05 16:13:45 -06:00
David Erwin 15297cd674 Update css naming convention 2017-06-05 18:12:38 -04:00
Riley Davis e46802910c Merge branch 'master' into StephanieDClark-master 2017-06-05 16:09:12 -06:00
riley 19b90910ba better tab classNames 2017-06-05 16:08:53 -06:00
David Erwin 291cee3af4 Merge pull request #647 from coralproject/auth-token-hooks
Added new tokenUserNotFound plugin hook
2017-06-05 18:05:56 -04:00
Wyatt Johnson 069c4c5e50 Merge branch 'master' into auth-token-hooks 2017-06-05 16:00:23 -06:00
Riley Davis 7d54fb60a8 use existing className attr instead of custom property 2017-06-05 15:57:34 -06:00
Wyatt Johnson e8143aea14 Added new tokenUserNotFound plugin hook 2017-06-05 15:52:32 -06:00
StephanieDClark 9e1d010db3 Adjusting tab classes 2017-06-05 14:48:50 -07:00
David Erwin 728d523b9e Begin adding css override classes 2017-06-05 17:25:27 -04:00
StephanieDClark 5838f5d486 fix lint errors 2017-06-05 14:00:21 -07:00
Riley Davis 1c8e1cc46c Merge branch 'master' into bulk-actions 2017-06-05 14:42:07 -06:00
Riley Davis c4a41af33b clear selected ids when panel is closed 2017-06-05 14:40:03 -06:00
StephanieDClark 5e463c00fd Merge branch 'master' into master 2017-06-05 13:35:36 -07:00
Kim Gardner 933a9371f3 Merge branch 'master' into safari-cookie-time 2017-06-05 16:35:23 -04:00
Kim Gardner 0be77fa5fd Merge pull request #645 from coralproject/fix-typos
Fix typos
2017-06-05 16:28:08 -04:00
Kim Gardner 49eecaa65b Fix typos 2017-06-05 16:22:17 -04:00
Riley Davis 39d8a5d01b bulk operations on User Detail comments 2017-06-05 14:20:34 -06:00
StephanieDClark 414e5d78bd Fix yarn lint error 2017-06-05 12:58:04 -07:00
Kiwi e9a6c3dfe3 Merge branch 'master' into subscribe-to-comments 2017-06-06 02:42:35 +07:00
StephanieDClark 3b7603ee31 add supplied classes to tabbar 2017-06-05 12:39:35 -07:00
StephanieDClark 8c89296007 add supplied classes to tab 2017-06-05 12:38:36 -07:00
StephanieDClark 32fe291847 Add classes to tabbar, tab 2017-06-05 12:37:13 -07:00
Chi Vinh Le 056ddc7010 Meaningful comment 2017-06-06 02:25:42 +07:00
Chi Vinh Le 2d0eec15c1 Readd falsely deleted part 2017-06-06 02:25:32 +07:00
Chi Vinh Le 2be73d1df2 Unsubscribe on unmount 2017-06-06 02:10:56 +07:00
Chi Vinh Le 3ad17f008f Refactor fetchMore updaters 2017-06-06 01:44:17 +07:00
David Erwin 547547d04c Merge pull request #638 from coralproject/auth-token-via-config
Use config auth_token if present on http headers
2017-06-05 14:22:48 -04:00
David Erwin 90d7e3706c Improve auth token logic. 2017-06-05 13:58:13 -04:00
Chi Vinh Le 9993350b7e Animate flash for new comments 2017-06-06 00:54:12 +07:00
David Erwin 7c66d846c2 Add auth token comment 2017-06-05 13:38:06 -04:00
Chi Vinh Le 6b0cdae183 Subscribe to comments 2017-06-05 20:51:08 +07:00
Chi Vinh Le c20df65ebc Use same lifespan for cookies as for tokens 2017-06-05 18:21:40 +07:00
Kiwi 7efdccb683 Merge branch 'master' into auth-token-via-config 2017-06-05 17:57:13 +07:00
Chi Vinh Le a9796d47cd Check login after loading config 2017-06-05 17:51:06 +07:00
Chi Vinh Le 7c145bbb3c Refactor import order and framework initialization 2017-06-05 17:49:43 +07:00
Chi Vinh Le ac9e70bc67 Add same getAuthToken logic to apollo transport 2017-06-05 17:48:09 +07:00
Chi Vinh Le defd28b20f Remove double login checks 2017-06-05 17:46:57 +07:00
Chi Vinh Le b05b54a991 Add auth_token default config 2017-06-05 17:46:30 +07:00
Kim Gardner 3c96b7eb1e Merge pull request #640 from coralproject/user-comment-history
user detail design pass
2017-06-02 17:27:13 -04:00
Kim Gardner 6e483842f1 Merge branch 'master' into user-comment-history 2017-06-02 17:22:48 -04:00
Kim Gardner 0276c00828 Merge pull request #639 from coralproject/key-trans-missing
Key translation missing
2017-06-02 17:08:48 -04:00
gaba c6efc586ae Merge branch 'master' into key-trans-missing 2017-06-02 14:03:33 -07:00
gaba 6de9c69668 Missing key translation. 2017-06-02 14:02:45 -07:00
Kim Gardner 396abaae22 Merge pull request #637 from coralproject/i18n-emails
I18n emails
2017-06-02 16:59:32 -04:00
gaba e98b9ff9bc Some translations missing and a folder that should not be there. 2017-06-02 13:50:48 -07:00
Gabriela Rodríguez Berón cbf83206b8 Merge branch 'master' into i18n-emails 2017-06-02 13:35:09 -07:00
Kim Gardner 664ace170b Merge pull request #618 from coralproject/user-comment-history
show comments on the UserDetail drawer
2017-06-02 16:10:22 -04:00
Kim Gardner bd60eca6f9 Merge branch 'master' into user-comment-history 2017-06-02 15:58:54 -04:00
Riley Davis e097b8eb21 design pass 2017-06-02 13:50:00 -06:00
Kim Gardner 058cab970b Merge pull request #636 from coralproject/story-146187883
UX Updates
2017-06-02 15:25:40 -04:00
Riley Davis 22c2843ae5 remove moderate link per designs 2017-06-02 13:12:59 -06:00
Riley Davis d8cb560755 add a tab for All/Rejected user detail comments 2017-06-02 13:02:22 -06:00
David Erwin ea3d8e141d Use config auth_token if present on http headers 2017-06-02 14:22:06 -04:00
gaba cf50a0e85c Translations missing. 2017-06-02 08:30:36 -07:00
gaba b9b62e5764 Use accepts library. 2017-06-02 08:24:21 -07:00
gaba eadbb03c15 Fix typo. 2017-06-02 06:54:01 -07:00
gaba b6dbb5975f Adds missing translations. 2017-06-01 17:26:49 -07:00
gaba fd1cf8121e Move start to the left. 2017-06-01 17:21:55 -07:00
gaba dcbd0fc167 Change signed in for logged in. 2017-06-01 17:13:55 -07:00
gaba 44d82f8b13 Merge branch 'master' into i18n-emails 2017-06-01 16:26:44 -07:00
gaba 27e6372fe2 Get language from the header. 2017-06-01 16:24:15 -07:00
gaba 686dc96c5f An attempt to guess language. In process. 2017-06-01 15:44:44 -07:00
Riley Davis 8d25b766ff Merge branch 'master' into user-comment-history 2017-06-01 15:47:54 -06:00
Riley Davis 555d54425a implement kiwi's pagination changes 2017-06-01 15:34:05 -06:00
Kim Gardner f3f7fad68f Merge pull request #635 from StephanieDClark/master
Add isReply to commentInputDetailArea api
2017-06-01 14:38:36 -04:00
Riley Davis 9483922781 Merge branch 'master' into master 2017-06-01 12:31:48 -06:00
Kim Gardner 91d415b434 Merge pull request #632 from coralproject/losowsky-patch-2
Updated description
2017-06-01 14:25:55 -04:00
Kim Gardner 1943b38544 Merge branch 'master' into losowsky-patch-2 2017-06-01 14:20:45 -04:00
Kim Gardner 9344490514 Merge pull request #631 from coralproject/losowsky-patch-1
Added iPhone 7
2017-06-01 14:20:28 -04:00
StephanieDClark 1bf2053cb8 Add isReply to commentInputDetailArea api 2017-06-01 11:01:57 -07:00
Andrew Losowsky 9fe7b961ca Updated description
Added mention of Plugins
2017-06-01 13:43:30 -04:00
Andrew Losowsky ac0db69c27 Added iPhone 7
What about the Android devices? It says Galaxy S5 but that's up to S7, same with Nexus.
2017-06-01 13:41:08 -04:00
Riley Davis d981b5a85b Merge branch 'master' into user-comment-history 2017-06-01 10:52:24 -06:00
Kim Gardner 1fe6c79d88 Merge pull request #627 from coralproject/bump-version-1-8-0
Bump version in prep for 1.8.0 release
2017-06-01 12:42:31 -04:00
Kim Gardner 8b3c1b275d Merge branch 'master' into bump-version-1-8-0 2017-06-01 12:24:34 -04:00
Kim Gardner c76e71ce31 Merge pull request #629 from coralproject/dec-inc-total-comment-count
Increase/Decrease totalCommentCount after mutation
2017-06-01 11:59:11 -04:00
Kim Gardner b8015506df Merge branch 'master' into dec-inc-total-comment-count 2017-06-01 11:54:49 -04:00
Kim Gardner 06e6730f04 Merge pull request #628 from coralproject/fix-translation-comment-count
Fix translation bug in CommentCount
2017-06-01 11:54:01 -04:00
Chi Vinh Le fa08450185 Increase/Decrease totalCommentCount after mutation 2017-06-01 22:39:05 +07:00
Chi Vinh Le d9acbbad96 Fix translation bug in CommentCount 2017-06-01 22:37:25 +07:00
David Erwin 4951527883 Merge pull request #626 from coralproject/pagination-refactor
Pagination refactor
2017-06-01 10:24:26 -04:00
Chi Vinh Le 33eff78a64 Always request comment id 2017-06-01 19:49:53 +07:00
Chi Vinh Le 5245755d49 Add some comments 2017-06-01 19:41:08 +07:00
Chi Vinh Le ebdd586d1e Fix mutation funkiness on permlink view 2017-06-01 18:47:07 +07:00
Chi Vinh Le 9a2b6b3ae1 Merge branch 'master' into pagination-refactor 2017-06-01 17:15:42 +07:00
gaba 276b2c7591 Merge branch 'master' into i18n-emails 2017-05-31 15:31:14 -07:00
gaba a126c94aa4 Adds service for translations in the backend. Clean yaml. 2017-05-31 13:55:27 -07:00
Kim Gardner 9bd75a5670 Bump version in prep for 1.8.0 release 2017-05-31 16:55:14 -04:00
Riley Davis 576cbed8e9 no more errors 2017-05-31 13:44:38 -06:00
Kim Gardner 4ce0431fba Merge pull request #623 from coralproject/add-trust-to-readme
Add trust to readme
2017-05-31 15:43:50 -04:00
Kim Gardner 93d8e0015b Remove space 2017-05-31 15:29:51 -04:00
Kim Gardner ac8e723e75 Copy changes 2017-05-31 15:29:50 -04:00
Kim Gardner c172260d74 Fix link 2017-05-31 15:29:49 -04:00
Kim Gardner 2a3941552d Update README with Trust info 2017-05-31 15:29:41 -04:00
Kim Gardner bf15354aa2 Merge branch 'master' into user-comment-history 2017-05-31 15:17:00 -04:00
Kim Gardner dd9e3247d2 Merge pull request #622 from coralproject/hide-comment-box-perm
Hide comment box during perm view
2017-05-31 15:12:41 -04:00
Chi Vinh Le b402f70f38 Hide comment box during perm view 2017-06-01 01:34:32 +07:00
Chi Vinh Le 30d4562383 Refactor pagination 2017-06-01 01:18:41 +07:00
riley 6e458e0149 resolve missing files 2017-05-31 10:54:34 -06:00
Riley Davis 259ce9c9fb merge master 2017-05-31 10:36:01 -06:00
Kim Gardner 93be1a91be Merge pull request #608 from coralproject/integrate-admin-graphql-framework
Integrate admin into GraphQL framework
2017-05-30 18:04:10 -04:00
Riley Davis 6599ed2b4c Merge branch 'master' into user-comment-history 2017-05-30 15:53:36 -06:00
Riley Davis 29753a958b show comments on the UserDetail drawer 2017-05-30 15:50:56 -06:00
Chi Vinh Le 109f4b2c0f Handle mutation errors 2017-05-31 00:44:16 +07:00
Kiwi 4f8ef88bd0 Merge branch 'master' into integrate-admin-graphql-framework 2017-05-31 00:24:06 +07:00
Kiwi 3160f6ae9e Merge pull request #604 from coralproject/graphql-errors-handling
Support response errors in the graphql framework
2017-05-31 00:21:14 +07:00
Kiwi 15a11dca4d Merge branch 'master' into graphql-errors-handling 2017-05-30 20:48:58 +07:00
Kiwi d28ba0028d Merge branch 'master' into integrate-admin-graphql-framework 2017-05-30 20:48:53 +07:00
Kiwi f42dd884c0 Merge pull request #617 from coralproject/forget-password-ports
Allow domains with ports
2017-05-30 20:48:42 +07:00
Kiwi 4a0785f6fe Merge branch 'master' into graphql-errors-handling 2017-05-30 20:18:32 +07:00
Kiwi 336e0373cd Merge branch 'master' into integrate-admin-graphql-framework 2017-05-30 20:16:09 +07:00
David Erwin bbed3e2a7f Merge branch 'master' into forget-password-ports 2017-05-30 08:56:07 -04:00
David Erwin c894e8eb1d Merge pull request #616 from coralproject/fix-redirect-uri
Use correct redirect url
2017-05-30 08:55:44 -04:00
Chi Vinh Le 68f6c8acb6 No shared store between popup and embed 2017-05-30 01:08:45 +07:00
Chi Vinh Le 1acf58182f Merge branch 'master' into integrate-admin-graphql-framework
Conflicts:
	client/coral-admin/src/containers/Community/Loading.js
	client/coral-admin/src/containers/Community/Table.js
	client/coral-admin/src/containers/Streams/Stories.js
	client/coral-admin/src/index.js
	client/coral-admin/src/routes/Community/components/FlaggedAccounts.js
	client/coral-admin/src/routes/Community/components/People.js
	client/coral-admin/src/routes/Configure/components/Configure.js
	client/coral-admin/src/routes/Configure/components/Domainlist.js
	client/coral-admin/src/routes/Configure/components/EmbedLink.js
	client/coral-admin/src/routes/Configure/components/ModerationSettings.js
	client/coral-admin/src/routes/Configure/components/StreamSettings.js
	client/coral-admin/src/routes/Configure/components/TechSettings.js
	client/coral-admin/src/routes/Configure/components/Wordlist.js
	client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js
	client/coral-admin/src/routes/Install/components/Steps/AddOrganizationName.js
	client/coral-admin/src/routes/Install/components/Steps/CreateYourAccount.js
	client/coral-admin/src/routes/Install/components/Steps/FinalStep.js
	client/coral-admin/src/routes/Install/components/Steps/InitialStep.js
	client/coral-admin/src/routes/Install/components/Steps/PermittedDomainsStep.js
	client/coral-admin/src/routes/Install/components/translations.json
	client/coral-admin/src/routes/Moderation/components/Moderation.js
	client/coral-admin/src/routes/Moderation/components/ModerationQueue.js
	client/coral-admin/src/routes/Stories/components/Stories.js
	client/coral-embed-stream/src/index.js
2017-05-29 23:06:40 +07:00
Chi Vinh Le 04ef1c5be5 Use new translation service 2017-05-29 22:45:56 +07:00
Chi Vinh Le a598bca12b Allow domains with ports 2017-05-29 22:10:16 +07:00
Kiwi 810f337d8d Merge branch 'master' into fix-redirect-uri 2017-05-29 22:06:33 +07:00
Kiwi c314054143 Merge branch 'master' into graphql-errors-handling 2017-05-29 22:00:49 +07:00
Chi Vinh Le bd5e272f00 Use correct redirect url 2017-05-29 22:00:27 +07:00
Kiwi bdbc7c5ff2 Merge pull request #586 from coralproject/i18n-refactor
Refactor i18n
2017-05-29 21:59:52 +07:00
Chi Vinh Le 2d398fe452 Change not authorized error message 2017-05-29 20:36:21 +07:00
Chi Vinh Le 0b7432b75e Merge branch 'master' into i18n-refactor
Conflicts:
	client/coral-sign-in/components/FakeComment.js
	client/coral-sign-in/components/UserBox.js
2017-05-29 20:23:27 +07:00
Chi Vinh Le 9a6532ecea Merge branch 'master' into integrate-admin-graphql-framework
Conflicts:
	client/coral-embed-stream/src/index.js
2017-05-29 20:09:03 +07:00
Kiwi f3f6633e99 Merge branch 'master' into graphql-errors-handling 2017-05-29 19:42:26 +07:00
David Erwin 93f8a81678 Merge pull request #607 from coralproject/unified-docs
Unified docs
2017-05-27 09:59:08 -04:00
Kim Gardner b13d3adce0 Merge branch 'master' into unified-docs 2017-05-26 17:00:53 -04:00
Kim Gardner d213a4a26e Merge pull request #613 from coralproject/minor-stream-style-changes
Stream and mod queue style fixes
2017-05-26 17:00:09 -04:00
Kim Gardner 82d442d95b Merge branch 'master' into minor-stream-style-changes 2017-05-26 16:53:55 -04:00
Kim Gardner 1dcd254519 Merge branch 'master' into unified-docs 2017-05-26 16:53:36 -04:00
Kim Gardner 37262ad9f6 Merge branch 'master' into graphql-errors-handling 2017-05-26 16:53:13 -04:00
IAmSamHankins 05e5200e79 moderation panel design cleanup PT 144120491 2017-05-26 12:48:15 -04:00
David Erwin 69a9afe084 Remove old docs 2017-05-26 11:40:56 -04:00
David Erwin 1d3a75e425 Update doc menu title 2017-05-26 11:39:24 -04:00
David Erwin 5f83584e48 Update tooling and client architecture docs 2017-05-26 11:37:33 -04:00
David Erwin 1dbecab13e Add swagger user docs and tools page 2017-05-26 11:27:10 -04:00
David Erwin deea24d0c5 Add docs to .eslintignore 2017-05-26 11:12:16 -04:00
David Erwin ce46329e27 Create FAQ page 2017-05-26 11:07:10 -04:00
IAmSamHankins 32715d3903 initial style updates on comment stream for PT 142803055 ~ copy translations still needed 2017-05-25 17:21:32 -04:00
Chi Vinh Le a39fcee3f8 More error handling fixes 2017-05-26 03:04:06 +07:00
Chi Vinh Le 841735b787 Restore error functionality 2017-05-26 02:55:09 +07:00
David Erwin f5bd2a9db3 Update top nav 2017-05-25 15:17:50 -04:00
Chi Vinh Le 209005aff5 More key misspellings 2017-05-26 02:10:00 +07:00
Chi Vinh Le 0107aa67cd Correct keys and translations 2017-05-26 01:59:34 +07:00
gaba 77bfd55af3 Merge branch 'i18n-refactor' into i18n-emails 2017-05-25 14:35:49 -04:00
gaba 96985b4780 More strings. 2017-05-25 14:05:17 -04:00
gaba 492f4a674c OMG. Strings. 2017-05-25 12:02:26 -04:00
Chi Vinh Le 4fe89257c2 Support store in withMutation 2017-05-25 22:19:16 +07:00
gaba 09bd0a6735 Move templates to a folder for translations. 2017-05-25 10:43:34 -04:00
Chi Vinh Le 246ca117be Little cleanup for admin services 2017-05-25 19:22:59 +07:00
Chi Vinh Le 93e6c0af32 Use default response fragments 2 2017-05-25 18:57:42 +07:00
Chi Vinh Le b43a0f533b Harmonize naming in admin and embed-stream 2017-05-25 18:37:23 +07:00
Chi Vinh Le 512767e023 Use default response fragments 2017-05-25 17:49:30 +07:00
David Erwin 69b19d2c91 Jekyll Initial Commit 2017-05-24 16:48:00 -04:00
gaba 4ff7da864e Still more strings. 2017-05-24 14:54:52 -04:00
Chi Vinh Le 48a8e394ef Use framework for mutations 2017-05-25 01:18:33 +07:00
Chi Vinh Le d9ee215d66 Remove obsolete fragment 2017-05-24 23:22:03 +07:00
gaba 846ac50b19 Clean out the spanish one. 2017-05-24 11:59:22 -04:00
gaba 03f70bcab9 Adds more missing translation. 2017-05-24 11:59:21 -04:00
Chi Vinh Le a3755b7505 Use unique names 2017-05-24 22:24:01 +07:00
Chi Vinh Le 5e2fa72466 Support extendable queries 2017-05-24 22:05:41 +07:00
Chi Vinh Le a9b4de1f5c Wrap queries with hocs 2017-05-24 21:00:17 +07:00
Chi Vinh Le 0231358075 Correctly set roles 2017-05-24 21:00:05 +07:00
Chi Vinh Le c35ce979f5 Move query logic to containers 2017-05-24 19:04:05 +07:00
Chi Vinh Le 3cde313ab4 Pass correct props to install step 2017-05-24 17:38:19 +07:00
Chi Vinh Le 26d6a97b20 Merge branch 'master' into extendable-queries-admin 2017-05-24 17:17:52 +07:00
Chi Vinh Le 8ab7047cfa Refactor admin 2017-05-24 17:09:21 +07:00
Chi Vinh Le db3f91cbcc Change to declarative API for plugins 2017-05-24 16:44:33 +07:00
Chi Vinh Le 1b3fe13e9b Fallback to default language for timeago 2017-05-24 16:33:39 +07:00
gaba 86258ba0c3 One more string to translate. 2017-05-23 16:06:07 -04:00
gaba 8da7df353c Adds a bunch of missing strings. 2017-05-23 16:06:04 -04:00
Chi Vinh Le b2798fd35f Merge branch 'i18n-refactor' of ssh://github.com/coralproject/talk into i18n-refactor 2017-05-24 01:39:09 +07:00
Chi Vinh Le 37b8fd8fe3 Simplify i18n + fallback to default language 2017-05-24 01:38:50 +07:00
gaba 2991af67f4 Adds more translation strings. 2017-05-23 14:03:34 -04:00
David Erwin a1a78334ea First pass docs restructure 2017-05-23 13:58:02 -04:00
Chi Vinh Le 5c66009d84 Change to import 2017-05-24 00:30:34 +07:00
Chi Vinh Le 8ae497366b Move locales to top level 2017-05-24 00:28:47 +07:00
Chi Vinh Le 48bef95a4d Simplify yaml loading 2017-05-24 00:26:55 +07:00
gaba adacafb586 Removed extra string. 2017-05-23 12:59:06 -04:00
Kim Gardner ac9e193c45 Merge branch 'master' into graphql-errors-handling 2017-05-23 12:57:53 -04:00
gaba 259b24faaa A few strings where using -. 2017-05-23 12:53:48 -04:00
gaba a184837ff8 Goes back to load all translations... 2017-05-23 12:46:53 -04:00
gaba 5757682732 Removes files that came from the merge and do not exist in master. 2017-05-23 11:57:51 -04:00
Gabriela Rodríguez Berón 6f7b8522c8 Merge branch 'master' into i18n-refactor 2017-05-23 11:47:58 -04:00
Chi Vinh Le 947ea66a77 Remove obsolete errors checking 2017-05-22 20:25:24 +07:00
Chi Vinh Le d1307eb5cd Handle response errors in the graphql framework 2017-05-22 20:11:16 +07:00
gaba f525af4c6e Added a few more translations that were missing. 2017-05-19 15:42:56 -07:00
gaba 97e9419f02 Missing translations. 2017-05-19 13:34:56 -07:00
gaba 67fb829717 A few more issues from the merge. 2017-05-19 13:29:23 -07:00
gaba 625362d51e Merge branch 'master' into i18n-refactor 2017-05-19 12:44:34 -07:00
gaba 9be443eda8 Moves i18n into coral-framework. 2017-05-19 12:28:44 -07:00
gaba 88b5a4169d Remove promise 2017-05-19 10:48:47 -07:00
gaba 8dddff25f9 Update es translation and add link to transifex. 2017-05-18 16:01:12 -07:00
gaba 12cb59ce30 Merge branch 'master' into i18n-refactor 2017-05-18 15:44:14 -07:00
gaba 1a7ad410f5 Loads timeago translation when loading translations. 2017-05-18 15:38:53 -07:00
gaba 3ca22bd101 Fix translation keys. 2017-05-18 15:14:37 -07:00
gaba 9e954f0ae3 Fix timeago with lang. 2017-05-17 18:05:41 -07:00
gaba 8d492ca4e9 Adds a few missing keys. 2017-05-17 13:18:04 -07:00
gaba 3aceb0dc38 Merge branch 'master' into i18n-refactor 2017-05-17 12:59:24 -07:00
gaba 5bd9bc992b Move i18n into a service 2017-05-17 12:53:36 -07:00
gaba 35c941d4d0 Add missing translations from commentbox. 2017-05-15 16:17:53 -07:00
gaba 61856c3cf6 Use timeago. 2017-05-15 16:04:10 -07:00
gaba c0c6dd6252 The merge got back some code to the coral-framework. 2017-05-15 14:23:56 -07:00
gaba 423cb3cade Merge branch 'master' into i18n-refactor 2017-05-15 14:02:34 -07:00
gaba 54e103b55a Change a few translations that had wrong key. 2017-05-15 13:44:46 -07:00
gaba 0ead2cb610 Adds sign in translations. 2017-05-15 13:25:09 -07:00
gaba f32eac4eab Lint YAML and notations 2017-05-15 12:53:28 -07:00
gaba b31212f847 Second run of notation change for translations. 2017-05-15 08:35:55 -07:00
gaba 633086cfa3 Fix a few more strings and translations. 2017-05-15 07:11:11 -07:00
gaba 3b80ee8b13 Add YAML loader. Fix i18n module to get YAML files. 2017-05-11 15:05:09 -07:00
gaba 653091fdf9 Merge branch 'master' into i18n-refactor 2017-05-11 09:02:21 -07:00
gaba c0fc87ea21 Merge branch 'master' into i18n-refactor 2017-05-01 11:28:06 -07:00
gaba 9e41f0bd65 Merge branch 'master' into i18n-refactor 2017-04-25 16:10:45 -05:00
gaba 527cbc7d63 Recover the scroll. 2017-04-25 16:10:32 -05:00
gaba 0f7b4ddaf6 Removes timeago from i18n module. 2017-04-25 11:43:04 -05:00
gaba 59e23ffd60 Moves into YAML and own module. 2017-04-24 18:09:13 -05:00
gaba 5aacdc3a74 Merge branch 'master' into i18n-refactor 2017-04-24 12:56:32 -05:00
gaba 05bf21fe09 Refactoring 2017-04-24 10:08:33 -05:00
406 changed files with 18614 additions and 5288 deletions
+2
View File
@@ -1,4 +1,5 @@
dist
docs
client/lib
**/*.html
plugins/*
@@ -9,4 +10,5 @@ plugins/*
!plugins/coral-plugin-like
!plugins/coral-plugin-mod
!plugins/coral-plugin-love
!plugins/coral-plugin-viewing-options
node_modules
+1
View File
@@ -23,5 +23,6 @@ plugins/*
!plugins/coral-plugin-like
!plugins/coral-plugin-mod
!plugins/coral-plugin-love
!plugins/coral-plugin-viewing-options
**/node_modules/*
+35
View File
@@ -280,6 +280,41 @@ send data to the client. If the type in question contains args, clients may subs
For more information, see the [Apollo Docs](https://github.com/apollographql/graphql-subscriptions).
#### Field: `tokenUserNotFound`
```js
tokenUserNotFound: async ({jwt, token}) => {
let profile = await someExternalService(token);
if (!profile) {
return null;
}
let user = await UserModel.findOneAndUpdate({
id: profile.id
}, {
id: profile.id,
username: profile.username,
lowercaseUsername: profile.username.toLowerCase(),
roles: [],
profiles: []
}, {
setDefaultsOnInsert: true,
new: true,
upsert: true
});
return user;
}
```
The `tokenUserNotFound` hook allows auth integrations to hook into the event
when a valid token is provided but a user can't be found in the database that
matches the provided id.
The function is async, and should return the user object that was created in the
database, or null if the user wasn't found. The `jwt` paramenter of the object
is the unpacked token, while `token` is the original jwt token string.
#### Field: `router`
```js
+6 -1
View File
@@ -17,7 +17,6 @@ To set up a development environment or build from source, see [INSTALL.md](https
To launch a Talk server of your own from your browser without any need to muck about in a terminal or think about engineering concepts, stay tuned. We will launch [our installer](https://github.com/coralproject/talk-install) shortly!
### Configuration
The Talk application looks for the following configuration values either as environment variables:
@@ -45,6 +44,12 @@ sign and verify tokens via a `HS256` algorithm.
Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for
alternative methods of loading configuration during development.
### Using Trust
Talk ships with core components we call "Trust". This allows Talk to automate certain actions based on previous user behavior.
Our first feature is the notion of Karma. Talk will automatically pre-moderate comments of users who have a negative karma score. You can [see more how karma works here](/services/karma.js).
## Supported Browsers
### Web
+3
View File
@@ -6,6 +6,7 @@ const helmet = require('helmet');
const authentication = require('./middleware/authentication');
const {passport} = require('./services/passport');
const plugins = require('./services/plugins');
const i18n = require('./services/i18n');
const enabled = require('debug').enabled;
const errors = require('./errors');
const {createGraphOptions} = require('./graph');
@@ -157,6 +158,8 @@ app.use('/', (err, req, res, next) => {
console.error(err);
}
i18n.init(req);
if (err instanceof errors.APIError) {
res.status(err.status);
res.render('error', {
+1 -1
View File
@@ -5,7 +5,7 @@
*/
const program = require('./commander');
const parseDuration = require('parse-duration');
const parseDuration = require('ms');
const Table = require('cli-table');
const AssetModel = require('../models/asset');
const mongoose = require('../services/mongoose');
+5 -6
View File
@@ -4,21 +4,20 @@
"es6": true,
"mocha": true
},
"extends": "../.eslintrc.json",
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
},
"sourceType": "module"
}
},
"parser": "babel-eslint",
"plugins": [
"react"
],
"rules": {
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
}
}
-3
View File
@@ -1,3 +0,0 @@
{
"basePath": "admin"
}
+24 -29
View File
@@ -1,25 +1,20 @@
import React from 'react';
import {Router, Route, IndexRedirect, browserHistory} from 'react-router';
import Stories from 'containers/Stories/Stories';
import Configure from 'containers/Configure/Configure';
import LayoutContainer from 'containers/LayoutContainer';
import InstallContainer from 'containers/Install/InstallContainer';
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 {ModerationLayout, Moderation} from 'routes/Moderation';
import CommunityLayout from 'containers/Community/CommunityLayout';
import CommunityContainer from 'containers/Community/CommunityContainer';
import ModerationLayout from 'containers/ModerationQueue/ModerationLayout';
import ModerationContainer from 'containers/ModerationQueue/ModerationContainer';
import Dashboard from 'containers/Dashboard/Dashboard';
import Layout from 'containers/Layout';
const routes = (
<div>
<Route exact path="/admin/install" component={InstallContainer}/>
<Route path='/admin' component={LayoutContainer}>
<Route exact path="/admin/install" component={Install}/>
<Route path='/admin' component={Layout}>
<IndexRedirect to='/admin/moderate/all' />
<Route path='community' component={CommunityContainer} />
<Route path='configure' component={Configure} />
<Route path='stories' component={Stories} />
<Route path='dashboard' component={Dashboard} />
@@ -27,11 +22,11 @@ const routes = (
{/* Community Routes */}
<Route path='community' component={CommunityLayout}>
<Route path='flagged' components={CommunityContainer}>
<Route path=':id' components={CommunityContainer} />
<Route path='flagged' components={Community}>
<Route path=':id' components={Community} />
</Route>
<Route path='people' components={CommunityContainer}>
<Route path=':id' components={CommunityContainer} />
<Route path='people' components={Community}>
<Route path=':id' components={Community} />
</Route>
<IndexRedirect to='flagged' />
</Route>
@@ -39,22 +34,22 @@ const routes = (
{/* Moderation Routes */}
<Route path='moderate' component={ModerationLayout}>
<Route path='all' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='all' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='accepted' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='accepted' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='premod' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='premod' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='rejected' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='rejected' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path='flagged' components={ModerationContainer}>
<Route path=':id' components={ModerationContainer} />
<Route path='flagged' components={Moderation}>
<Route path=':id' components={Moderation} />
</Route>
<Route path=':id' components={ModerationContainer} />
<Route path=':id' components={Moderation} />
<IndexRedirect to='premod' />
</Route>
</Route>
+25 -1
View File
@@ -5,7 +5,7 @@ export const singleView = () => ({type: actions.SINGLE_VIEW});
// Ban User Dialog
export const showBanUserDialog = (user, commentId, commentStatus, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, commentStatus, showRejectedNote});
export const hideBanUserDialog = (showDialog) => ({type: actions.HIDE_BANUSER_DIALOG, showDialog});
export const hideBanUserDialog = () => ({type: actions.HIDE_BANUSER_DIALOG});
// Suspend User Dialog
export const showSuspendUserDialog = (userId, username, commentId, commentStatus) =>
@@ -27,3 +27,27 @@ export const hideShortcutsNote = () => {
export const viewUserDetail = (userId) => ({type: actions.VIEW_USER_DETAIL, userId});
export const hideUserDetail = () => ({type: actions.HIDE_USER_DETAIL});
export const setSortOrder = (order) => ({
type: actions.SET_SORT_ORDER,
order
});
export const changeUserDetailStatuses = (tab) => {
let statuses;
if (tab === 'all') {
statuses = ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'];
} else if (tab === 'rejected') {
statuses = ['REJECTED'];
}
return {type: actions.CHANGE_USER_DETAIL_STATUSES, tab, statuses};
};
export const clearUserDetailSelections = () => ({type: actions.CLEAR_USER_DETAIL_SELECTIONS});
export const toggleSelectCommentInUserDetail = (id, active) => {
return {
type: active ? actions.SELECT_USER_DETAIL_COMMENT : actions.UNSELECT_USER_DETAIL_COMMENT,
id
};
};
@@ -1,25 +1,27 @@
import React, {PropTypes} from 'react';
import styles from './ModerationList.css';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap';
import {menuActionsMap} from '../routes/Moderation/helpers/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';
if (text === 'approve' && active) {
text = 'approved';
} else if (text === 'reject' && active) {
text = 'rejected';
}
return (
<Button
className={`${typeName} ${styles.actionButton} ${active ? styles[`${typeName}__active`] : ''}`}
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}
>{text}</Button>
>{props.minimal ? '' : t(`modqueue.${text}`)}</Button>
);
};
@@ -5,9 +5,7 @@ import cn from 'classnames';
import {findDOMNode} from 'react-dom';
import styles from './ActionsMenu.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
let count = 0;
@@ -43,7 +41,7 @@ class ActionsMenu extends React.Component {
onClick={this.syncOpenState}
icon={this.props.icon}
raised>
{lang.t('modqueue.actions')}
{t('modqueue.actions')}
<Icon
name={this.state.open ? 'keyboard_arrow_up' : 'keyboard_arrow_down'}
className={styles.arrowIcon}
@@ -2,10 +2,8 @@ import React, {PropTypes} from 'react';
import Layout from 'coral-admin/src/components/ui/Layout';
import styles from './NotFound.css';
import {Button, TextField, Alert, Success} from 'coral-ui';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
import t from 'coral-framework/services/i18n';
import Recaptcha from 'react-recaptcha';
const lang = new I18n(translations);
class AdminLogin extends React.Component {
@@ -37,7 +35,7 @@ class AdminLogin extends React.Component {
const {errorMessage, loginMaxExceeded, recaptchaPublic} = this.props;
const signInForm = (
<form onSubmit={this.handleSignIn}>
{errorMessage && <Alert>{lang.t(`errors.${errorMessage}`)}</Alert>}
{errorMessage && <Alert>{t(`error.${errorMessage}`)}</Alert>}
<TextField
label='Email Address'
value={this.state.email}
@@ -1,17 +1,14 @@
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
import React, {PropTypes} from 'react';
import Modal from 'components/Modal';
import styles from './ModerationKeysModal.css';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const shortcuts = [
{
title: 'modqueue.navigation',
shortcuts: {
'j': 'modqueue.nextcomment',
'k': 'modqueue.prevcomment',
'j': 'modqueue.next_comment',
'k': 'modqueue.prev_comment',
's': 'modqueue.singleview',
'?': 'modqueue.thismenu'
}
@@ -40,29 +37,29 @@ export default class ModerationKeysModal extends React.Component {
<div>
<div className={styles.callToAction} style={{display: shortcutsNoteVisible === 'show' ? 'block' : 'none'}}>
<div onClick={hideShortcutsNote} className={styles.closeButton}>×</div>
<p className={styles.ctaHeader}>{lang.t('modqueue.mod-faster')}</p>
<p><strong>{lang.t('modqueue.try-these')}:</strong></p>
<p className={styles.ctaHeader}>{t('modqueue.mod_faster')}</p>
<p><strong>{t('modqueue.try_these')}:</strong></p>
<ul>
<li><span>{lang.t('modqueue.approve')}</span> <span className={styles.smallKey}>t</span></li>
<li><span>{lang.t('modqueue.reject')}</span> <span className={styles.smallKey}>r</span></li>
<li><span>{t('modqueue.approve')}</span> <span className={styles.smallKey}>t</span></li>
<li><span>{t('modqueue.reject')}</span> <span className={styles.smallKey}>r</span></li>
</ul>
<p><span>{lang.t('modqueue.view-more-shortcuts')}</span> <span className={styles.smallKey}>{lang.t('modqueue.shift-key')}</span> + <span className={styles.smallKey}>/</span></p>
<p><span>{t('modqueue.view_more_shortcuts')}</span> <span className={styles.smallKey}>{t('modqueue.shift_key')}</span> + <span className={styles.smallKey}>/</span></p>
</div>
<Modal open={open} onClose={onClose}>
<h3>{lang.t('modqueue.shortcuts')}</h3>
<h3>{t('modqueue.shortcuts')}</h3>
<div className={styles.container}>
{shortcuts.map((shortcut, i) => (
<table className={styles.table} key={i}>
<thead>
<tr>
<th>{lang.t(shortcut.title)}</th>
<th>{t(shortcut.title)}</th>
</tr>
</thead>
<tbody>
{Object.keys(shortcut.shortcuts).map((key) => (
<tr key={`${key}tr`}>
<td className={styles.shortcut}><span className={styles.key}>{key}</span></td>
<td>{lang.t(shortcut.shortcuts[key])}</td>
<td>{t(shortcut.shortcuts[key])}</td>
</tr>
))}
</tbody>
@@ -189,6 +189,12 @@
width: 140px;
}
.minimal {
width: 45px;
min-width: 0;
float: left;
}
.approve__active {
box-shadow: none;
color: white;
@@ -2,8 +2,7 @@ import React, {PropTypes} from 'react';
import {Navigation, Drawer} from 'react-mdl';
import {IndexLink, Link} from 'react-router';
import styles from './Drawer.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import t from 'coral-framework/services/i18n';
import {can} from 'coral-framework/services/perms';
const CoralDrawer = ({handleLogout, auth}) => (
@@ -15,7 +14,7 @@ const CoralDrawer = ({handleLogout, auth}) => (
className={styles.navLink}
to="/admin/dashboard"
activeClassName={styles.active}>
{lang.t('configure.dashboard')}
{t('configure.dashboard')}
</IndexLink>
{
can(auth.user, 'MODERATE_COMMENTS') && (
@@ -23,19 +22,19 @@ const CoralDrawer = ({handleLogout, auth}) => (
className={styles.navLink}
to="/admin/moderate"
activeClassName={styles.active}>
{lang.t('configure.moderate')}
{t('configure.moderate')}
</Link>
)
}
<Link className={styles.navLink}
to="/admin/stories"
activeClassName={styles.active}>
{lang.t('configure.stories')}
{t('configure.stories')}
</Link>
<Link className={styles.navLink}
to="/admin/community"
activeClassName={styles.active}>
{lang.t('configure.community')}
{t('configure.community')}
</Link>
{
can(auth.user, 'UPDATE_CONFIG') &&
@@ -44,7 +43,7 @@ const CoralDrawer = ({handleLogout, auth}) => (
className={styles.navLink}
to="/admin/configure"
activeClassName={styles.active}>
{lang.t('configure.configure')}
{t('configure.configure')}
</Link>
)
}
@@ -60,6 +59,4 @@ CoralDrawer.propTypes = {
restricted: PropTypes.bool // hide app elements from a logged out user
};
const lang = new I18n(translations);
export default CoralDrawer;
@@ -73,6 +73,8 @@
font-weight: 500;
background-color: transparent;
transition: background-color 200ms;
font-weight: 100;
letter-spacing: .8;
&:hover {
background-color: #232323;
@@ -80,6 +82,7 @@
&.active {
background-color: #232323;
font-weight: 400;
}
}
+8 -11
View File
@@ -2,8 +2,7 @@ import React, {PropTypes} from 'react';
import {Navigation, Header, IconButton, MenuItem, Menu} from 'react-mdl';
import {Link, IndexLink} from 'react-router';
import styles from './Header.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import t from 'coral-framework/services/i18n';
import {Logo} from './Logo';
import {can} from 'coral-framework/services/perms';
@@ -23,7 +22,7 @@ const CoralHeader = ({
className={styles.navLink}
to="/admin/dashboard"
activeClassName={styles.active}>
{lang.t('configure.dashboard')}
{t('configure.dashboard')}
</IndexLink>
{
can(auth.user, 'MODERATE_COMMENTS') && (
@@ -32,7 +31,7 @@ const CoralHeader = ({
className={styles.navLink}
to="/admin/moderate"
activeClassName={styles.active}>
{lang.t('configure.moderate')}
{t('configure.moderate')}
</Link>
)
}
@@ -41,14 +40,14 @@ const CoralHeader = ({
className={styles.navLink}
to="/admin/stories"
activeClassName={styles.active}>
{lang.t('configure.stories')}
{t('configure.stories')}
</Link>
<Link
id='communityNav'
className={styles.navLink}
to="/admin/community"
activeClassName={styles.active}>
{lang.t('configure.community')}
{t('configure.community')}
</Link>
{
can(auth.user, 'UPDATE_CONFIG') && (
@@ -57,7 +56,7 @@ const CoralHeader = ({
className={styles.navLink}
to="/admin/configure"
activeClassName={styles.active}>
{lang.t('configure.configure')}
{t('configure.configure')}
</Link>
)
}
@@ -71,8 +70,8 @@ const CoralHeader = ({
<div>
<IconButton name="settings" id="menu-settings"/>
<Menu target="menu-settings" align="right">
<MenuItem onClick={() => showShortcuts(true)}>{lang.t('configure.shortcuts')}</MenuItem>
<MenuItem onClick={handleLogout}>{lang.t('configure.sign-out')}</MenuItem>
<MenuItem onClick={() => showShortcuts(true)}>{t('configure.shortcuts')}</MenuItem>
<MenuItem onClick={handleLogout}>{t('configure.sign_out')}</MenuItem>
</Menu>
</div>
</li>
@@ -91,6 +90,4 @@ CoralHeader.propTypes = {
handleLogout: PropTypes.func.isRequired
};
const lang = new I18n(translations);
export default CoralHeader;
@@ -7,3 +7,8 @@ export const SHOW_SUSPEND_USER_DIALOG = 'SHOW_SUSPEND_USER_DIALOG';
export const HIDE_SUSPEND_USER_DIALOG = 'HIDE_SUSPEND_USER_DIALOG';
export const VIEW_USER_DETAIL = 'VIEW_USER_DETAIL';
export const HIDE_USER_DETAIL = 'HIDE_USER_DETAIL';
export const SET_SORT_ORDER = 'MODERATION_SET_SORT_ORDER';
export const CHANGE_USER_DETAIL_STATUSES = 'CHANGE_USER_DETAIL_STATUSES';
export const SELECT_USER_DETAIL_COMMENT = 'SELECT_USER_DETAIL_COMMENT';
export const UNSELECT_USER_DETAIL_COMMENT = 'UNSELECT_USER_DETAIL_COMMENT';
export const CLEAR_USER_DETAIL_SELECTIONS = 'CLEAR_USER_DETAIL_SELECTIONS';
@@ -1,7 +0,0 @@
import React from 'react';
const Loading = () => (
<h1> Loading results </h1>
);
export default Loading;
@@ -1,81 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {SelectField, Option} from 'react-mdl-selectfield';
import styles from './Community.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations';
import {setRole, setCommenterStatus} from '../../actions/community';
const lang = new I18n(translations);
class Table extends Component {
constructor (props) {
super(props);
this.onRoleChange = this.onRoleChange.bind(this);
}
onRoleChange (id, role) {
this.props.dispatch(setRole(id, role));
}
onCommenterStatusChange (id, status) {
this.props.dispatch(setCommenterStatus(id, status));
}
render () {
const {headers, commenters, onHeaderClickHandler} = this.props;
return (
<table className={`mdl-data-table ${styles.dataTable}`}>
<thead>
<tr>
{headers.map((header, i) =>(
<th
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
{header.title}
</th>
))}
</tr>
</thead>
<tbody>
{commenters.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.username}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
{row.created_at}
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.status || ''}
className={styles.selectField}
label={lang.t('community.status')}
onChange={(status) => this.onCommenterStatusChange(row.id, status)}>
<Option value={'ACTIVE'}>{lang.t('community.active')}</Option>
<Option value={'BANNED'}>{lang.t('community.banned')}</Option>
</SelectField>
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.roles[0] || ''}
className={styles.selectField}
label={lang.t('community.role')}
onChange={(role) => this.onRoleChange(row.id, role)}>
<Option value={''}>.</Option>
<Option value={'STAFF'}>{lang.t('community.staff')}</Option>
<Option value={'MODERATOR'}>{lang.t('community.moderator')}</Option>
<Option value={'ADMIN'}>{lang.t('community.admin')}</Option>
</SelectField>
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default connect((state) => ({commenters: state.community.get('accounts')}))(Table);
@@ -1,48 +0,0 @@
import React from 'react';
import styles from './Dashboard.css';
import {compose} from 'react-apollo';
import {connect} from 'react-redux';
import {getMetrics} from 'coral-admin/src/graphql/queries';
import FlagWidget from './FlagWidget';
import ActivityWidget from './ActivityWidget';
import CountdownTimer from 'coral-admin/src/components/CountdownTimer';
import {Spinner} from 'coral-ui';
class Dashboard extends React.Component {
reloadData = () => {
this.props.data.refetch();
}
render () {
if (this.props.data && this.props.data.loading) {
return <Spinner />;
}
const {data: {assetsByActivity, assetsByFlag}} = this.props;
return (
<div>
<CountdownTimer handleTimeout={this.reloadData} />
<div className={styles.Dashboard}>
<FlagWidget assets={assetsByFlag} />
<ActivityWidget assets={assetsByActivity} />
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
settings: state.settings.toJS(),
moderation: state.moderation.toJS()
};
};
export default compose(
connect(mapStateToProps),
getMetrics
)(Dashboard);
@@ -1,101 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import styles from './style.css';
import {Wizard, WizardNav} from 'coral-ui';
import Layout from 'coral-admin/src/components/ui/Layout';
import {
goToStep,
nextStep,
submitUser,
checkInstall,
previousStep,
finishInstall,
submitSettings,
updateUserFormData,
updateSettingsFormData,
updatePermittedDomains
} from '../../actions/install';
import InitialStep from './components/Steps/InitialStep';
import AddOrganizationName from './components/Steps/AddOrganizationName';
import CreateYourAccount from './components/Steps/CreateYourAccount';
import PermittedDomainsStep from './components/Steps/PermittedDomainsStep';
import FinalStep from './components/Steps/FinalStep';
class InstallContainer extends Component {
componentDidMount() {
const {checkInstall} = this.props;
checkInstall(() => {
this.context.router.push('/admin');
});
}
render() {
const {install} = this.props;
return (
<Layout restricted={true}>
<div className={styles.Install}>
{
!install.alreadyInstalled ? (
<div>
<h2>Welcome to the Coral Project</h2>
{ install.step !== 0 ? <WizardNav items={install.navItems} currentStep={install.step} icon='check'/> : null }
<Wizard currentStep={install.step} {...this.props}>
<InitialStep/>
<AddOrganizationName/>
<CreateYourAccount/>
<PermittedDomainsStep/>
<FinalStep/>
</Wizard>
</div>
) : (
<div>Talk is already installed</div>
)
}
</div>
</Layout>
);
}
}
InstallContainer.contextTypes = {
router: React.PropTypes.object
};
const mapStateToProps = (state) => ({
install: state.install.toJS()
});
const mapDispatchToProps = (dispatch) => ({
nextStep: () => dispatch(nextStep()),
goToStep: (step) => dispatch(goToStep(step)),
previousStep: () => dispatch(previousStep()),
finishInstall: () => dispatch(finishInstall()),
checkInstall: (next) => dispatch(checkInstall(next)),
handleDomainsChange: (value) => {
dispatch(updatePermittedDomains(value));
},
handleSettingsChange: (e) => {
const {name, value} = e.currentTarget;
dispatch(updateSettingsFormData(name, value));
},
handleUserChange: (e) => {
const {name, value} = e.currentTarget;
dispatch(updateUserFormData(name, value));
},
handleSettingsSubmit: (e) => {
e.preventDefault();
dispatch(submitSettings());
},
handleUserSubmit: (e) => {
e.preventDefault();
dispatch(submitUser());
}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(InstallContainer);
@@ -1,19 +0,0 @@
import React from 'react';
import styles from './style.css';
import {Button} from 'coral-ui';
const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
const InitialStep = (props) => {
const {nextStep} = props;
return (
<div className={styles.step}>
<p>{lang.t('INITIAL.DESCRIPTION')}</p>
<Button cStyle='green' onClick={nextStep} raised>{lang.t('INITIAL.SUBMIT')}</Button>
</div>
);
};
export default InitialStep;
@@ -1,58 +0,0 @@
{
"en": {
"INITIAL" : {
"DESCRIPTION": "Let's set up your Talk community in just a few short steps.",
"SUBMIT": "Get Started"
},
"ADD_ORGANIZATION": {
"DESCRIPTION": "Please tell us the name of your organization. This will appear in emails when inviting new team members.",
"LABEL": "Organization Name",
"SAVE": "Save"
},
"CREATE": {
"EMAIL": "Email address",
"USERNAME": "Username",
"PASSWORD": "Password",
"CONFIRM_PASSWORD": "Confirm Password",
"SAVE": "Save"
},
"PERMITTED_DOMAINS": {
"TITLE": "Permitted domains",
"DESCRIPTION": "Enter the domains you would like to permit for Talk, e.g. your local, staging and production environments (ex. localhost:3000, staging.domain.com, domain.com).",
"SUBMIT": "Finish install"
},
"FINAL": {
"DESCRIPTION": "Thanks for installing Talk! We sent an email to verify your email address. While you finish setting up the account, you can start engaging with your readers now.",
"LAUNCH": "Launch Talk",
"CLOSE": "Close this Installer"
}
},
"es": {
"INITIAL" : {
"DESCRIPTION": "Configuremos tu comunidad de Talk en sólo algunos pasos.",
"SUBMIT": "Empezá!"
},
"ADD_ORGANIZATION": {
"DESCRIPTION": "Por favor, dinos el nombre de tu organización. Este aparecerá en los emails cuando invites nuevos miembros.",
"LABEL": "Nombre de la Organización",
"SAVE": "Guardar"
},
"CREATE": {
"EMAIL": "Dirección de E-Mail",
"USERNAME": "Usuario",
"PASSWORD": "Contraseña",
"CONFIRM_PASSWORD": "Confirmar contraseña",
"SAVE": "Guardar"
},
"PERMITTED_DOMAINS": {
"TITLE": "Dominios Permitidos",
"DESCRIPTION": "Agrega dominios permitidos a Talk, e.g. tu localhost, staging y ambientes de production (ex. localhost:3000, staging.domain.com, domain.com).",
"SUBMIT": "Finalizar instalación"
},
"FINAL": {
"DESCRIPTION": "Gracias por instalar Talk! Te enviamos un email para verificar tu identidad. Mientras se termina de configurar la cuenta, ya puedes empezar a interactuar con tus lectores",
"LAUNCH": "Lanzar Talk",
"CLOSE": "Cerrar este instalador"
}
}
}
@@ -1,41 +0,0 @@
.copyButton {
float: right;
top: -10px;
}
.memberSince {
clear: both;
}
.small {
color: #aaa;
}
.stats {
display: flex;
.stat {
margin: 0 4px 12px;
}
.stat:last-child {
margin-right: 0;
}
p {
margin: 0;
}
.stat p:first-child {
font-weight: bold;
}
}
.profileEmail {
border: none;
background-color: transparent;
font-size: 16px;
position: absolute;
width: 100%;
outline: none;
}
@@ -1,74 +0,0 @@
import React, {PropTypes} from 'react';
import {Button, Drawer} from 'coral-ui';
import styles from './UserDetail.css';
import {compose} from 'react-apollo';
import {getUserDetail} from 'coral-admin/src/graphql/queries';
import Slot from 'coral-framework/components/Slot';
class UserDetail extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
hideUserDetail: PropTypes.func.isRequired
}
copyPermalink = () => {
this.profile.select();
try {
document.execCommand('copy');
} catch (e) {
/* nothing */
}
}
render () {
const {data, hideUserDetail} = this.props;
if (!('user' in data)) {
return null;
}
const {user, totalComments, rejectedComments} = data;
const localProfile = user.profiles.find((p) => p.provider === 'local');
let profile;
if (localProfile) {
profile = localProfile.id;
}
let rejectedPercent = rejectedComments / totalComments;
if (rejectedPercent === Infinity || isNaN(rejectedPercent)) {
// if totalComments is 0, you're dividing by zero, which is naughty
rejectedPercent = 0;
}
return (
<Drawer handleClickOutside={hideUserDetail}>
<h3>{user.username}</h3>
<Button className={styles.copyButton} onClick={this.copyPermalink}>Copy</Button>
{profile && <input className={styles.profileEmail} readOnly type="text" ref={(ref) => this.profile = ref} value={profile} />}
<Slot fill="userProfile" user={user} />
<p className={styles.memberSince}><strong>Member since</strong> {new Date(user.created_at).toLocaleString()}</p>
<hr/>
<p>
<strong>Account summary</strong>
<br/><small className={styles.small}>Data represents the last six months of activity</small>
</p>
<div className={styles.stats}>
<div className={styles.stat}>
<p>Total Comments</p>
<p>{totalComments}</p>
</div>
<div className={styles.stat}>
<p>Reject Rate</p>
<p>{`${(rejectedPercent).toFixed(1)}%`}</p>
</div>
</div>
</Drawer>
);
}
}
export default compose(
getUserDetail
)(UserDetail);
@@ -1,34 +0,0 @@
import React, {PropTypes} from 'react';
import {Button} from 'coral-ui';
import styles from './styles.css';
const LoadMore = ({comments, loadMore, sort, tab, assetId, showLoadMore}) =>
<div className={styles.loadMoreContainer}>
{
showLoadMore && <Button
className={styles.loadMore}
onClick={() => {
const lastComment = comments[comments.length - 1];
const cursor = lastComment ? lastComment.created_at : null;
return loadMore({
cursor,
sort,
tab,
asset_id: assetId
});
}}>
Load More
</Button>
}
</div>;
LoadMore.propTypes = {
comments: PropTypes.array.isRequired,
loadMore: PropTypes.func.isRequired,
sort: PropTypes.oneOf(['CHRONOLOGICAL', 'REVERSE_CHRONOLOGICAL']).isRequired,
tab: PropTypes.oneOf(['rejected', 'premod', 'flagged', 'all', 'accepted']).isRequired,
assetId: PropTypes.string,
showLoadMore: PropTypes.bool.isRequired
};
export default LoadMore;
@@ -1,14 +0,0 @@
export const actionsMap = {
PREMOD: ['APPROVE', 'REJECT'],
FLAGGED: ['APPROVE', 'REJECT'],
REJECTED: ['APPROVE', 'REJECTED']
};
export const menuActionsMap = {
'REJECT': {status: 'REJECTED', text: 'Reject', icon: 'close', key: 'r'},
'REJECTED': {status: 'REJECTED', text: 'Rejected', icon: 'close'},
'APPROVE': {status: 'ACCEPTED', text: 'Approve', icon: 'done', key: 't'},
'FLAGGED': {status: 'FLAGGED', text: 'Flag', icon: 'flag', filter: 'Untouched'},
'BAN': {status: 'BANNED', text: 'Ban User', icon: 'not interested'},
'': {icon: 'done'}
};
@@ -1,187 +0,0 @@
import React, {Component} from 'react';
import styles from './Stories.css';
import {connect} from 'react-redux';
import I18n from 'coral-framework/modules/i18n/i18n';
import {fetchAssets, updateAssetState} from '../../actions/assets';
import translations from '../../translations.json';
import {Link} from 'react-router';
import {Pager, Icon} from 'coral-ui';
import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl';
import EmptyCard from 'coral-admin/src/components/EmptyCard';
const limit = 25;
class Stories extends Component {
state = {
search: '',
sort: 'desc',
filter: 'all',
statusMenus: {},
timer: null,
page: 0
}
componentDidMount () {
this.props.fetchAssets(0, limit, '', this.state.sortBy);
}
onSettingChange = (setting) => (e) => {
let options = this.state;
this.setState({[setting]: e.target.value});
options[setting] = e.target.value;
this.props.fetchAssets(0, limit, options.search, options.sort, options.filter);
}
onSearchChange = (e) => {
const search = e.target.value;
this.setState((prevState) => {
prevState.search = search;
clearTimeout(prevState.timer);
const fetchAssets = this.props.fetchAssets;
prevState.timer = setTimeout(() => {
fetchAssets(0, limit, search, this.state.sort, this.state.filter);
}, 350);
return prevState;
});
}
renderDate = (date) => {
const d = new Date(date);
return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
}
onStatusClick = (closeStream, id, statusMenuOpen) => () => {
if (statusMenuOpen) {
this.setState((prev) => {
prev.statusMenus[id] = false;
return prev;
});
this.props.updateAssetState(id, closeStream ? Date.now() : null)
.then(() => {
const {search, sort, filter, page} = this.state;
this.props.fetchAssets(page, limit, search, sort, filter);
});
} else {
this.setState((prev) => {
prev.statusMenus[id] = true;
return prev;
});
}
}
renderTitle = (title, {id}) => <Link to={`/admin/moderate/${id}`}>{title}</Link>
renderStatus = (closedAt, {id}) => {
const closed = closedAt && new Date(closedAt).getTime() < Date.now();
const statusMenuOpen = this.state.statusMenus[id];
return <div className={styles.statusMenu}>
<div
className={closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(closed, id, statusMenuOpen)}>
{!statusMenuOpen && <Icon className={styles.statusMenuIcon} name='keyboard_arrow_down'/>}
{closed ? lang.t('streams.closed') : lang.t('streams.open')}
</div>
{
statusMenuOpen &&
<div
className={!closed ? styles.statusMenuClosed : styles.statusMenuOpen}
onClick={this.onStatusClick(!closed, id, statusMenuOpen)}>
{!closed ? lang.t('streams.closed') : lang.t('streams.open')}
</div>
}
</div>;
}
onPageClick = (page) => {
this.setState({page});
const {search, sort, filter} = this.state;
this.props.fetchAssets((page - 1) * limit, limit, search, sort, filter);
}
render () {
const {search, sort, filter} = this.state;
const {assets} = this.props;
const assetsIds = assets.ids.map((id) => assets.byId[id]);
return (
<div className={styles.container}>
<div className={styles.leftColumn}>
<div className={styles.searchBox}>
<Icon name='search' className={styles.searchIcon}/>
<input
type='text'
value={search}
className={styles.searchBoxInput}
onChange={this.onSearchChange}
placeholder={lang.t('streams.search')}/>
</div>
<div className={styles.optionHeader}>{lang.t('streams.filter-streams')}</div>
<div className={styles.optionDetail}>{lang.t('streams.stream-status')}</div>
<RadioGroup
name='status filter'
value={filter}
childContainer='div'
onChange={this.onSettingChange('filter')}
className={styles.radioGroup}
>
<Radio value='all'>{lang.t('streams.all')}</Radio>
<Radio value='open'>{lang.t('streams.open')}</Radio>
<Radio value='closed'>{lang.t('streams.closed')}</Radio>
</RadioGroup>
<div className={styles.optionHeader}>{lang.t('streams.sort-by')}</div>
<RadioGroup
name='sort by'
value={sort}
childContainer='div'
onChange={this.onSettingChange('sort')}
className={styles.radioGroup}
>
<Radio value='desc'>{lang.t('streams.newest')}</Radio>
<Radio value='asc'>{lang.t('streams.oldest')}</Radio>
</RadioGroup>
</div>
{
assetsIds.length
? <div className={styles.mainContent}>
<DataTable className={styles.streamsTable} rows={assetsIds} onClick={this.goToModeration}>
<TableHeader name="title" cellFormatter={this.renderTitle}>{lang.t('streams.article')}</TableHeader>
<TableHeader name="publication_date" cellFormatter={this.renderDate}>
{lang.t('streams.pubdate')}
</TableHeader>
<TableHeader name="closedAt" cellFormatter={this.renderStatus} className={styles.status}>
{lang.t('streams.status')}
</TableHeader>
</DataTable>
<Pager
totalPages={Math.ceil((assets.count || 0) / limit)}
page={this.state.page}
onNewPageHandler={this.onPageClick} />
</div>
: <EmptyCard>{lang.t('streams.empty_result')}</EmptyCard>
}
</div>
);
}
}
const mapStateToProps = ({assets}) => {
return {
assets: assets.toJS()
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchAssets: (...args) => {
dispatch(fetchAssets.apply(this, args));
},
updateAssetState: (...args) => dispatch(updateAssetState.apply(this, args))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Stories);
const lang = new I18n(translations);
@@ -1,12 +0,0 @@
fragment metrics on Asset {
id
title
url
author
created_at
commentCount
action_summaries {
actionCount
actionableItemCount
}
}
@@ -1,31 +0,0 @@
fragment commentView on Comment {
id
body
created_at
status
user {
id
name: username
status
}
asset {
id
title
url
}
action_summaries {
count
... on FlagActionSummary {
reason
}
}
actions {
... on FlagAction {
reason
message
user {
username
}
}
}
}
+68
View File
@@ -0,0 +1,68 @@
import {add} from 'coral-framework/services/graphqlRegistry';
import update from 'immutability-helper';
const queues = ['all', 'premod', 'flagged', 'accepted', 'rejected'];
const extension = {
mutations: {
SetUserStatus: () => ({
refetchQueries: ['CoralAdmin_Community'],
}),
RejectUsername: () => ({
refetchQueries: ['CoralAdmin_Community'],
}),
SetCommentStatus: ({variables: {commentId, status}}) => ({
updateQueries: {
CoralAdmin_Moderation: (prev) => {
const comment = queues.reduce((comment, queue) => {
return comment ? comment : prev[queue].nodes.find((c) => c.id === commentId);
}, null);
let acceptedNodes = prev.accepted.nodes;
let acceptedCount = prev.acceptedCount;
let rejectedNodes = prev.rejected.nodes;
let rejectedCount = prev.rejectedCount;
if (status !== comment.status) {
if (status === 'ACCEPTED') {
comment.status = 'ACCEPTED';
acceptedCount++;
acceptedNodes = [comment, ...acceptedNodes];
}
else if (status === 'REJECTED') {
comment.status = 'REJECTED';
rejectedCount++;
rejectedNodes = [comment, ...rejectedNodes];
}
}
const premodNodes = prev.premod.nodes.filter((c) => c.id !== commentId);
const flaggedNodes = prev.flagged.nodes.filter((c) => c.id !== commentId);
const premodCount = premodNodes.length < prev.premod.nodes.length ? prev.premodCount - 1 : prev.premodCount;
const flaggedCount = flaggedNodes.length < prev.flagged.nodes.length ? prev.flaggedCount - 1 : prev.flaggedCount;
if (status === 'REJECTED') {
acceptedNodes = prev.accepted.nodes.filter((c) => c.id !== commentId);
acceptedCount = acceptedNodes.length < prev.accepted.nodes.length ? prev.acceptedCount - 1 : prev.acceptedCount;
}
else if (status === 'ACCEPTED') {
rejectedNodes = prev.rejected.nodes.filter((c) => c.id !== commentId);
rejectedCount = rejectedNodes.length < prev.rejected.nodes.length ? prev.rejectedCount - 1 : prev.rejectedCount;
}
return update(prev, {
premodCount: {$set: Math.max(0, premodCount)},
flaggedCount: {$set: Math.max(0, flaggedCount)},
acceptedCount: {$set: Math.max(0, acceptedCount)},
rejectedCount: {$set: Math.max(0, rejectedCount)},
premod: {nodes: {$set: premodNodes}},
flagged: {nodes: {$set: flaggedNodes}},
accepted: {nodes: {$set: acceptedNodes}},
rejected: {nodes: {$set: rejectedNodes}},
});
}
}
}),
},
};
add(extension);
@@ -1,153 +0,0 @@
import {graphql} from 'react-apollo';
import SET_USER_STATUS from './setUserStatus.graphql';
import SET_COMMENT_STATUS from './setCommentStatus.graphql';
import SUSPEND_USER from './suspendUser.graphql';
import REJECT_USERNAME from './rejectUsername.graphql';
export const banUser = graphql(SET_USER_STATUS, {
props: ({mutate}) => ({
banUser: ({userId}) => {
return mutate({
variables: {
userId,
status: 'BANNED'
},
refetchQueries: ['Users']
});
}}),
});
export const setUserStatus = graphql(SET_USER_STATUS, {
props: ({mutate}) => ({
approveUser: ({userId}) => {
return mutate({
variables: {
userId,
status: 'APPROVED'
},
refetchQueries: ['Users']
});
}
})
});
export const suspendUser = graphql(SUSPEND_USER, {
props: ({mutate}) => ({
suspendUser: (input) => {
return mutate({
variables: {
input,
},
});
}
})
});
export const rejectUsername = graphql(REJECT_USERNAME, {
props: ({mutate}) => ({
rejectUsername: (input) => {
return mutate({
variables: {
input,
},
refetchQueries: ['Users']
});
}
})
});
const views = ['all', 'premod', 'flagged', 'accepted', 'rejected'];
export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
props: ({mutate}) => ({
acceptComment: ({commentId}) => {
return mutate({
variables: {
commentId,
status: 'ACCEPTED'
},
updateQueries: {
ModQueue: (oldData) => {
const comment = views.reduce((comment, view) => {
return comment ? comment : oldData[view].find((c) => c.id === commentId);
}, null);
let accepted;
let acceptedCount = oldData.acceptedCount;
// if the comment was already in the Approved queue, don't re-add it
if (comment.status === 'ACCEPTED') {
accepted = [...oldData.accepted];
} else {
comment.status = 'ACCEPTED';
acceptedCount++;
accepted = [comment, ...oldData.accepted];
}
const premod = oldData.premod.filter((c) => c.id !== commentId);
const flagged = oldData.flagged.filter((c) => c.id !== commentId);
const rejected = oldData.rejected.filter((c) => c.id !== commentId);
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
const rejectedCount = rejected.length < oldData.rejected.length ? oldData.rejectedCount - 1 : oldData.rejectedCount;
return {
...oldData,
premodCount: Math.max(0, premodCount),
flaggedCount: Math.max(0, flaggedCount),
acceptedCount: Math.max(0, acceptedCount),
rejectedCount: Math.max(0, rejectedCount),
premod,
flagged,
accepted,
rejected,
};
}
}
});
},
rejectComment: ({commentId}) => {
return mutate({
variables: {
commentId,
status: 'REJECTED'
},
updateQueries: {
ModQueue: (oldData) => {
const comment = views.reduce((comment, view) => {
return comment ? comment : oldData[view].find((c) => c.id === commentId);
}, null);
let rejected;
let rejectedCount = oldData.rejectedCount;
// if the item was already in the Rejected queue, don't put it in again
if (comment.status === 'REJECTED') {
rejected = oldData.rejected;
} else {
comment.status = 'REJECTED';
rejectedCount++;
rejected = [comment, ...oldData.rejected];
}
const premod = oldData.premod.filter((c) => c.id !== commentId);
const flagged = oldData.flagged.filter((c) => c.id !== commentId);
const accepted = oldData.accepted.filter((c) => c.id !== commentId);
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
const acceptedCount = accepted.length < oldData.accepted.length ? oldData.acceptedCount - 1 : oldData.acceptedCount;
return {
...oldData,
premodCount: Math.max(0, premodCount),
flaggedCount: Math.max(0, flaggedCount),
acceptedCount: Math.max(0, acceptedCount),
rejectedCount: Math.max(0, rejectedCount),
premod,
flagged,
accepted,
rejected
};
}
}
});
}
})
});
@@ -1,7 +0,0 @@
mutation rejectUsername($input: RejectUsernameInput!) {
rejectUsername(input: $input) {
errors {
translation_key
}
}
}
@@ -1,7 +0,0 @@
mutation setCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){
setCommentStatus(id: $commentId, status: $status) {
errors {
translation_key
}
}
}
@@ -1,7 +0,0 @@
mutation setUserStatus($userId: ID!, $status: USER_STATUS!) {
setUserStatus(id: $userId, status: $status) {
errors {
translation_key
}
}
}
@@ -1,7 +0,0 @@
mutation suspendUser($input: SuspendUserInput!) {
suspendUser(input: $input) {
errors {
translation_key
}
}
}
@@ -1,6 +0,0 @@
query Assets {
assets {
id
title
}
}
@@ -1,22 +0,0 @@
query Counts ($asset_id: ID) {
allCount: commentCount(query: {
asset_id: $asset_id
})
acceptedCount: commentCount(query: {
statuses: [ACCEPTED],
asset_id: $asset_id
})
premodCount: commentCount(query: {
statuses: [PREMOD],
asset_id: $asset_id
})
rejectedCount: commentCount(query: {
statuses: [REJECTED],
asset_id: $asset_id
})
flaggedCount: commentCount(query: {
action_type: FLAG,
asset_id: $asset_id,
statuses: [NONE, PREMOD]
})
}
@@ -1,116 +0,0 @@
import {graphql} from 'react-apollo';
import MOD_QUEUE_QUERY from './modQueueQuery.graphql';
import MOD_QUEUE_LOAD_MORE from './loadMore.graphql';
import MOD_USER_FLAGGED_QUERY from './modUserFlaggedQuery.graphql';
import METRICS from './metricsQuery.graphql';
import USER_DETAIL from './userDetail.graphql';
import GET_QUEUE_COUNTS from './getQueueCounts.graphql';
export const modQueueQuery = graphql(MOD_QUEUE_QUERY, {
options: ({params: {id = null}}) => {
return {
variables: {
asset_id: id,
sort: 'REVERSE_CHRONOLOGICAL'
}
};
},
props: ({ownProps: {params: {id = null}}, data}) => ({
data,
modQueueResort: modQueueResort(id, data.fetchMore),
loadMore: loadMore(data.fetchMore)
})
});
export const getMetrics = graphql(METRICS, {
options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => {
return {
variables: {
from: dashboardWindowStart,
to: dashboardWindowEnd
}
};
}
});
export const loadMore = (fetchMore) => ({limit = 10, cursor, sort, tab, asset_id}) => {
let variables = {
limit,
cursor,
sort,
asset_id
};
switch(tab) {
case 'all':
variables.statuses = null;
break;
case 'accepted':
variables.statuses = ['ACCEPTED'];
break;
case 'premod':
variables.statuses = ['PREMOD'];
break;
case 'flagged':
variables.statuses = ['NONE', 'PREMOD'];
variables.action_type = 'FLAG';
break;
case 'rejected':
variables.statuses = ['REJECTED'];
break;
}
return fetchMore({
query: MOD_QUEUE_LOAD_MORE,
variables,
updateQuery: (oldData, {fetchMoreResult:{comments}}) => {
return {
...oldData,
[tab]: [
...oldData[tab],
...comments
]
};
}
});
};
export const modUserFlaggedQuery = graphql(MOD_USER_FLAGGED_QUERY, {
options: ({params: {action_type = 'FLAG'}}) => {
return {
variables: {
action_type: action_type
}
};
}
});
export const modQueueResort = (id, fetchMore) => (sort) => {
return fetchMore({
query: MOD_QUEUE_QUERY,
variables: {
asset_id: id,
sort
},
updateQuery: (oldData, {fetchMoreResult:{data}}) => data
});
};
export const getUserDetail = graphql(USER_DETAIL, {
options: ({id}) => {
return {
variables: {author_id: id}
};
}
});
export const getQueueCounts = graphql(GET_QUEUE_COUNTS, {
options: ({params: {id = null}}) => {
return {
pollInterval: 5000,
variables: {
asset_id: id
}
};
}
});
@@ -1,13 +0,0 @@
#import "../fragments/commentView.graphql"
query LoadMoreModQueue($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) {
comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort, action_type: $action_type}) {
...commentView
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
}
@@ -1,10 +0,0 @@
#import "../fragments/assetMetricsView.graphql"
query Metrics ($from: Date!, $to: Date!) {
assetsByFlag: assetMetrics(from: $from, to: $to, sort: FLAG) {
...metrics
}
assetsByActivity: assetMetrics(from: $from, to: $to, sort: ACTIVITY) {
...metrics
}
}
@@ -1,68 +0,0 @@
#import "../fragments/commentView.graphql"
query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
all: comments(query: {
statuses: [NONE, PREMOD, ACCEPTED, REJECTED],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
accepted: comments(query: {
statuses: [ACCEPTED],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
premod: comments(query: {
statuses: [PREMOD],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
flagged: comments(query: {
action_type: FLAG,
asset_id: $asset_id,
statuses: [NONE, PREMOD],
sort: $sort
}) {
...commentView
}
rejected: comments(query: {
statuses: [REJECTED],
asset_id: $asset_id,
sort: $sort
}) {
...commentView
}
assets: assets {
id
title
url
}
allCount: commentCount(query: {
asset_id: $asset_id
})
acceptedCount: commentCount(query: {
statuses: [ACCEPTED],
asset_id: $asset_id
})
premodCount: commentCount(query: {
statuses: [PREMOD],
asset_id: $asset_id
})
rejectedCount: commentCount(query: {
statuses: [REJECTED],
asset_id: $asset_id
})
flaggedCount: commentCount(query: {
action_type: FLAG,
asset_id: $asset_id,
statuses: [NONE, PREMOD]
})
settings {
organizationName
}
}
@@ -1,26 +0,0 @@
query Users ($action_type: ACTION_TYPE) {
users (query:{action_type: $action_type}){
id
username
status
roles
actions{
id
created_at
... on FlagAction {
reason
message
user {
id
username
}
}
}
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
}
@@ -1,13 +0,0 @@
query UserDetail ($author_id: ID!) {
user(id: $author_id) {
id
username
created_at
profiles {
id
provider
}
}
totalComments: commentCount(query: {author_id: $author_id})
rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]})
}
+5
View File
@@ -8,6 +8,11 @@ import store from './services/store';
import App from './components/App';
import 'react-mdl/extra/material.js';
import './graphql';
import {loadPluginsTranslations, injectPluginsReducers} from 'coral-framework/helpers/plugins';
loadPluginsTranslations();
injectPluginsReducers();
render(
<ApolloProvider client={client} store={store}>
+1 -1
View File
@@ -13,5 +13,5 @@ export default {
community,
moderation,
install,
config
config,
};
+20 -2
View File
@@ -1,4 +1,4 @@
import {fromJS, Map} from 'immutable';
import {fromJS, Map, Set} from 'immutable';
import * as actions from '../constants/moderation';
const initialState = fromJS({
@@ -8,8 +8,12 @@ const initialState = fromJS({
commentId: null,
commentStatus: null,
userDetailId: null,
userDetailActiveTab: 'all',
userDetailStatuses: ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'],
userDetailSelectedIds: new Set(),
banDialog: false,
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
sortOrder: 'REVERSE_CHRONOLOGICAL',
suspendUserDialog: {
show: false,
userId: null,
@@ -63,7 +67,21 @@ export default function moderation (state = initialState, action) {
case actions.VIEW_USER_DETAIL:
return state.set('userDetailId', action.userId);
case actions.HIDE_USER_DETAIL:
return state.set('userDetailId', null);
return state
.set('userDetailId', null)
.update('userDetailSelectedIds', (set) => set.clear());
case actions.CLEAR_USER_DETAIL_SELECTIONS:
return state.update('userDetailSelectedIds', (set) => set.clear());
case actions.CHANGE_USER_DETAIL_STATUSES:
return state
.set('userDetailActiveTab', action.tab)
.set('userDetailStatuses', action.statuses);
case actions.SELECT_USER_DETAIL_COMMENT:
return state.update('userDetailSelectedIds', (set) => set.add(action.id));
case actions.UNSELECT_USER_DETAIL_COMMENT:
return state.update('userDetailSelectedIds', (set) => set.delete(action.id));
case actions.SET_SORT_ORDER:
return state.set('sortOrder', action.order);
default :
return state;
}
@@ -1,8 +1,10 @@
import React from 'react';
import styles from '../Community.css';
import styles from './Community.css';
import BanUserButton from './BanUserButton';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../../../containers/ModerationQueue/helpers/moderationQueueActionsMap';
import {menuActionsMap} from '../../Moderation/helpers/moderationQueueActionsMap';
import t from 'coral-framework/services/i18n';
const ActionButton = ({type = '', user, ...props}) => {
if (type === 'BAN') {
@@ -17,7 +19,7 @@ const ActionButton = ({type = '', user, ...props}) => {
onClick={() => {
type === 'APPROVE' ? props.approveUser({userId: user.id}) : props.showSuspendUserDialog({user: user});
}}
>{menuActionsMap[type].text}</Button>
>{t(`modqueue.${menuActionsMap[type].text}`)}</Button>
);
};
@@ -2,9 +2,7 @@ import React, {PropTypes} from 'react';
import {Button, Icon} from 'coral-ui';
import styles from './BanUserButton.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const BanUserButton = ({user, ...props}) => (
<div className={styles.ban}>
@@ -14,7 +12,7 @@ const BanUserButton = ({user, ...props}) => (
onClick={props.onClick}
raised>
<Icon name='not_interested' />
{lang.t('comment.ban_user')}
{t('comment.ban_user')}
</Button>
</div>
);
@@ -4,10 +4,7 @@ import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
<Dialog
@@ -16,19 +13,19 @@ const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
open={open}
onClose={handleClose}
onCancel={handleClose}
title={lang.t('community.ban_user')}>
title={t('community.ban_user')}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h2>{lang.t('community.ban_user')}</h2>
<h2>{t('community.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{lang.t('community.are_you_sure', user.username)}</h3>
<i>{lang.t('community.note')}</i>
<h3>{t('community.are_you_sure', user.username)}</h3>
<i>{t('community.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{lang.t('community.cancel')}
{t('community.cancel')}
</Button>
<Button
cStyle="black" className={styles.ban}
@@ -38,7 +35,7 @@ const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
});
}}
raised>
{lang.t('community.yes_ban_user')}
{t('community.yes_ban_user')}
</Button>
</div>
</div>
@@ -1,55 +1,26 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {compose} from 'react-apollo';
import {modUserFlaggedQuery} from 'coral-admin/src/graphql/queries';
import {banUser, setUserStatus, rejectUsername} from 'coral-admin/src/graphql/mutations';
import {
fetchAccounts,
updateSorting,
newPage,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog
} from '../../actions/community';
import CommunityMenu from './components/CommunityMenu';
import BanUserDialog from './components/BanUserDialog';
import SuspendUserDialog from './components/SuspendUserDialog';
import CommunityMenu from './CommunityMenu';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import People from './People';
import FlaggedAccounts from './FlaggedAccounts';
class CommunityContainer extends Component {
export default class Community extends Component {
constructor(props) {
super(props);
state = {
searchValue: '',
timer: null
};
this.state = {
searchValue: '',
timer: null
};
this.onKeyDownHandler = this.onKeyDownHandler.bind(this);
this.onSearchChange = this.onSearchChange.bind(this);
this.onHeaderClickHandler = this.onHeaderClickHandler.bind(this);
this.onNewPageHandler = this.onNewPageHandler.bind(this);
}
componentWillMount() {
this.props.fetchAccounts({});
}
onKeyDownHandler(e) {
onKeyDownHandler = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.search();
}
}
onSearchChange(e) {
onSearchChange = (e) => {
const value = e.target.value;
this.setState((prevState) => {
prevState.searchValue = value;
@@ -62,6 +33,16 @@ class CommunityContainer extends Component {
});
}
onHeaderClickHandler = (sort) => {
this.props.updateSorting(sort);
this.search();
}
onNewPageHandler = (page) => {
this.props.newPage(page);
this.search({page});
}
search(query = {}) {
const {community} = this.props;
@@ -71,21 +52,10 @@ class CommunityContainer extends Component {
asc: community.ascPeople,
...query
});
}
onHeaderClickHandler(sort) {
this.props.dispatch(updateSorting(sort));
this.search();
}
onNewPageHandler(page) {
this.props.dispatch(newPage(page));
this.search({page});
}
getTabContent(searchValue, props) {
const {community, data} = props;
const {community, root: {users}} = props;
const activeTab = props.route.path === ':id' ? 'flagged' : props.route.path;
if (activeTab === 'people') {
@@ -108,9 +78,7 @@ class CommunityContainer extends Component {
return (
<div>
<FlaggedAccounts
commenters={data.users}
isFetching={data.loading}
error={data.error}
commenters={users}
showBanUserDialog={props.showBanUserDialog}
approveUser={props.approveUser}
rejectUsername={props.rejectUsername}
@@ -134,7 +102,6 @@ class CommunityContainer extends Component {
render() {
const {searchValue} = this.state;
const tab = this.getTabContent(searchValue, this.props);
return (
@@ -148,22 +115,3 @@ class CommunityContainer extends Component {
}
}
const mapStateToProps = (state) => ({
community: state.community.toJS()
});
const mapDispatchToProps = (dispatch) => ({
fetchAccounts: (query) => dispatch(fetchAccounts(query)),
showBanUserDialog: (user) => dispatch(showBanUserDialog(user)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
showSuspendUserDialog: (user) => dispatch(showSuspendUserDialog(user)),
hideSuspendUserDialog: () => dispatch(hideSuspendUserDialog())
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
modUserFlaggedQuery,
banUser,
setUserStatus,
rejectUsername
)(CommunityContainer);
@@ -1,14 +1,9 @@
import React from 'react';
import styles from './styles.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
import t from 'coral-framework/services/i18n';
import {Link} from 'react-router';
const lang = new I18n(translations);
const CommunityMenu = () => {
const flaggedPath = '/admin/community/flagged';
const peoplePath = '/admin/community/people';
@@ -17,10 +12,10 @@ const CommunityMenu = () => {
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<div>
<Link to={flaggedPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('community.flaggedaccounts')}
{t('community.flaggedaccounts')}
</Link>
<Link to={peoplePath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('community.people')}
{t('community.people')}
</Link>
</div>
</div>
@@ -1,25 +1,19 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
import styles from './Community.css';
import Loading from './Loading';
import EmptyCard from 'coral-admin/src/components/EmptyCard';
import User from './components/User';
import User from './User';
const FlaggedAccounts = ({...props}) => {
const {commenters, isFetching} = props;
const hasResults = !isFetching && commenters && !!commenters.length;
const {commenters} = props;
const hasResults = commenters && !!commenters.length;
// if (commenter.status === 'PENDING' && commenter.actions.length > 0) {
return (
<div className={styles.container}>
<div className={styles.mainFlaggedContent}>
{ isFetching && <Loading /> }
{
hasResults
? commenters.map((commenter, index) => {
@@ -34,7 +28,7 @@ const FlaggedAccounts = ({...props}) => {
suspendUser={props.suspendUser}
/>;
})
: <EmptyCard>{lang.t('community.no-flagged-accounts')}</EmptyCard>
: <EmptyCard>{t('community.no_flagged_accounts')}</EmptyCard>
}
</div>
</div>
@@ -1,29 +1,26 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
import styles from './Community.css';
import Table from './Table';
import Table from '../containers/Table';
import {Pager, Icon} from 'coral-ui';
import EmptyCard from '../../components/EmptyCard';
const lang = new I18n(translations);
import EmptyCard from '../../../components/EmptyCard';
import t from 'coral-framework/services/i18n';
const tableHeaders = [
{
title: lang.t('community.username_and_email'),
title: t('community.username_and_email'),
field: 'username'
},
{
title: lang.t('community.account_creation_date'),
title: t('community.account_creation_date'),
field: 'created_at'
},
{
title: lang.t('community.status'),
title: t('community.status'),
field: 'status'
},
{
title: lang.t('community.newsroom_role'),
title: t('community.newsroom_role'),
field: 'role'
}
];
@@ -41,7 +38,7 @@ const People = ({commenters, searchValue, onSearchChange, ...props}) => {
className={styles.searchBoxInput}
value={searchValue}
onChange={onSearchChange}
placeholder={lang.t('streams.search')}
placeholder={t('streams.search')}
/>
</div>
</div>
@@ -53,7 +50,7 @@ const People = ({commenters, searchValue, onSearchChange, ...props}) => {
commenters={commenters}
onHeaderClickHandler={props.onHeaderClickHandler}
/>
: <EmptyCard>{lang.t('community.no-results')}</EmptyCard>
: <EmptyCard>{t('community.no_results')}</EmptyCard>
}
<Pager
totalPages={props.totalPages}
@@ -3,10 +3,7 @@ import React, {Component, PropTypes} from 'react';
import {Dialog, Button} from 'coral-ui';
import styles from './SuspendUserDialog.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const stages = [
{
@@ -38,7 +35,7 @@ class SuspendUserDialog extends Component {
}
componentDidMount() {
this.setState({email: lang.t('suspenduser.email_message_reject'), about: lang.t('suspenduser.username')});
this.setState({email: t('suspenduser.email_message_reject'), about: t('suspenduser.username')});
}
/*
@@ -79,18 +76,18 @@ class SuspendUserDialog extends Component {
open={open}
onClose={handleClose}
onCancel={handleClose}
title={lang.t('suspenduser.suspend_user')}>
title={t('suspenduser.suspend_user')}>
<div className={styles.title}>
{lang.t(stages[stage].title, lang.t('suspenduser.username'))}
{t(stages[stage].title, t('suspenduser.username'))}
</div>
<div className={styles.container}>
<div className={styles.description}>
{lang.t(stages[stage].description, lang.t('suspenduser.username'))}
{t(stages[stage].description, t('suspenduser.username'))}
</div>
{
stage === 1 &&
<div className={styles.writeContainer}>
<div className={styles.emailMessage}>{lang.t('suspenduser.write_message')}</div>
<div className={styles.emailMessage}>{t('suspenduser.write_message')}</div>
<div className={styles.emailContainer}>
<textarea
rows={5}
@@ -103,7 +100,7 @@ class SuspendUserDialog extends Component {
<div className={styles.modalButtons}>
{Object.keys(stages[stage].options).map((key, i) => (
<Button key={i} onClick={this.onActionClick(stage, i)}>
{lang.t(stages[stage].options[key], lang.t('suspenduser.username'))}
{t(stages[stage].options[key], t('suspenduser.username'))}
</Button>
))}
</div>
@@ -0,0 +1,54 @@
import React from 'react';
import {SelectField, Option} from 'react-mdl-selectfield';
import styles from '../components/Community.css';
import t from 'coral-framework/services/i18n';
export default ({headers, commenters, onHeaderClickHandler, onRoleChange, onCommenterStatusChange}) => (
<table className={`mdl-data-table ${styles.dataTable}`}>
<thead>
<tr>
{headers.map((header, i) =>(
<th
key={i}
className="mdl-data-table__cell--non-numeric"
onClick={() => onHeaderClickHandler({field: header.field})}>
{header.title}
</th>
))}
</tr>
</thead>
<tbody>
{commenters.map((row, i)=> (
<tr key={i}>
<td className="mdl-data-table__cell--non-numeric">
{row.username}
<span className={styles.email}>{row.profiles.map(({id}) => id)}</span>
</td>
<td className="mdl-data-table__cell--non-numeric">
{row.created_at}
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.status || ''}
className={styles.selectField}
label={t('community.status')}
onChange={(status) => onCommenterStatusChange(row.id, status)}>
<Option value={'ACTIVE'}>{t('community.active')}</Option>
<Option value={'BANNED'}>{t('community.banned')}</Option>
</SelectField>
</td>
<td className="mdl-data-table__cell--non-numeric">
<SelectField label={'Select me'} value={row.roles[0] || ''}
className={styles.selectField}
label={t('community.role')}
onChange={(role) => onRoleChange(row.id, role)}>
<Option value={''}>.</Option>
<Option value={'STAFF'}>{t('community.staff')}</Option>
<Option value={'MODERATOR'}>{t('community.moderator')}</Option>
<Option value={'ADMIN'}>{t('community.admin')}</Option>
</SelectField>
</td>
</tr>
))}
</tbody>
</table>
);
@@ -1,12 +1,17 @@
import React from 'react';
import styles from '../Community.css';
import styles from './Community.css';
import ActionButton from './ActionButton';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../../translations.json';
import t from 'coral-framework/services/i18n';
const lang = new I18n(translations);
const shortReasons = {
'This comment is offensive': t('community.offensive'),
'This looks like an ad/marketing': t('community.spam_ads'),
'This user is impersonating': t('community.impersonating'),
'I don\'t like this username': t('community.dont_like_username'),
'Other': t('community.other')
};
// Render a single user for the list
const User = (props) => {
@@ -35,11 +40,11 @@ const User = (props) => {
<div className={styles.itemBody}>
<div className={styles.body}>
<div className={styles.flaggedByCount}>
<i className="material-icons">flag</i><span className={styles.flaggedByLabel}>{lang.t('community.flags')}({ user.actions.length })</span>:
<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}>
{lang.t(`community.${action.reason}`)} ({action.count})
{shortReasons[action.reason]} ({action.count})
</span>;
}
)}
@@ -49,7 +54,7 @@ const User = (props) => {
(action_sum, i) => {
return <div key={i}>
<span className={styles.flaggedByLabel}>
{lang.t(`community.${action_sum.reason}`)} ({action_sum.count})
{shortReasons[action_sum.reason]} ({action_sum.count})
</span>
{user.actions.map(
@@ -0,0 +1,107 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose, gql} from 'react-apollo';
import withQuery from 'coral-framework/hocs/withQuery';
import {Spinner} from 'coral-ui';
import {withSetUserStatus, withRejectUsername} from 'coral-framework/graphql/mutations';
import {
fetchAccounts,
updateSorting,
newPage,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog
} from '../../../actions/community';
import Community from '../components/Community';
class CommunityContainer extends Component {
componentWillMount() {
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}/>
);
}
}
export const withCommunityQuery = withQuery(gql`
query CoralAdmin_Community($action_type: ACTION_TYPE) {
users(query:{action_type: $action_type}){
id
username
status
roles
actions{
id
created_at
... on FlagAction {
reason
message
user {
id
username
}
}
}
action_summaries {
count
... on FlagActionSummary {
reason
}
}
}
}
`, {
options: ({params: {action_type = 'FLAG'}}) => {
return {
variables: {
action_type: action_type
}
};
}
});
const mapStateToProps = (state) => ({
community: state.community.toJS()
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
fetchAccounts,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog,
updateSorting,
newPage,
}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withCommunityQuery,
withSetUserStatus,
withRejectUsername,
)(CommunityContainer);
@@ -0,0 +1,37 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import {setRole, setCommenterStatus} from '../../../actions/community';
import Table from '../components/Table';
class TableContainer extends Component {
constructor (props) {
super(props);
}
render () {
return <Table
{...this.props}
onRoleChange={this.props.setRole}
onCommenterStatusChange={this.props.setCommenterStatus}
commenters={this.props.commenters}
/>;
}
}
const mapStateToProps = (state) => ({
commenters: state.community.get('accounts'),
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
setCommenterStatus,
setRole,
}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
)(TableContainer);
@@ -0,0 +1,2 @@
export {default as Community} from './containers/Community';
export {default as CommunityLayout} from './components/CommunityLayout';
@@ -99,7 +99,7 @@
.inlineTextfield {
border-color: #ccc;
border-style: solid;
border-width: 0px 0px 1px 0px;
border-width: 0px 0px 1px 0px;
text-align: center;
font-size: inherit;
}
@@ -108,7 +108,7 @@
outline: none;
}
.charCountTexfield {
.charCountTexfield, .editCommentTimeframeTextfield {
width: 4em;
padding: 0px;
}
@@ -1,61 +1,43 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {
fetchSettings,
updateSettings,
saveSettingsToServer,
updateWordlist,
updateDomainlist
} from '../../actions/settings';
import {Button, List, Item, Card, Spinner} from 'coral-ui';
import styles from './Configure.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
import StreamSettings from './StreamSettings';
import ModerationSettings from './ModerationSettings';
import TechSettings from './TechSettings';
import t from 'coral-framework/services/i18n';
import {can} from 'coral-framework/services/perms';
class Configure extends Component {
constructor (props) {
super(props);
export default class Configure extends Component {
this.state = {
activeSection: 'stream',
changed: false,
errors: {}
};
this.changeSection = this.changeSection.bind(this);
}
componentWillMount = () => {
this.props.dispatch(fetchSettings());
}
state = {
activeSection: 'stream',
changed: false,
errors: {}
};
saveSettings = () => {
this.props.dispatch(saveSettingsToServer());
this.props.saveSettingsToServer();
this.setState({changed: false});
}
changeSection(activeSection) {
changeSection = (activeSection) => {
this.setState({activeSection});
}
onChangeWordlist = (listName, list) => {
this.setState({changed: true});
this.props.dispatch(updateWordlist(listName, list));
this.props.updateWordlist(listName, list);
}
onChangeDomainlist = (listName, list) => {
this.setState({changed: true});
this.props.dispatch(updateDomainlist(listName, list));
this.props.updateDomainlist(listName, list);
}
onSettingUpdate = (setting) => {
this.setState({changed: true});
this.props.dispatch(updateSettings(setting));
this.props.updateSettings(setting);
}
// Sets an arbitrary error string and a boolean state.
@@ -106,11 +88,11 @@ class Configure extends Component {
getPageTitle (section) {
switch(section) {
case 'stream':
return lang.t('configure.stream-settings');
return t('configure.stream_settings');
case 'moderation':
return lang.t('configure.moderation-settings');
return t('configure.moderation_settings');
case 'tech':
return lang.t('configure.tech-settings');
return t('configure.tech_settings');
default:
return '';
}
@@ -133,13 +115,13 @@ class Configure extends Component {
<div className={styles.leftColumn}>
<List onChange={this.changeSection} activeItem={activeSection}>
<Item itemId='stream' icon='speaker_notes'>
{lang.t('configure.stream-settings')}
{t('configure.stream_settings')}
</Item>
<Item itemId='moderation' icon='thumbs_up_down'>
{lang.t('configure.moderation-settings')}
{t('configure.moderation_settings')}
</Item>
<Item itemId='tech' icon='code'>
{lang.t('configure.tech-settings')}
{t('configure.tech_settings')}
</Item>
</List>
<div className={styles.saveBox}>
@@ -152,7 +134,7 @@ class Configure extends Component {
icon='check'
full
>
{lang.t('configure.save-changes')}
{t('configure.save_changes')}
</Button>
:
<Button
@@ -161,7 +143,7 @@ class Configure extends Component {
icon='check'
full
>
{lang.t('configure.save-changes')}
{t('configure.save_changes')}
</Button>
}
</div>
@@ -176,11 +158,3 @@ class Configure extends Component {
);
}
}
const mapStateToProps = (state) => ({
auth: state.auth.toJS(),
settings: state.settings.toJS()
});
export default connect(mapStateToProps)(Configure);
const lang = new I18n(translations);
@@ -2,17 +2,14 @@ import React from 'react';
import {Card} from 'coral-ui';
import styles from './Configure.css';
import TagsInput from 'react-tagsinput';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const Domainlist = ({domains, onChangeDomainlist}) => {
return (
<Card id={styles.domainlist} className={styles.configSetting}>
<div className={styles.wrapper}>
<div className={styles.settingsHeader}>{lang.t('configure.domain-list-title')}</div>
<p className={styles.domainlistDesc}>{lang.t('configure.domain-list-text')}</p>
<div className={styles.settingsHeader}>{t('configure.domain_list_title')}</div>
<p className={styles.domainlistDesc}>{t('configure.domain_list_text')}</p>
<div className={styles.wrapper}>
<TagsInput
value={domains}
@@ -1,6 +1,5 @@
import React, {Component} from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import t from 'coral-framework/services/i18n';
import styles from './Configure.css';
import {Button, Card} from 'coral-ui';
@@ -47,11 +46,11 @@ class EmbedLink extends Component {
<Card shadow="2" className={styles.configSetting}>
<div className={styles.wrapper}>
<div className={styles.settingsHeader}>Embed Comment Stream</div>
<p>{lang.t('configure.copy-and-paste')}</p>
<p>{t('configure.copy_and_paste')}</p>
<textarea rows={5} type='text' className={styles.embedInput} value={embedText} readOnly={true}/>
<div className={styles.actions}>
<Button raised className={styles.copyButton} onClick={this.copyToClipBoard} cStyle="black">
{lang.t('embedlink.copy')}
{t('embedlink.copy')}
</Button>
<div className={styles.copiedText}>
{this.state.copied && 'Copied!'}
@@ -64,5 +63,3 @@ class EmbedLink extends Component {
}
export default EmbedLink;
const lang = new I18n(translations);
@@ -3,9 +3,7 @@ import styles from './Configure.css';
import {Card} from 'coral-ui';
import {Checkbox} from 'react-mdl';
import Wordlist from './Wordlist';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const updateModeration = (updateSettings, mod) => () => {
const moderation = mod === 'PRE' ? 'POST' : 'PRE';
@@ -27,12 +25,6 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
const on = styles.enabledSetting;
const off = styles.disabledSetting;
const onChangeEditCommentWindowLength = (e) => {
const value = e.target.value;
const valueAsNumber = parseFloat(value);
const milliseconds = (!isNaN(valueAsNumber)) && (valueAsNumber * 1000);
updateSettings({editCommentWindowLength: milliseconds || value});
};
return (
<div className={styles.Configure}>
<Card className={`${styles.configSetting} ${settings.requireEmailConfirmation ? on : off}`}>
@@ -42,9 +34,9 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
checked={settings.requireEmailConfirmation} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.require-email-verification')}</div>
<div className={styles.settingsHeader}>{t('configure.require_email_verification')}</div>
<p className={settings.requireEmailConfirmation ? '' : styles.disabledSettingText}>
{lang.t('configure.require-email-verification-text')}
{t('configure.require_email_verification_text')}
</p>
</div>
</Card>
@@ -55,9 +47,9 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
checked={settings.moderation === 'PRE'} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.enable-pre-moderation')}</div>
<div className={styles.settingsHeader}>{t('configure.enable_pre_moderation')}</div>
<p className={settings.moderation === 'PRE' ? '' : styles.disabledSettingText}>
{lang.t('configure.enable-pre-moderation-text')}
{t('configure.enable_pre_moderation_text')}
</p>
</div>
</Card>
@@ -68,9 +60,9 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
checked={settings.premodLinksEnable} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.enable-premod-links')}</div>
<div className={styles.settingsHeader}>{t('configure.enable_premod_links')}</div>
<p>
{lang.t('configure.enable-premod-links-text')}
{t('configure.enable_premod_links_text')}
</p>
</div>
</Card>
@@ -78,27 +70,6 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
bannedWords={settings.wordlist.banned}
suspectWords={settings.wordlist.suspect}
onChangeWordlist={onChangeWordlist} />
{/* Edit Comment Timeframe */}
<Card className={styles.configSetting}>
<div className={styles.settingsHeader}>{lang.t('configure.edit-comment-timeframe-heading')}</div>
<p>
{lang.t('configure.edit-comment-timeframe-text-pre')}
&nbsp;
<input
style={{width: '3em'}}
className={styles.inlineTextfield}
type="number"
min="0"
onChange={onChangeEditCommentWindowLength}
placeholder="30"
defaultValue={(settings.editCommentWindowLength / 1000) /* saved as ms, rendered as seconds */}
pattern='[0-9]+([\.][0-9]*)?'
/>
&nbsp;
{lang.t('configure.edit-comment-timeframe-text-post')}
</p>
</Card>
</div>
);
};
@@ -1,7 +1,6 @@
import React from 'react';
import {SelectField, Option} from 'react-mdl-selectfield';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import t from 'coral-framework/services/i18n';
import styles from './Configure.css';
import {Checkbox, Textfield} from 'react-mdl';
import {Card, Icon, TextArea} from 'coral-ui';
@@ -62,6 +61,13 @@ const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => {
}
};
const updateEditCommentWindowLength = (updateSettings) => (e) => {
const value = e.target.value;
const valueAsNumber = parseFloat(value);
const milliseconds = (!isNaN(valueAsNumber)) && (valueAsNumber * 1000);
updateSettings({editCommentWindowLength: milliseconds || value});
};
const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
// just putting this here for shorthand below
@@ -77,9 +83,9 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
checked={settings.charCountEnable} />
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>{lang.t('configure.comment-count-header')}</div>
<div className={styles.settingsHeader}>{t('configure.comment_count_header')}</div>
<p className={settings.charCountEnable ? '' : styles.disabledSettingText}>
<span>{lang.t('configure.comment-count-text-pre')}</span>
<span>{t('configure.comment_count_text_pre')}</span>
<input type='text'
className={`${styles.inlineTextfield} ${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
htmlFor='charCount'
@@ -87,13 +93,13 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
value={settings.charCount}
disabled={settings.charCountEnable ? '' : 'disabled'}
/>
<span>{lang.t('configure.comment-count-text-post')}</span>
<span>{t('configure.comment_count_text_post')}</span>
{
errors.charCount &&
<span className={styles.settingsError}>
<br/>
<Icon name="error_outline"/>
{lang.t('configure.comment-count-error')}
{t('configure.comment_count_error')}
</span>
}
</p>
@@ -107,10 +113,10 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
</div>
<div className={styles.content}>
<div className={styles.settingsHeader}>
{lang.t('configure.include-comment-stream')}
{t('configure.include_comment_stream')}
</div>
<p className={settings.infoBoxEnable ? '' : styles.disabledSettingText}>
{lang.t('configure.include-comment-stream-desc')}
{t('configure.include_comment_stream_desc')}
</p>
<div className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? null : styles.hidden}`} >
<MarkdownEditor
@@ -123,8 +129,8 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
</Card>
<Card className={`${styles.configSetting} ${styles.configSettingInfoBox}`}>
<div className={styles.wrapper}>
<div className={styles.settingsHeader}>{lang.t('configure.closed-stream-settings')}</div>
<p>{lang.t('configure.closed-comments-desc')}</p>
<div className={styles.settingsHeader}>{t('configure.closed_stream_settings')}</div>
<p>{t('configure.closed_comments_desc')}</p>
<div>
<TextArea className={styles.descriptionBox}
onChange={updateClosedMessage(updateSettings)}
@@ -133,6 +139,25 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
</div>
</div>
</Card>
{/* Edit Comment Timeframe */}
<Card className={styles.configSetting}>
<div className={styles.settingsHeader}>{t('configure.edit_comment_timeframe_heading')}</div>
<p>
{t('configure.edit_comment_timeframe_text_pre')}
&nbsp;
<input
className={`${styles.inlineTextfield} ${styles.editCommentTimeframeTextfield}`}
type="number"
min="0"
onChange={updateEditCommentWindowLength(updateSettings)}
placeholder="30"
defaultValue={(settings.editCommentWindowLength / 1000) /* saved as ms, rendered as seconds */}
pattern='[0-9]+([\.][0-9]*)?'
/>
&nbsp;
{t('configure.edit_comment_timeframe_text_post')}
</p>
</Card>
<Card className={`${styles.configSetting} ${styles.configSettingInfoBox}`}>
<div className={styles.action}>
<Checkbox
@@ -140,7 +165,7 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
checked={settings.autoCloseStream} />
</div>
<div className={styles.content}>
{lang.t('configure.close-after')}
{t('configure.close_after')}
<br />
<Textfield
type='number'
@@ -148,15 +173,15 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
style={{width: 50}}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout)}
value={getTimeoutAmount(settings.closedTimeout)}
label={lang.t('configure.closed-comments-label')} />
label={t('configure.closed_comments_label')} />
<div className={styles.configTimeoutSelect}>
<SelectField
label="comments closed time window"
value={getTimeoutMeasure(settings.closedTimeout)}
onChange={updateClosedTimeout(updateSettings, settings.closedTimeout, true)}>
<Option value={'hours'}>{lang.t('configure.hours')}</Option>
<Option value={'days'}>{lang.t('configure.days')}</Option>
<Option value={'weeks'}>{lang.t('configure.weeks')}</Option>
<Option value={'hours'}>{t('configure.hours')}</Option>
<Option value={'days'}>{t('configure.days')}</Option>
<Option value={'weeks'}>{t('configure.weeks')}</Option>
</SelectField>
</div>
</div>
@@ -183,5 +208,3 @@ const getTimeoutMeasure = (ts) => {
// Dividing the amount by it's measure (hours, days, weeks) we
// obtain the amount of time
const getTimeoutAmount = (ts) => ts / TIMESTAMPS[getTimeoutMeasure(ts)];
const lang = new I18n(translations);
@@ -3,9 +3,7 @@ import {Card} from 'coral-ui';
import Domainlist from './Domainlist';
import EmbedLink from './EmbedLink';
import styles from './Configure.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const updateCustomCssUrl = (updateSettings) => (event) => {
const customCssUrl = event.target.value;
@@ -21,8 +19,8 @@ const TechSettings = ({settings, onChangeDomainlist, updateSettings}) => {
<EmbedLink />
<Card className={styles.configSetting}>
<div className={styles.wrapper}>
<div className={styles.settingsHeader}>{lang.t('configure.custom-css-url')}</div>
<p>{lang.t('configure.custom-css-url-desc')}</p>
<div className={styles.settingsHeader}>{t('configure.custom_css_url')}</div>
<p>{t('configure.custom_css_url_desc')}</p>
<input
className={styles.customCSSInput}
value={settings.customCssUrl}
@@ -1,6 +1,5 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import t from 'coral-framework/services/i18n';
import TagsInput from 'react-tagsinput';
import styles from './Configure.css';
import {Card} from 'coral-ui';
@@ -8,8 +7,8 @@ import {Card} from 'coral-ui';
const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
<div>
<Card id={styles.bannedWordlist} className={styles.configSetting}>
<div className={styles.settingsHeader}>{lang.t('configure.banned-words-title')}</div>
<p className={styles.wordlistDesc}>{lang.t('configure.banned-word-text')}</p>
<div className={styles.settingsHeader}>{t('configure.banned_words_title')}</div>
<p className={styles.wordlistDesc}>{t('configure.banned_word_text')}</p>
<div className={styles.wrapper}>
<TagsInput
value={bannedWords}
@@ -21,8 +20,8 @@ const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
</div>
</Card>
<Card id={styles.suspectWordlist} className={styles.configSetting}>
<div className={styles.settingsHeader}>{lang.t('configure.suspect-words-title')}</div>
<p className={styles.wordlistDesc}>{lang.t('configure.suspect-word-text')}</p>
<div className={styles.settingsHeader}>{t('configure.suspect_word_title')}</div>
<p className={styles.wordlistDesc}>{t('configure.suspect_word_text')}</p>
<div className={styles.wrapper}>
<TagsInput
value={suspectWords}
@@ -36,5 +35,3 @@ const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
);
export default Wordlist;
const lang = new I18n(translations);
@@ -0,0 +1,42 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import {
fetchSettings,
updateSettings,
saveSettingsToServer,
updateWordlist,
updateDomainlist
} from '../../../actions/settings';
import Configure from '../components/Configure';
class ConfigureContainer extends Component {
componentWillMount = () => {
this.props.fetchSettings();
}
render () {
return <Configure {...this.props} />;
}
}
const mapStateToProps = (state) => ({
auth: state.auth.toJS(),
settings: state.settings.toJS()
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
fetchSettings,
updateSettings,
saveSettingsToServer,
updateWordlist,
updateDomainlist
}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
)(ConfigureContainer);
@@ -0,0 +1 @@
export {default} from './containers/Configure';
@@ -1,18 +1,15 @@
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './Widget.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const ActivityWidget = ({assets}) => {
return (
<div className={styles.widget}>
<h2 className={styles.heading}>Articles with the most conversations</h2>
<h2 className={styles.heading}>{t('dashboard.most_conversations')}</h2>
<div className={styles.widgetHead}>
<p>{lang.t('streams.article')}</p>
<p>{lang.t('dashboard.comment_count')}</p>
<p>{t('streams.article')}</p>
<p>{t('dashboard.comment_count')}</p>
</div>
<div className={styles.widgetTable}>
{
@@ -29,7 +26,7 @@ const ActivityWidget = ({assets}) => {
</div>
);
})
: <div className={styles.rowLinkify}>{lang.t('dashboard.no_activity')}</div>
: <div className={styles.rowLinkify}>{t('dashboard.no_activity')}</div>
}
</div>
</div>
@@ -1,10 +1,8 @@
import React, {PropTypes} from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
import styles from 'coral-admin/src/containers/Dashboard/Dashboard.css';
import styles from './Dashboard.css';
import {Icon} from 'coral-ui';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const refreshIntervalSeconds = 60 * 5;
class CountdownTimer extends React.Component {
@@ -74,7 +72,7 @@ class CountdownTimer extends React.Component {
className={styles.autoUpdate}
onClick={this.dismissNote}>
<b>×</b>
<Icon name='timer' /> <strong>{lang.t('dashboard.next-update', this.formatTime())}</strong> {lang.t('dashboard.auto-update')}
<Icon name='timer' /> <strong>{t('dashboard.next_update', this.formatTime())}</strong> {t('dashboard.auto_update')}
</p>
);
}
@@ -0,0 +1,15 @@
import React from 'react';
import FlagWidget from './FlagWidget';
import ActivityWidget from './ActivityWidget';
import CountdownTimer from './CountdownTimer';
import styles from './Dashboard.css';
export default ({root: {assetsByActivity, assetsByFlag}, reloadData}) => (
<div>
<CountdownTimer handleTimeout={reloadData} />
<div className={styles.Dashboard}>
<FlagWidget assets={assetsByFlag} />
<ActivityWidget assets={assetsByActivity} />
</div>
</div>
);
@@ -1,19 +1,17 @@
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './Widget.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const FlagWidget = ({assets}) => {
return (
<div className={styles.widget}>
<h2 className={styles.heading}>{lang.t('dashboard.most_flags')}</h2>
<h2 className={styles.heading}>{t('dashboard.most_flags')}</h2>
<div className={styles.widgetHead}>
<p>{lang.t('streams.article')}</p>
<p>{lang.t('dashboard.flags')}</p>
<p>{t('streams.article')}</p>
<p>{t('dashboard.flags')}</p>
</div>
<div className={styles.widgetTable}>
{
@@ -35,7 +33,7 @@ const FlagWidget = ({assets}) => {
</div>
);
})
: <div className={styles.rowLinkify}>{lang.t('dashboard.no_flags')}</div>
: <div className={styles.rowLinkify}>{t('dashboard.no_flags')}</div>
}
</div>
</div>
@@ -1,10 +1,7 @@
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import styles from './Widget.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const LikeWidget = ({assets}) => {
@@ -12,8 +9,8 @@ const LikeWidget = ({assets}) => {
<div className={styles.widget}>
<h2 className={styles.heading}>Articles with the most likes</h2>
<div className={styles.widgetHead}>
<p>{lang.t('streams.article')}</p>
<p>{lang.t('modqueue.likes')}</p>
<p>{t('streams.article')}</p>
<p>{t('modqueue.likes')}</p>
</div>
<div className={styles.widgetTable}>
{
@@ -31,7 +28,7 @@ const LikeWidget = ({assets}) => {
</div>
);
})
: <div className={styles.rowLinkify}>{lang.t('dashboard.no_likes')}</div>
: <div className={styles.rowLinkify}>{t('dashboard.no_likes')}</div>
}
</div>
</div>
@@ -1,11 +1,9 @@
import React from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations';
import ModerationQueue from 'coral-admin/src/containers/ModerationQueue/ModerationQueue';
import styles from './Widget.css';
import BanUserDialog from 'coral-admin/src/components/BanUserDialog';
const lang = new I18n(translations);
import t from 'coral-framework/services/i18n';
const MostLikedCommentsWidget = (props) => {
const {
@@ -21,7 +19,7 @@ const MostLikedCommentsWidget = (props) => {
return (
<div className={styles.widget}>
<h2 className={styles.heading}>{lang.t('most_liked_comments')}</h2>
<h2 className={styles.heading}>{t('most_liked_comments')}</h2>
<ModerationQueue
comments={comments}
suspectWords={settings.wordlist.suspect}
@@ -0,0 +1,65 @@
import React from 'react';
import {connect} from 'react-redux';
import Dashboard from '../components/Dashboard';
import {compose, gql} from 'react-apollo';
import withQuery from 'coral-framework/hocs/withQuery';
import {Spinner} from 'coral-ui';
class DashboardContainer extends React.Component {
reloadData = () => {
this.props.data.refetch();
}
render () {
if (this.props.data.loading) {
return <Spinner />;
}
return <Dashboard {...this.props} reloadData={this.reloadData} />;
}
}
export const witDashboardQuery = withQuery(gql`
query CoralAdmin_Dashboard($from: Date!, $to: Date!) {
assetsByFlag: assetMetrics(from: $from, to: $to, sort: FLAG) {
...CoralAdmin_Metrics
}
assetsByActivity: assetMetrics(from: $from, to: $to, sort: ACTIVITY) {
...CoralAdmin_Metrics
}
}
fragment CoralAdmin_Metrics on Asset {
id
title
url
author
created_at
commentCount
action_summaries {
actionCount
actionableItemCount
}
}
`, {
options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => {
return {
variables: {
from: dashboardWindowStart,
to: dashboardWindowEnd
}
};
}
});
const mapStateToProps = (state) => {
return {
settings: state.settings.toJS(),
moderation: state.moderation.toJS()
};
};
export default compose(
connect(mapStateToProps),
witDashboardQuery,
)(DashboardContainer);
@@ -0,0 +1 @@
export {default} from './containers/Dashboard.js';
@@ -0,0 +1,79 @@
import React, {Component} from 'react';
import styles from './style.css';
import {Wizard, WizardNav} from 'coral-ui';
import Layout from 'coral-admin/src/components/ui/Layout';
import InitialStep from './Steps/InitialStep';
import AddOrganizationName from './Steps/AddOrganizationName';
import CreateYourAccount from './Steps/CreateYourAccount';
import PermittedDomainsStep from './Steps/PermittedDomainsStep';
import FinalStep from './Steps/FinalStep';
export default class Install extends Component {
handleDomainsChange = (value) => {
this.props.updatePermittedDomains(value);
};
handleSettingsChange = ({currentTarget: {name, value}}) => {
this.props.updateSettingsFormData(name, value);
};
handleUserChange = ({currentTarget: {name, value}}) => {
this.props.updateUserFormData(name, value);
};
handleSettingsSubmit = (e) => {
e.preventDefault();
this.props.submitSettings();
};
handleUserSubmit = (e) => {
e.preventDefault();
this.props.submitUser();
}
render() {
const {install} = this.props;
return (
<Layout restricted={true}>
<div className={styles.Install}>
{
!install.alreadyInstalled ? (
<div>
<h2>Welcome to the Coral Project</h2>
{ install.step !== 0 ? <WizardNav items={install.navItems} currentStep={install.step} icon='check'/> : null }
<Wizard
currentStep={install.step}
nextStep={this.props.nextStep}
previousStep={this.props.previousStep}
goToStep={this.props.goToStep}
>
<InitialStep/>
<AddOrganizationName
install={install}
handleSettingsChange={this.handleSettingsChange}
handleSettingsSubmit={this.handleSettingsSubmit}
/>
<CreateYourAccount
install={install}
handleUserChange={this.handleUserChange}
handleUserSubmit={this.handleUserSubmit}
/>
<PermittedDomainsStep
install={install}
handleDomainsChange={this.handleDomainsChange}
finishInstall={this.props.finishInstall}
/>
<FinalStep/>
</Wizard>
</div>
) : (
<div>Talk is already installed</div>
)
}
</div>
</Layout>
);
}
}
@@ -2,26 +2,24 @@ import React from 'react';
import styles from './style.css';
import {TextField, Button} from 'coral-ui';
const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
import t from 'coral-framework/services/i18n';
const AddOrganizationName = (props) => {
const {handleSettingsChange, handleSettingsSubmit, install} = props;
return (
<div className={styles.step}>
<p>{lang.t('ADD_ORGANIZATION.DESCRIPTION')}</p>
<p>{t('install.add_organization.description')}</p>
<div className={styles.form}>
<form onSubmit={handleSettingsSubmit}>
<TextField
className={styles.TextField}
id="organizationName"
type="text"
label={lang.t('ADD_ORGANIZATION.LABEL')}
label={t('install.add_organization.label')}
onChange={handleSettingsChange}
showErrors={install.showErrors}
errorMsg={install.errors.organizationName} />
<Button type="submit" cStyle='black' full>{lang.t('ADD_ORGANIZATION.SAVE')}</Button>
<Button type="submit" cStyle='black' full>{t('install.add_organization.save')}</Button>
</form>
</div>
</div>
@@ -2,9 +2,7 @@ import React from 'react';
import styles from './style.css';
import {TextField, Button, Spinner} from 'coral-ui';
const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
import t from 'coral-framework/services/i18n';
const InitialStep = (props) => {
const {handleUserChange, handleUserSubmit, install} = props;
@@ -16,7 +14,7 @@ const InitialStep = (props) => {
className={styles.textField}
id="email"
type="email"
label={lang.t('CREATE.EMAIL')}
label={t('install.create.email')}
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.email}
@@ -27,7 +25,7 @@ const InitialStep = (props) => {
className={styles.textField}
id="username"
type="text"
label={lang.t('CREATE.USERNAME')}
label={t('install.create.username')}
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.username}
@@ -37,7 +35,7 @@ const InitialStep = (props) => {
className={styles.textField}
id="password"
type="password"
label={lang.t('CREATE.PASSWORD')}
label={t('install.create.password')}
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.password}
@@ -47,15 +45,15 @@ const InitialStep = (props) => {
className={styles.textField}
id="confirmPassword"
type="password"
label={lang.t('CREATE.CONFIRM_PASSWORD')}
label={t('install.create.confirm_password')}
onChange={handleUserChange}
showErrors={install.showErrors}
errorMsg={install.errors.confirmPassword}
errorMsg={install.errors.confirm_password}
/>
{
!props.install.isLoading ?
<Button cStyle='black' type="submit" full>{lang.t('CREATE.SAVE')}</Button>
<Button cStyle='black' type="submit" full>{t('install.create.save')}</Button>
:
<Spinner />
}
@@ -3,16 +3,14 @@ import styles from './style.css';
import {Button} from 'coral-ui';
import {Link} from 'react-router';
const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
import t from 'coral-framework/services/i18n';
const InitialStep = () => {
return (
<div className={`${styles.step} ${styles.finalStep}`}>
<p>{lang.t('FINAL.DESCRIPTION')}</p>
<Button raised><Link to='/admin'>{lang.t('FINAL.LAUNCH')}</Link></Button>
<Button cStyle='black' raised><a href="http://coralproject.net">{lang.t('FINAL.CLOSE')}</a></Button>
<p>{t('install.final.description')}</p>
<Button raised><Link to='/admin'>{t('install.final.launch')}</Link></Button>
<Button cStyle='black' raised><a href="http://coralproject.net">{t('install.final.close')}</a></Button>
</div>
);
};
@@ -0,0 +1,17 @@
import React from 'react';
import styles from './style.css';
import {Button} from 'coral-ui';
import t from 'coral-framework/services/i18n';
const InitialStep = (props) => {
const {nextStep} = props;
return (
<div className={styles.step}>
<p>{t('install.initial.description')}</p>
<Button cStyle='green' onClick={nextStep} raised>{t('install.initial.submit')}</Button>
</div>
);
};
export default InitialStep;
@@ -3,18 +3,16 @@ import styles from './style.css';
import {Button, Card} from 'coral-ui';
import TagsInput from 'react-tagsinput';
const lang = new I18n(translations);
import translations from '../../translations.json';
import I18n from 'coral-framework/modules/i18n/i18n';
import t from 'coral-framework/services/i18n';
const PermittedDomainsStep = (props) => {
const {finishInstall, install, handleDomainsChange} = props;
const domains = install.data.settings.domains.whitelist;
return (
<div className={styles.step}>
<h3>{lang.t('PERMITTED_DOMAINS.TITLE')}</h3>
<h3>{t('install.permitted_domains.title')}</h3>
<Card className={styles.card}>
<p>{lang.t('PERMITTED_DOMAINS.DESCRIPTION')}</p>
<p>{t('install.permitted_domains.description')}</p>
<TagsInput
value={domains}
inputProps={{placeholder: 'URL'}}
@@ -23,7 +21,7 @@ const PermittedDomainsStep = (props) => {
onChange={(tags) => handleDomainsChange(tags)}
/>
</Card>
<Button cStyle='green' onClick={finishInstall} raised>{lang.t('PERMITTED_DOMAINS.SUBMIT')}</Button>
<Button cStyle='green' onClick={finishInstall} raised>{t('install.permitted_domains.submit')}</Button>
</div>
);
};
@@ -0,0 +1,57 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import Install from '../components/Install';
import {
goToStep,
nextStep,
submitUser,
checkInstall,
previousStep,
finishInstall,
submitSettings,
updateUserFormData,
updateSettingsFormData,
updatePermittedDomains
} from '../../../actions/install';
class InstallContainer extends Component {
componentDidMount() {
const {checkInstall} = this.props;
checkInstall(() => {
this.context.router.push('/admin');
});
}
render() {
return <Install {...this.props}/>;
}
}
InstallContainer.contextTypes = {
router: React.PropTypes.object
};
const mapStateToProps = (state) => ({
install: state.install.toJS()
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
goToStep,
nextStep,
submitUser,
checkInstall,
previousStep,
finishInstall,
submitSettings,
updateUserFormData,
updateSettingsFormData,
updatePermittedDomains
}, dispatch);
export default compose(
connect(mapStateToProps, mapDispatchToProps),
)(InstallContainer);
@@ -0,0 +1 @@
export {default} from './containers/Install.js';

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