From 91a25fc3c12e3f95708d34d588d62c675a30193a Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 5 Oct 2017 08:00:08 -0300 Subject: [PATCH 01/57] max-width --- .../coral-admin/src/components/ui/Header.css | 8 +- .../coral-admin/src/components/ui/Header.js | 170 +++++++++--------- .../coral-admin/src/components/ui/Layout.css | 4 +- 3 files changed, 93 insertions(+), 89 deletions(-) diff --git a/client/coral-admin/src/components/ui/Header.css b/client/coral-admin/src/components/ui/Header.css index f85a7cdf9..e6791b63b 100644 --- a/client/coral-admin/src/components/ui/Header.css +++ b/client/coral-admin/src/components/ui/Header.css @@ -15,19 +15,23 @@ } } +.headerWrapper { + background-color: #696969; +} + .header { background-color: transparent; box-shadow: none; min-height: 58px; display: block; + max-width: 1280px; + margin: 0 auto; } .header > div { background-color: #696969; position: relative; padding: 0; - min-width: 1280px; - margin: 0 auto; box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); height: 58px; } diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index 30c5fa7e6..62f6a7fd1 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -15,93 +15,95 @@ const CoralHeader = ({ root }) => { return ( -
- -
- { - auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ? - - - {t('configure.dashboard')} - - { - can(auth.user, 'MODERATE_COMMENTS') && ( - - {t('configure.moderate')} - {(root.premodCount !== 0 || root.reportedCount !== 0) && } - - ) - } - - {t('configure.stories')} - +
+
+ +
+ { + auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ? + + + {t('configure.dashboard')} + + { + can(auth.user, 'MODERATE_COMMENTS') && ( + + {t('configure.moderate')} + {(root.premodCount !== 0 || root.reportedCount !== 0) && } + + ) + } + + {t('configure.stories')} + - - {t('configure.community')} - {root.flaggedUsernamesCount !== 0 && } - + + {t('configure.community')} + {root.flaggedUsernamesCount !== 0 && } + - { - can(auth.user, 'UPDATE_CONFIG') && ( - - {t('configure.configure')} - - ) - } - - : - null - } -
- + { + can(auth.user, 'UPDATE_CONFIG') && ( + + {t('configure.configure')} + + ) + } + + : + null + } +
+ +
-
-
+
+ ); }; diff --git a/client/coral-admin/src/components/ui/Layout.css b/client/coral-admin/src/components/ui/Layout.css index a3cc7b7b6..3554c20e6 100644 --- a/client/coral-admin/src/components/ui/Layout.css +++ b/client/coral-admin/src/components/ui/Layout.css @@ -1,5 +1,3 @@ .layout { - max-width: 1280px; - margin: 0 auto; - background-color: #FAFAFA; + background-color: #FAFAFA; } From 886df1cdcac34a57d3136cbca8c0108d68943bfc Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 5 Oct 2017 08:21:17 -0300 Subject: [PATCH 02/57] Now every section is full width :D --- client/coral-admin/src/components/ui/Drawer.css | 13 +++++++++++++ client/coral-admin/src/components/ui/Drawer.js | 7 ++++--- client/coral-admin/src/components/ui/Layout.js | 1 + .../src/routes/Configure/components/Configure.css | 2 ++ .../src/routes/Dashboard/components/Dashboard.css | 2 ++ .../src/routes/Stories/components/Stories.css | 2 ++ 6 files changed, 24 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/components/ui/Drawer.css b/client/coral-admin/src/components/ui/Drawer.css index e69de29bb..861d4074b 100644 --- a/client/coral-admin/src/components/ui/Drawer.css +++ b/client/coral-admin/src/components/ui/Drawer.css @@ -0,0 +1,13 @@ +@custom-media --table-viewport (max-width: 1024px); + +:global { + .mdl-layout__drawer-button { + visibility: hidden; + } + + @media (--table-viewport) { + .mdl-layout__drawer-button { + visibility: visible; + } + } +} \ No newline at end of file diff --git a/client/coral-admin/src/components/ui/Drawer.js b/client/coral-admin/src/components/ui/Drawer.js index 2b728a33a..0385b1773 100644 --- a/client/coral-admin/src/components/ui/Drawer.js +++ b/client/coral-admin/src/components/ui/Drawer.js @@ -6,8 +6,8 @@ import styles from './Drawer.css'; import t from 'coral-framework/services/i18n'; import {can} from 'coral-framework/services/perms'; -const CoralDrawer = ({handleLogout, auth}) => ( - +const CoralDrawer = ({handleLogout, auth = {}}) => ( + { auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
@@ -57,7 +57,8 @@ const CoralDrawer = ({handleLogout, auth}) => ( CoralDrawer.propTypes = { handleLogout: PropTypes.func.isRequired, - restricted: PropTypes.bool // hide app elements from a logged out user + restricted: PropTypes.bool, // hide app elements from a logged out user + auth: PropTypes.object }; export default CoralDrawer; diff --git a/client/coral-admin/src/components/ui/Layout.js b/client/coral-admin/src/components/ui/Layout.js index 9ee9fda9f..00a09efb6 100644 --- a/client/coral-admin/src/components/ui/Layout.js +++ b/client/coral-admin/src/components/ui/Layout.js @@ -21,6 +21,7 @@ const Layout = ({
{children} diff --git a/client/coral-admin/src/routes/Configure/components/Configure.css b/client/coral-admin/src/routes/Configure/components/Configure.css index 0b7319132..7c9a26770 100644 --- a/client/coral-admin/src/routes/Configure/components/Configure.css +++ b/client/coral-admin/src/routes/Configure/components/Configure.css @@ -4,6 +4,8 @@ .container { display: flex; + max-width: 1280px; + margin: 0 auto; h3 { color: black; diff --git a/client/coral-admin/src/routes/Dashboard/components/Dashboard.css b/client/coral-admin/src/routes/Dashboard/components/Dashboard.css index 586ff6c08..aee7e702b 100644 --- a/client/coral-admin/src/routes/Dashboard/components/Dashboard.css +++ b/client/coral-admin/src/routes/Dashboard/components/Dashboard.css @@ -4,6 +4,8 @@ .Dashboard { display: flex; + max-width: 1280px; + margin: 0 auto; } .heading { diff --git a/client/coral-admin/src/routes/Stories/components/Stories.css b/client/coral-admin/src/routes/Stories/components/Stories.css index d8a75a4ce..be6b6f921 100644 --- a/client/coral-admin/src/routes/Stories/components/Stories.css +++ b/client/coral-admin/src/routes/Stories/components/Stories.css @@ -1,6 +1,8 @@ .container { padding: 10px; display: flex; + max-width: 1280px; + margin: 0 auto; } .leftColumn { From a1978571e8b86303b9a1da0d7a912a9810e12cb7 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Thu, 5 Oct 2017 13:38:08 +0100 Subject: [PATCH 03/57] First part of Product Guide --- docs/_docs/product-guide-how-talk-works.md | 23 +++++ .../product-guide-plugins-and-recipes.md | 91 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 docs/_docs/product-guide-how-talk-works.md create mode 100644 docs/_docs/product-guide-plugins-and-recipes.md diff --git a/docs/_docs/product-guide-how-talk-works.md b/docs/_docs/product-guide-how-talk-works.md new file mode 100644 index 000000000..09dfbdb74 --- /dev/null +++ b/docs/_docs/product-guide-how-talk-works.md @@ -0,0 +1,23 @@ +--- +title: How Talk Works +permalink: /how-talk-works// +class: product-guide +--- + +Talk is an open-source commenting platform. It has two pieces. One is the embedded script, which allows newsrooms to have a unique comments section on each story/post/page they have on their site, and allows their readers to comment and discuss articles. The other is the Admin, which is where newsrooms moderate their comments and manage and configure Talk. + +### What is Talk Core? + +As we’re building Talk, our vision was always to have it be very modular, so there are features we have built and are opinionated about, but we allow newsrooms and their developers to customize Talk easily so that it fits their use cases and needs. + +Talk Core is the core application of Talk - this contains all of the standard commenting features that are necessary for a comment section, and ones that we believe are important to be universal. + +### What are Plugins? + +Plugins are additional functionality which are optional to use with Talk. You can turn these on or off, depending on your specific needs. Plugins are either part of our core plugins, which ship with Talk, or they are developed by 3rd parties and either used privately and internally, or are open sourced for use across the greater community. + + +### What are Recipes? + +Recipes are plugin templates that are created by the Talk team and 3rd party developers, in order to help contributors and newsrooms build plugins easily. + diff --git a/docs/_docs/product-guide-plugins-and-recipes.md b/docs/_docs/product-guide-plugins-and-recipes.md new file mode 100644 index 000000000..b907eb49f --- /dev/null +++ b/docs/_docs/product-guide-plugins-and-recipes.md @@ -0,0 +1,91 @@ +--- +title: Plugins & Recipes +permalink: /plugins-and-recipes/ +class: product-guide +--- + +### Default Plugins + +The default Talk plugins can be found in the `plugins.default.json` file [here](https://github.com/coralproject/talk/blob/master/plugins.default.json). + +Talk ships out of the box with these plugins: + +#### Auth `talk-plugin-auth` + +Enables generic registration via an email address, a username, a password, and a password confirmation. To sync Talk auth with your own auth systems, you can use this plugin as a template. + +#### Facebook Auth `talk-plugin-facebook-auth` + +Enables sign-in via Facebook. + +#### Comment Content `talk-plugin-comment-content` + +Pluginizes the text of a comment to support custom treatment of this text. By default, we linkify urls so they are clickable. + +#### Respect `talk-plugin-respect` + +Enables a Respect reaction button. Why a “respect” button, you ask? [Read more here](https://mediaengagement.org/research/engagement-buttons/). + +#### Ignore User `talk-plugin-ignore-user` + +Enables ability for users to ignore (or “mute”) other users. If a user is ignored, you will not see any of their comments. You can un-ignore a user via the My Profile tab. + +#### Permalinks `talk-plugin-permalink` + +#### Featured Comments `talk-plugin-featured-comments` + +Enables the ability for Moderators to feature and un-feature comments via the Stream and the Admin. Featured comments show in a first-place tab on the Stream if there are any featured comments on that story. + +#### Viewing Options `talk-plugin-viewing-options` + +##### Sorting Options `talk-plugin-sort-newest`, `talk-plugin-sort-oldest`, `talk-plugin-most-respected`, `talk-plugin-most-replied` + +##### Off-topic `talk-plugin-offtopic` + +#### Author Menu `talk-plugin-author-menu` + +##### Member Since `talk-plugin-member-since` + +#### In-Stream Moderation `talk-plugin-moderation-actions` + +Enables in-stream moderation so that Moderators can reject, approve, and feature comments, as well as ban users, directly from the comment stream. + +#### Moderation Flag Details `talk-plugin-flag-details` + +Enables other plugins to use the Flag Details area of comments in the Moderation Queues to display data. + + +### Additional Plugins + +#### Like `talk-plugin-like` + +Provides a `like` reaction button. Can also be added to the Viewing Options sorts by including `talk-plugin-most-liked`. + +#### Love `talk-plugin-love` + +Provides a `love` reaction button. Can also be added to the Viewing Options sorts by including `talk-plugin-most-loved`. + +#### Remember Sort `talk-plugin-remember-sort` + +Enables saving a user’s last sort selection as they browse other articles. + +#### Deep Reply Count `talk-plugin-deep-reply-count` + +Enables counting of comments to include replies. Requires dev work to enable this fully. + +### Recipes + +Recipes are available here: +https://github.com/coralproject/talk-recipes + +#### Avatars `recipe-avatar` + +Provides support for commenter avatars. + +#### Subscriber Badge `recipe-subscriber` + +Provides support for badges for `subscribers`. + +#### Author Name `recipe-author-name` + +Enables the ability to hover over a commenter’s name and add plugin functionality there. The Member Since plugin is an example of this. From 71de5a33a95f6192b440f7c030b20c5470313043 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Thu, 5 Oct 2017 13:40:53 +0100 Subject: [PATCH 04/57] Update nav --- docs/_data/nav.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/_data/nav.yaml b/docs/_data/nav.yaml index 51b44f025..2a6f5b5e1 100644 --- a/docs/_data/nav.yaml +++ b/docs/_data/nav.yaml @@ -16,4 +16,10 @@ items: - title: Plugins children: - title: Plugins Overview - url: /plugins/ \ No newline at end of file + url: /plugins/ + - title: Product Guide + children: + - title: How Talk Works + url: /how-talk-works/ + - title: Plugin and Recipe List + url: /plugins-and-recipes/ From 6ffab51bcf0ceeb46c6b217971c09e8899c2aa4a Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Thu, 5 Oct 2017 13:41:54 +0100 Subject: [PATCH 05/57] Cleanup titles --- docs/_docs/product-guide-plugins-and-recipes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_docs/product-guide-plugins-and-recipes.md b/docs/_docs/product-guide-plugins-and-recipes.md index b907eb49f..aed661adb 100644 --- a/docs/_docs/product-guide-plugins-and-recipes.md +++ b/docs/_docs/product-guide-plugins-and-recipes.md @@ -1,5 +1,5 @@ --- -title: Plugins & Recipes +title: Plugin and Recipe List permalink: /plugins-and-recipes/ class: product-guide --- From 9c00050f6a277dd067b00e9d173c418389e0d2c3 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 5 Oct 2017 21:05:05 +0700 Subject: [PATCH 06/57] Load settings from graph --- client/coral-admin/src/actions/configure.js | 5 + client/coral-admin/src/actions/settings.js | 58 --------- .../coral-admin/src/components/UserDetail.js | 6 - .../src/components/UserDetailComment.js | 21 ++-- client/coral-admin/src/constants/configure.js | 3 + .../coral-admin/src/containers/UserDetail.js | 2 - .../src/containers/UserDetailComment.js | 7 +- client/coral-admin/src/reducers/configure.js | 35 ++++++ client/coral-admin/src/reducers/dashboard.js | 15 +++ client/coral-admin/src/reducers/index.js | 6 +- client/coral-admin/src/reducers/settings.js | 92 -------------- .../routes/Configure/components/Configure.js | 75 ++--------- .../components/ModerationSettings.js | 1 + .../Configure/components/StreamSettings.js | 14 +-- .../Configure/components/TechSettings.js | 1 + .../routes/Configure/containers/Configure.js | 116 +++++++++++++++--- .../routes/Dashboard/containers/Dashboard.js | 10 +- .../routes/Moderation/components/Comment.js | 9 +- .../Moderation/components/Moderation.js | 7 +- .../Moderation/components/ModerationQueue.js | 6 - .../routes/Moderation/containers/Comment.js | 7 +- .../Moderation/containers/Moderation.js | 4 - 22 files changed, 218 insertions(+), 282 deletions(-) create mode 100644 client/coral-admin/src/actions/configure.js delete mode 100644 client/coral-admin/src/actions/settings.js create mode 100644 client/coral-admin/src/constants/configure.js create mode 100644 client/coral-admin/src/reducers/configure.js create mode 100644 client/coral-admin/src/reducers/dashboard.js delete mode 100644 client/coral-admin/src/reducers/settings.js diff --git a/client/coral-admin/src/actions/configure.js b/client/coral-admin/src/actions/configure.js new file mode 100644 index 000000000..cdb2a3d1b --- /dev/null +++ b/client/coral-admin/src/actions/configure.js @@ -0,0 +1,5 @@ +import * as actions from 'constants/configure'; + +export const updatePending = ({updater, errorUpdater}) => { + return {type: actions.UPDATE_PENDING, updater, errorUpdater}; +}; diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js deleted file mode 100644 index 35f2013c9..000000000 --- a/client/coral-admin/src/actions/settings.js +++ /dev/null @@ -1,58 +0,0 @@ -import t from 'coral-framework/services/i18n'; - -export const SETTINGS_LOADING = 'SETTINGS_LOADING'; -export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED'; -export const SETTINGS_FETCH_ERROR = 'SETTINGS_FETCH_ERROR'; - -export const SETTINGS_UPDATED = 'SETTINGS_UPDATED'; - -export const SAVE_SETTINGS_LOADING = 'SAVE_SETTINGS_LOADING'; -export const SAVE_SETTINGS_SUCCESS = 'SAVE_SETTINGS_SUCCESS'; -export const SAVE_SETTINGS_FAILED = 'SAVE_SETTINGS_FAILED'; - -export const WORDLIST_UPDATED = 'WORDLIST_UPDATED'; -export const DOMAINLIST_UPDATED = 'DOMAINLIST_UPDATED'; - -export const fetchSettings = () => (dispatch, _, {rest}) => { - dispatch({type: SETTINGS_LOADING}); - rest('/settings') - .then((settings) => { - dispatch({type: SETTINGS_RECEIVED, settings}); - }) - .catch((error) => { - console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch({type: SETTINGS_FETCH_ERROR, error: errorMessage}); - }); -}; - -// for updating top-level settings -export const updateSettings = (settings) => { - return {type: SETTINGS_UPDATED, settings}; -}; - -// this is a nested property, so it needs a special action. -export const updateWordlist = (listName, list) => { - return {type: WORDLIST_UPDATED, listName, list}; -}; - -export const updateDomainlist = (listName, list) => { - return {type: DOMAINLIST_UPDATED, listName, list}; -}; - -export const saveSettingsToServer = () => (dispatch, getState, {rest}) => { - let settings = getState().settings; - if (settings.charCount) { - settings.charCount = parseInt(settings.charCount); - } - dispatch({type: SAVE_SETTINGS_LOADING}); - rest('/settings', {method: 'PUT', body: settings}) - .then(() => { - dispatch({type: SAVE_SETTINGS_SUCCESS, settings}); - }) - .catch((error) => { - console.error(error); - const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString(); - dispatch({type: SAVE_SETTINGS_FAILED, error: errorMessage}); - }); -}; diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index a5955af45..275503085 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -17,8 +17,6 @@ export default class UserDetail extends React.Component { userId: PropTypes.string.isRequired, hideUserDetail: PropTypes.func.isRequired, root: PropTypes.object.isRequired, - bannedWords: PropTypes.array.isRequired, - suspectWords: PropTypes.array.isRequired, acceptComment: PropTypes.func.isRequired, rejectComment: PropTypes.func.isRequired, changeStatus: PropTypes.func.isRequired, @@ -79,8 +77,6 @@ export default class UserDetail extends React.Component { }, activeTab, selectedCommentIds, - bannedWords, - suspectWords, toggleSelect, bulkAccept, bulkReject, @@ -184,8 +180,6 @@ export default class UserDetail extends React.Component { root={root} data={data} comment={comment} - suspectWords={suspectWords} - bannedWords={bannedWords} acceptComment={this.acceptThenReload} rejectComment={this.rejectThenReload} selected={selected} diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index f8d81ce8b..e3e89d00a 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -30,12 +30,11 @@ class UserDetailComment extends React.Component { render() { const { comment, - suspectWords, - bannedWords, selected, toggleSelect, className, data, + root: {settings: {wordlist: {banned, suspect}}}, } = this.props; return ( @@ -72,8 +71,8 @@ class UserDetailComment extends React.Component {
{' '} @@ -123,9 +122,15 @@ UserDetailComment.propTypes = { acceptComment: PropTypes.func.isRequired, rejectComment: PropTypes.func.isRequired, className: PropTypes.string, - suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired, - bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired, toggleSelect: PropTypes.func, + root: PropTypes.shape({ + settings: PropTypes.shape({ + wordlist: PropTypes.shape({ + suspect: PropTypes.arrayOf(PropTypes.string).isRequired, + banned: PropTypes.arrayOf(PropTypes.string).isRequired, + }), + }), + }), comment: PropTypes.shape({ id: PropTypes.string.isRequired, status: PropTypes.string.isRequired, @@ -136,8 +141,8 @@ UserDetailComment.propTypes = { title: PropTypes.string, url: PropTypes.string, id: PropTypes.string - }) - }) + }), + }), }; export default UserDetailComment; diff --git a/client/coral-admin/src/constants/configure.js b/client/coral-admin/src/constants/configure.js new file mode 100644 index 000000000..00985e3ff --- /dev/null +++ b/client/coral-admin/src/constants/configure.js @@ -0,0 +1,3 @@ +const prefix = 'CORAL_ADMIN'; + +export const UPDATE_PENDING = `${prefix}_UPDATE_PENDING`; diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 2ae842b1d..ffc661150 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -179,8 +179,6 @@ const mapStateToProps = (state) => ({ selectedCommentIds: state.userDetail.selectedCommentIds, statuses: state.userDetail.statuses, activeTab: state.userDetail.activeTab, - bannedWords: state.settings.wordlist.banned, - suspectWords: state.settings.wordlist.suspect, }); const mapDispatchToProps = (dispatch) => ({ diff --git a/client/coral-admin/src/containers/UserDetailComment.js b/client/coral-admin/src/containers/UserDetailComment.js index 9eef5934f..fe95f4ae4 100644 --- a/client/coral-admin/src/containers/UserDetailComment.js +++ b/client/coral-admin/src/containers/UserDetailComment.js @@ -8,7 +8,12 @@ import CommentDetails from './CommentDetails'; export default withFragments({ root: gql` fragment CoralAdmin_UserDetailComment_root on RootQuery { - __typename + settings { + wordlist { + banned + suspect + } + } ...${getDefinitionName(CommentLabels.fragments.root)} ...${getDefinitionName(CommentDetails.fragments.root)} } diff --git a/client/coral-admin/src/reducers/configure.js b/client/coral-admin/src/reducers/configure.js new file mode 100644 index 000000000..116c8663f --- /dev/null +++ b/client/coral-admin/src/reducers/configure.js @@ -0,0 +1,35 @@ +import * as actions from '../constants/configure'; +import isEmpty from 'lodash/isEmpty'; +import update from 'immutability-helper'; + +const initialState = { + canSave: false, + pending: {}, + errors: {}, +}; + +export default function configure(state = initialState, action) { + switch (action.type) { + case actions.UPDATE_PENDING: { + let next = state; + if (action.updater) { + next = update(next, { + pending: action.updater, + }); + } + if (action.errorUpdater) { + next = update(next, { + errors: action.errorUpdater, + }); + } + const noErrors = Object.keys(next.errors).reduce((res, error) => res && !next.errors[error], true); + const canSave = !isEmpty(next.pending) && noErrors; + next = update(next, { + canSave: {$set: canSave}, + }); + + return next; + } + } + return state; +} diff --git a/client/coral-admin/src/reducers/dashboard.js b/client/coral-admin/src/reducers/dashboard.js new file mode 100644 index 000000000..3b3414d3a --- /dev/null +++ b/client/coral-admin/src/reducers/dashboard.js @@ -0,0 +1,15 @@ +// this is initialized here because +// currently you have to reload the dashboard to get new stats +// cleaner updates are planned in the future. +const DASHBOARD_WINDOW_MINUTES = 5; +let then = new Date(); +then.setMinutes(then.getMinutes() - DASHBOARD_WINDOW_MINUTES); + +const initialState = { + windowStart: then.toISOString(), + windowEnd: new Date().toISOString(), +}; + +export default function dashboard (state = initialState, _action) { + return state; +} diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index b329675a4..372fedbae 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,6 +1,7 @@ import auth from './auth'; import assets from './assets'; -import settings from './settings'; +import dashboard from './dashboard'; +import configure from './configure'; import community from './community'; import moderation from './moderation'; import install from './install'; @@ -12,10 +13,11 @@ import userDetail from './userDetail'; export default { auth, banUserDialog, + dashboard, + configure, suspendUserDialog, userDetail, assets, - settings, community, moderation, install, diff --git a/client/coral-admin/src/reducers/settings.js b/client/coral-admin/src/reducers/settings.js deleted file mode 100644 index 336047e5b..000000000 --- a/client/coral-admin/src/reducers/settings.js +++ /dev/null @@ -1,92 +0,0 @@ -import * as actions from '../actions/settings'; -import update from 'immutability-helper'; - -// this is initialized here because -// currently you have to reload the dashboard to get new stats -// cleaner updates are planned in the future. -// TODO: if there are more than two fields for the dashboard being created here, -// please create a new reducer specifically for the Dashboard. -const DASHBOARD_WINDOW_MINUTES = 5; -let then = new Date(); -then.setMinutes(then.getMinutes() - DASHBOARD_WINDOW_MINUTES); - -const initialState = { - wordlist: { - banned: [], - suspect: [] - }, - dashboardWindowStart: then.toISOString(), - dashboardWindowEnd: new Date().toISOString(), - domains: { - whitelist: [] - }, - saveSettingsError: null, - fetchSettingsError: null, - fetchingSettings: false -}; - -export default function settings (state = initialState, action) { - switch (action.type) { - case actions.SETTINGS_LOADING: - return { - ...state, - fetchingSettings: true, - fetchSettingsError: null, - }; - case actions.SETTINGS_RECEIVED: - return { - ...state, - fetchingSettings: false, - fetchSettingsError: null, - ...action.settings - }; - case actions.SETTINGS_FETCH_ERROR: - return { - ...state, - fetchingSettings: false, - fetchSettingsError: action.error, - }; - case actions.SETTINGS_UPDATED: - return { - ...state, - fetchingSettings: false, - fetchSettingsError: null, - ...action.settings - }; - case actions.SAVE_SETTINGS_LOADING: - return { - ...state, - fetchingSettings: true, - saveSettingsError: null, - }; - case actions.SAVE_SETTINGS_SUCCESS: - return { - ...state, - fetchingSettings: false, - fetchSettingsError: null, - ...action.settings - }; - case actions.SAVE_SETTINGS_FAILED: - return { - ...state, - fetchingSettings: false, - fetchSettingsError: action.error, - }; - case actions.WORDLIST_UPDATED: - return update(state, { - wordlist: { - [action.listName]: { - $set: action.list - } - } - }); - case actions.DOMAINLIST_UPDATED: - return update(state, { - domains: { - [action.listName]: {$set: action.list}, - } - }); - default: - return state; - } -} diff --git a/client/coral-admin/src/routes/Configure/components/Configure.js b/client/coral-admin/src/routes/Configure/components/Configure.js index 15ff69b69..f60951de3 100644 --- a/client/coral-admin/src/routes/Configure/components/Configure.js +++ b/client/coral-admin/src/routes/Configure/components/Configure.js @@ -1,6 +1,6 @@ import React, {Component} from 'react'; -import {Button, List, Item, Card, Spinner} from 'coral-ui'; +import {Button, List, Item} from 'coral-ui'; import styles from './Configure.css'; import StreamSettings from './StreamSettings'; import ModerationSettings from './ModerationSettings'; @@ -12,104 +12,57 @@ export default class Configure extends Component { state = { activeSection: 'stream', - changed: false, - errors: {} }; saveSettings = () => { this.props.saveSettingsToServer(); - this.setState({changed: false}); } changeSection = (activeSection) => { this.setState({activeSection}); } - onChangeWordlist = (listName, list) => { - this.setState({changed: true}); - this.props.updateWordlist(listName, list); - } - - onChangeDomainlist = (listName, list) => { - this.setState({changed: true}); - this.props.updateDomainlist(listName, list); - } - - onSettingUpdate = (setting) => { - this.setState({changed: true}); - this.props.updateSettings(setting); - } - - // Sets an arbitrary error string and a boolean state. - // This allows the system to track multiple errors. - onSettingError = (error, state) => { - this.setState((prevState) => { - prevState.errors[error] = state; - return prevState; - }); - } - getSection (section) { - const pageTitle = this.getPageTitle(section); let sectionComponent; switch(section){ case 'stream': sectionComponent = ; + updateSettings={this.props.updateSettings} + errors={this.props.errors} + />; break; case 'moderation': sectionComponent = ; + updateSettings={this.props.updateSettings} + />; break; case 'tech': sectionComponent = ; - } - - if (this.props.settings.fetchingSettings) { - return Loading settings...; + updateSettings={this.props.updateSettings} + />; } return (
-

{pageTitle}

{sectionComponent}
); } - getPageTitle (section) { - switch(section) { - case 'stream': - return t('configure.stream_settings'); - case 'moderation': - return t('configure.moderation_settings'); - case 'tech': - return t('configure.tech_settings'); - default: - return ''; - } - } - render () { const {activeSection} = this.state; const section = this.getSection(activeSection); - const {auth: {user}} = this.props; + const {auth: {user}, canSave} = this.props; if (!can(user, 'UPDATE_CONFIG')) { return

You must be an administrator to access config settings. Please find the nearest Admin and ask them to level you up!

; } - const showSave = Object.keys(this.state.errors).reduce( - (bool, error) => this.state.errors[error] ? false : bool, this.state.changed); - return (
@@ -126,7 +79,7 @@ export default class Configure extends Component {
{ - showSave ? + canSave ?
); diff --git a/client/coral-admin/src/routes/Configure/components/ModerationSettings.js b/client/coral-admin/src/routes/Configure/components/ModerationSettings.js index 92a5413e2..b3f51d843 100644 --- a/client/coral-admin/src/routes/Configure/components/ModerationSettings.js +++ b/client/coral-admin/src/routes/Configure/components/ModerationSettings.js @@ -28,6 +28,7 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => { return (
+

{t('configure.moderation_settings')}

() => { updateSettings({charCountEnable}); }; -const updateCharCount = (updateSettings, settingsError) => (event) => { +const updateCharCount = (updateSettings) => (event) => { + let error = null; const charCount = event.target.value; if (charCount.match(/[^0-9]/) || charCount.length === 0) { - settingsError('charCount', true); - } else { - settingsError('charCount', false); + error = true; } - updateSettings({charCount: charCount}); + updateSettings({charCount: charCount}, {setError: {'charCount': error}}); }; const updateInfoBoxEnable = (updateSettings, infoBox) => () => { @@ -68,7 +67,7 @@ const updateEditCommentWindowLength = (updateSettings) => (e) => { updateSettings({editCommentWindowLength: milliseconds || value}); }; -const StreamSettings = ({updateSettings, settingsError, settings, errors}) => { +const StreamSettings = ({updateSettings, settings, errors}) => { // just putting this here for shorthand below const on = styles.enabledSetting; @@ -76,6 +75,7 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => { return (
+

{t('configure.stream_settings')}

{ diff --git a/client/coral-admin/src/routes/Configure/components/TechSettings.js b/client/coral-admin/src/routes/Configure/components/TechSettings.js index 4f1202a67..2fc7afb7f 100644 --- a/client/coral-admin/src/routes/Configure/components/TechSettings.js +++ b/client/coral-admin/src/routes/Configure/components/TechSettings.js @@ -14,6 +14,7 @@ const updateCustomCssUrl = (updateSettings) => (event) => { const TechSettings = ({settings, onChangeDomainlist, updateSettings}) => { return (
+

{t('configure.tech_settings')}

diff --git a/client/coral-admin/src/routes/Configure/containers/Configure.js b/client/coral-admin/src/routes/Configure/containers/Configure.js index aff1e57b2..f6edbe2ae 100644 --- a/client/coral-admin/src/routes/Configure/containers/Configure.js +++ b/client/coral-admin/src/routes/Configure/containers/Configure.js @@ -1,42 +1,124 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; -import {compose} from 'react-apollo'; +import {compose, gql} from 'react-apollo'; +import withQuery from 'coral-framework/hocs/withQuery'; +import {Spinner} from 'coral-ui'; +import {notify} from 'coral-framework/actions/notification'; +import merge from 'lodash/merge'; import { - fetchSettings, - updateSettings, - saveSettingsToServer, - updateWordlist, - updateDomainlist -} from '../../../actions/settings'; + updatePending, +} from '../../../actions/configure'; import Configure from '../components/Configure'; class ConfigureContainer extends Component { - componentWillMount = () => { - this.props.fetchSettings(); - } + + updateWordlist = (listName, list) => { + this.props.updatePending({updater: { + wordlist: {$apply: (wordlist) => { + const changeSet = {[listName]: list}; + if (!wordlist) { + return changeSet; + } + return { + ...wordlist, + ...changeSet, + }; + }}, + }}); + }; + + updateDomainlist = (listName, list) => { + this.props.updatePending({updater: { + domains: {$apply: (domains) => { + const changeSet = {[listName]: list}; + if (!domains) { + return changeSet; + } + return { + ...domains, + ...changeSet, + }; + }}, + }}); + }; + + updateSettings = (settings, {setError = {}} = {}) => { + this.props.updatePending({updater: {$merge: settings}, errorUpdater: {$merge: setError}}); + }; render () { - return ; + if(this.props.data.loading) { + return ; + } + + const merged = merge({}, this.props.root.settings, this.props.pending); + + return ; } } +const withConfigureQuery = withQuery(gql` + query CoralEmbedStream_Embed { + settings { + moderation + requireEmailConfirmation + infoBoxEnable + infoBoxContent + questionBoxEnable + questionBoxContent + premodLinksEnable + questionBoxIcon + autoCloseStream + customCssUrl + closedTimeout + closedMessage + editCommentWindowLength + charCountEnable + charCount + organizationName + wordlist { + suspect + banned + } + domains { + whitelist + } + } + } + `, { + options: () => ({ + variables: {}, + }), + }); + const mapStateToProps = (state) => ({ auth: state.auth, - settings: state.settings + pending: state.configure.pending, + canSave: state.configure.canSave, + errors: state.configure.errors, }); const mapDispatchToProps = (dispatch) => bindActionCreators({ - fetchSettings, - updateSettings, - saveSettingsToServer, - updateWordlist, - updateDomainlist + notify, + updatePending, }, dispatch); export default compose( + withConfigureQuery, connect(mapStateToProps, mapDispatchToProps), )(ConfigureContainer); diff --git a/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js b/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js index 2bb192f2e..e2e9ef28c 100644 --- a/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js +++ b/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js @@ -42,11 +42,11 @@ export const witDashboardQuery = withQuery(gql` } } `, { - options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => { + options: ({windowStart, windowEnd}) => { return { variables: { - from: dashboardWindowStart, - to: dashboardWindowEnd + from: windowStart, + to: windowEnd, } }; } @@ -54,8 +54,8 @@ export const witDashboardQuery = withQuery(gql` const mapStateToProps = (state) => { return { - settings: state.settings, - moderation: state.moderation + windowStart: state.dashboard.windowStart, + windowEnd: state.dashboard.windowEnd, }; }; diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index 42c9c2d13..aedc69739 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -58,12 +58,11 @@ class Comment extends React.Component { render() { const { comment, - suspectWords, - bannedWords, selected, className, data, root, + root: {settings}, currentUserId, currentAsset, } = this.props; @@ -130,8 +129,8 @@ class Comment extends React.Component {
{' '} @@ -188,8 +187,6 @@ Comment.propTypes = { acceptComment: PropTypes.func.isRequired, rejectComment: PropTypes.func.isRequired, className: PropTypes.string, - suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired, - bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired, currentAsset: PropTypes.object, showBanUserDialog: PropTypes.func.isRequired, showSuspendUserDialog: PropTypes.func.isRequired, diff --git a/client/coral-admin/src/routes/Moderation/components/Moderation.js b/client/coral-admin/src/routes/Moderation/components/Moderation.js index 668d013b0..222890cf5 100644 --- a/client/coral-admin/src/routes/Moderation/components/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/components/Moderation.js @@ -56,7 +56,7 @@ class Moderation extends Component { const comments = this.getComments(); const commentIdx = comments.findIndex((comment) => comment.id === selectedCommentId); const comment = comments[commentIdx]; - + if (accept) { comment.status !== 'ACCEPTED' && acceptComment({commentId: comment.id}); } else { @@ -196,7 +196,7 @@ class Moderation extends Component { } render () { - const {root, data, moderation, settings, viewUserDetail, activeTab, getModPath, queueConfig, handleCommentChange, ...props} = this.props; + const {root, data, moderation, viewUserDetail, activeTab, getModPath, queueConfig, handleCommentChange, ...props} = this.props; const {asset} = root; const assetId = asset && asset.id; @@ -235,8 +235,6 @@ class Moderation extends Component { activeTab={activeTab} singleView={moderation.singleView} selectedCommentId={this.state.selectedCommentId} - bannedWords={settings.wordlist.banned} - suspectWords={settings.wordlist.suspect} showBanUserDialog={props.showBanUserDialog} showSuspendUserDialog={props.showSuspendUserDialog} acceptComment={props.acceptComment} @@ -281,7 +279,6 @@ Moderation.propTypes = { storySearchChange: PropTypes.func.isRequired, moderation: PropTypes.object.isRequired, auth: PropTypes.object.isRequired, - settings: PropTypes.object.isRequired, queueConfig: PropTypes.object.isRequired, handleCommentChange: PropTypes.func.isRequired, setSortOrder: PropTypes.func.isRequired, diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js index 47ed56925..dc4a8c74c 100644 --- a/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js @@ -147,8 +147,6 @@ class ModerationQueue extends React.Component { key={comment.id} comment={comment} selected={true} - suspectWords={props.suspectWords} - bannedWords={props.bannedWords} viewUserDetail={viewUserDetail} showBanUserDialog={props.showBanUserDialog} showSuspendUserDialog={props.showSuspendUserDialog} @@ -193,8 +191,6 @@ class ModerationQueue extends React.Component { key={comment.id} comment={comment} selected={comment.id === selectedCommentId} - suspectWords={props.suspectWords} - bannedWords={props.bannedWords} viewUserDetail={viewUserDetail} showBanUserDialog={props.showBanUserDialog} showSuspendUserDialog={props.showSuspendUserDialog} @@ -218,8 +214,6 @@ class ModerationQueue extends React.Component { ModerationQueue.propTypes = { viewUserDetail: PropTypes.func.isRequired, - bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired, - suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired, currentAsset: PropTypes.object, showBanUserDialog: PropTypes.func.isRequired, showSuspendUserDialog: PropTypes.func.isRequired, diff --git a/client/coral-admin/src/routes/Moderation/containers/Comment.js b/client/coral-admin/src/routes/Moderation/containers/Comment.js index b060b9f9e..9748359ae 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Comment.js +++ b/client/coral-admin/src/routes/Moderation/containers/Comment.js @@ -15,7 +15,12 @@ const slots = [ export default withFragments({ root: gql` fragment CoralAdmin_ModerationComment_root on RootQuery { - __typename + settings { + wordlist { + banned + suspect + } + } ${getSlotFragmentSpreads(slots, 'root')} ...${getDefinitionName(CommentLabels.fragments.root)} ...${getDefinitionName(CommentDetails.fragments.root)} diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index 7aa839292..8787d77cd 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -13,7 +13,6 @@ import {isPremod, getModPath} from '../../../utils'; import {withSetCommentStatus} from 'coral-framework/graphql/mutations'; import {handleCommentChange} from '../graphql'; -import {fetchSettings} from 'actions/settings'; import {showBanUserDialog} from 'actions/banUserDialog'; import {showSuspendUserDialog} from 'actions/suspendUserDialog'; import {viewUserDetail} from '../../../actions/userDetail'; @@ -146,7 +145,6 @@ class ModerationContainer extends Component { componentWillMount() { this.props.clearState(); - this.props.fetchSettings(); this.subscribeToUpdates(); } @@ -384,7 +382,6 @@ const withModQueueQuery = withQuery(({queueConfig}) => gql` const mapStateToProps = (state) => ({ moderation: state.moderation, - settings: state.settings, auth: state.auth, }); @@ -392,7 +389,6 @@ const mapDispatchToProps = (dispatch) => ({ ...bindActionCreators({ toggleModal, singleView, - fetchSettings, showBanUserDialog, hideShortcutsNote, toggleStorySearch, From 5073ef6c1b8804aa61d378d3a85cfd481e2220e5 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 6 Oct 2017 18:27:10 +0700 Subject: [PATCH 07/57] Implement save settings --- client/coral-admin/src/actions/configure.js | 4 ++++ client/coral-admin/src/constants/configure.js | 1 + client/coral-admin/src/graphql/index.js | 10 ++++++++++ client/coral-admin/src/reducers/configure.js | 6 ++++++ .../routes/Configure/components/Configure.js | 8 ++------ .../routes/Configure/containers/Configure.js | 18 +++++++++++++++++- client/coral-framework/graphql/fragments.js | 1 + client/coral-framework/graphql/mutations.js | 18 ++++++++++++++++++ 8 files changed, 59 insertions(+), 7 deletions(-) diff --git a/client/coral-admin/src/actions/configure.js b/client/coral-admin/src/actions/configure.js index cdb2a3d1b..cf33a4d11 100644 --- a/client/coral-admin/src/actions/configure.js +++ b/client/coral-admin/src/actions/configure.js @@ -3,3 +3,7 @@ import * as actions from 'constants/configure'; export const updatePending = ({updater, errorUpdater}) => { return {type: actions.UPDATE_PENDING, updater, errorUpdater}; }; + +export const clearPending = () => { + return {type: actions.CLEAR_PENDING}; +}; diff --git a/client/coral-admin/src/constants/configure.js b/client/coral-admin/src/constants/configure.js index 00985e3ff..f2d55a2c2 100644 --- a/client/coral-admin/src/constants/configure.js +++ b/client/coral-admin/src/constants/configure.js @@ -1,3 +1,4 @@ const prefix = 'CORAL_ADMIN'; export const UPDATE_PENDING = `${prefix}_UPDATE_PENDING`; +export const CLEAR_PENDING = `${prefix}_CLEAR_PENDING`; diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index 6b8aa7456..bb83199e5 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -29,6 +29,16 @@ export default { } } }), + UpdateSettings: ({variables: {input}}) => ({ + updateQueries: { + TalkAdmin_Configure: (prev) => { + const updated = update(prev, { + settings: {$merge: input}, + }); + return updated; + } + } + }), }, }; diff --git a/client/coral-admin/src/reducers/configure.js b/client/coral-admin/src/reducers/configure.js index 116c8663f..a692addc1 100644 --- a/client/coral-admin/src/reducers/configure.js +++ b/client/coral-admin/src/reducers/configure.js @@ -30,6 +30,12 @@ export default function configure(state = initialState, action) { return next; } + case actions.CLEAR_PENDING: + return { + ...state, + pending: {}, + canSave: false, + }; } return state; } diff --git a/client/coral-admin/src/routes/Configure/components/Configure.js b/client/coral-admin/src/routes/Configure/components/Configure.js index f60951de3..5b5f9a524 100644 --- a/client/coral-admin/src/routes/Configure/components/Configure.js +++ b/client/coral-admin/src/routes/Configure/components/Configure.js @@ -14,10 +14,6 @@ export default class Configure extends Component { activeSection: 'stream', }; - saveSettings = () => { - this.props.saveSettingsToServer(); - } - changeSection = (activeSection) => { this.setState({activeSection}); } @@ -57,7 +53,7 @@ export default class Configure extends Component { render () { const {activeSection} = this.state; const section = this.getSection(activeSection); - const {auth: {user}, canSave} = this.props; + const {auth: {user}, canSave, savePending} = this.props; if (!can(user, 'UPDATE_CONFIG')) { return

You must be an administrator to access config settings. Please find the nearest Admin and ask them to level you up!

; @@ -82,7 +78,7 @@ export default class Configure extends Component { canSave ?