From 18a29bf0df5837d2234ffe80b82c88c22071c64f Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 5 Apr 2017 19:33:28 +0700 Subject: [PATCH] Restructure frontend plugins --- client/coral-embed-stream/src/Embed.js | 8 +- client/coral-framework/helpers/plugins.js | 50 +++---- .../coral-framework/loaders/plugins-loader.js | 63 ++------- client/coral-framework/reducers/index.js | 4 +- .../client/components/RespectButton.js | 131 +++++++++++++++++ .../client/{ => components}/style.css | 0 .../coral-plugin-respect/client/config.json | 4 - plugins/coral-plugin-respect/client/index.js | 133 +----------------- 8 files changed, 166 insertions(+), 227 deletions(-) create mode 100644 plugins/coral-plugin-respect/client/components/RespectButton.js rename plugins/coral-plugin-respect/client/{ => components}/style.css (100%) delete mode 100644 plugins/coral-plugin-respect/client/config.json diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 3caa40566..31f829829 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -1,6 +1,5 @@ import React, {Component} from 'react'; import {compose} from 'react-apollo'; -import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; import isEqual from 'lodash/isEqual'; import I18n from 'coral-framework/modules/i18n/i18n'; @@ -17,7 +16,7 @@ import {queryStream} from 'coral-framework/graphql/queries'; import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag} from 'coral-framework/graphql/mutations'; import {editName} from 'coral-framework/actions/user'; import {updateCountCache} from 'coral-framework/actions/asset'; -import {notificationActions, authActions, assetActions, pym, actions} from 'coral-framework'; +import {notificationActions, authActions, assetActions, pym} from 'coral-framework'; import Stream from './Stream'; import InfoBox from 'coral-plugin-infobox/InfoBox'; @@ -66,11 +65,6 @@ class Embed extends Component { removeCommentTag: React.PropTypes.func, } - constructor(props) { - super(props); - this.pluginActions = bindActionCreators(actions.pluginActions, props.dispatch); - } - componentDidMount () { pym.sendMessage('childReady'); } diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js index be730535d..ed9c27e8e 100644 --- a/client/coral-framework/helpers/plugins.js +++ b/client/coral-framework/helpers/plugins.js @@ -1,45 +1,29 @@ import React from 'react'; -import values from 'lodash/values'; -import flatten from 'lodash/flatten'; import merge from 'lodash/merge'; +import flatten from 'lodash/flatten'; import plugins from 'pluginsConfig'; -/** - * Returns the config object of given plugin. - */ -function getConfig(plugin) { - return plugins.configs.filter(o => o.plugin === plugin)[0].getModule(); -} +const components = flatten( + plugins + .map(o => o.module.components) + .filter(o => o) +); + +export const pluginReducers = merge( + ...plugins + .filter(o => o.module.reducer) + .map(o => ({...o.module.reducer})) +); /** * Returns React Elements for given slot. */ export function getSlotElements(slot, props = {}) { - return plugins.components - .map((o, i) => ({ - component: o.getModule(), - props: {...getConfig(o.plugin), ...props, key: i}, + return components + .map((component, i) => ({ + component, + props: {...props, key: i}, })) - .filter(o => o.props.slot === slot) + .filter(o => o.component.slot === slot) .map(o => React.createElement(o.component, o.props)); } - -// Returns a map of redux actions. -export function getPluginActions() { - return merge(...plugins.actions.map(o => ({...o.getModule()}))); -} - -// Returns a map of redux reducers. -export function getPluginReducers() { - merge(...plugins.reducers.map(o => ({...o.getModule()}))); -} - -// Returns an array of graphQL queries and mutators. -export function getPluginQueriesAndMutators() { - return flatten( - merge( - plugins.queries.map(o => values(o.getModule())), - plugins.mutators.map(o => values(o.getModule())), - ) - ); -} diff --git a/client/coral-framework/loaders/plugins-loader.js b/client/coral-framework/loaders/plugins-loader.js index 170e7f61b..83bdae33b 100644 --- a/client/coral-framework/loaders/plugins-loader.js +++ b/client/coral-framework/loaders/plugins-loader.js @@ -1,38 +1,13 @@ /** * Executes `source` to retrieve plugins configuration - * and loads all modules of specified plugins. + * and loads the `index.js` of specified plugins. * * Outputs a module that looks like the following: * - * module.exports.actions = [{plugin: string, getModule: callback}, ...] - * module.exports.reducer = [{plugin: string, getModule: callback}, ...] - * [...] + * module.exports = [{plugin: string, module: object}, ...] * */ const {stripIndent} = require('common-tags'); -const fs = require('fs'); -const path = require('path'); - -function moduleExists(loc) { - const fileExists = fs.existsSync(path.resolve(__dirname, loc)); - if (fileExists) { - return true; - } - - const hasExtension = /\.[^\/\\]*$/.test(loc); - if (hasExtension) { - return false; - } - - // Find file with appended `.js` extension. - return fs.existsSync(path.resolve(__dirname, `${loc}.js`)); -} - -function addIfExists(list, plugin, resource) { - if (moduleExists(`../../../plugins/${plugin}/client/${resource}`)) { - list.push(`{getModule: () => require('plugins/${plugin}/client/${resource}'), plugin: '${plugin}'}`); - } -} function getPluginList(config) { return config.client.map(x => typeof x === 'string' ? x : Object.keys(x)[0]); @@ -43,34 +18,14 @@ module.exports = function(source) { const config = this.exec(source, this.resourcePath); const plugins = getPluginList(config); - const exports = { - configs: [], - actions: [], - reducers: [], - components: [], - queries: [], - mutators: [], - }; - + const list = []; plugins.forEach(plugin => { - addIfExists(exports.components, plugin, 'index.js'); - addIfExists(exports.configs, plugin, 'config.json'); - addIfExists(exports.actions, plugin, 'actions'); - addIfExists(exports.reducers, plugin, 'reducer'); - addIfExists(exports.mutators, plugin, 'mutations'); - addIfExists(exports.queries, plugin, 'queries'); + list.push(`{module: require('plugins/${plugin}/client/index.js'), plugin: '${plugin}'}`); }); - let result = ''; - - Object.keys(exports).forEach(key => { - if (result) { result += '\n\n'; } - result += stripIndent` - module.exports.${key} = [ - ${exports[key].join(',')} - ]; - `; - }); - - return result; + return stripIndent` + module.exports = [ + ${list.join(',')} + ]; + `; }; diff --git a/client/coral-framework/reducers/index.js b/client/coral-framework/reducers/index.js index 6b296bed9..fae7e60d5 100644 --- a/client/coral-framework/reducers/index.js +++ b/client/coral-framework/reducers/index.js @@ -1,11 +1,11 @@ import auth from './auth'; import user from './user'; import asset from './asset'; -import {getPluginReducers} from '../helpers/plugins'; +import {pluginReducers} from '../helpers/plugins'; export default { auth, user, asset, - ...getPluginReducers() + ...pluginReducers }; diff --git a/plugins/coral-plugin-respect/client/components/RespectButton.js b/plugins/coral-plugin-respect/client/components/RespectButton.js new file mode 100644 index 000000000..a6c61de7c --- /dev/null +++ b/plugins/coral-plugin-respect/client/components/RespectButton.js @@ -0,0 +1,131 @@ +import React from 'react'; +import styles from './style.css'; +import Icon from './Icon'; + +import {compose, gql, graphql} from 'react-apollo'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {I18n} from 'coral-framework'; +import cn from 'classnames'; +import translations from '../translations.json'; +import {withPostRespect} from '../mutations'; + +const lang = new I18n(translations); + +import {getActionSummary} from 'coral-framework/utils'; +import {showSignInDialog} from 'coral-framework/actions/auth'; +import {deleteAction} from 'coral-framework/graphql/mutations'; + +class RespectButton extends React.Component { + + static slot = 'Comment.Detail'; + + constructor(props) { + super(props); + + this.state = { + localPost: null, // Set to the ID of an action if one is posted + localDelete: false // Set to true is the user deletes an action, unless localPost is already set. + }; + } + + handleClick = () => { + const {postRespect, showSignInDialog, deleteAction, commentId} = this.props; + const {me, comment} = this.props.data; + + const {localPost, localDelete} = this.state; + const respect = getActionSummary('RespectActionSummary', comment); + const respected = (respect && respect.current_user && !localDelete) || localPost; + + /** + * If the current user does not exist, trigger signIn Box + */ + + if (!me) { + const offset = document.getElementById(`c_${commentId}`).getBoundingClientRect().top - 75; + showSignInDialog(offset); + return; + } + + /** + * If the current user is banned, do nothing + */ + + if (me.status === 'BANNED') { + return; + } + + if (!respected) { + this.setState({ + localPost: 'temp' + }); + + postRespect({ + item_id: commentId, + item_type: 'COMMENTS' + }).then(({data}) => { + this.setState({ + localPost: data.createRespect.respect.id + }); + }); + + } else { + this.setState((prev) => prev.localPost ? {...prev, localPost: null} : {...prev, localDelete: true}); + deleteAction(localPost || respect.current_user.id); + } + } + + render() { + const {comment} = this.props.data; + const {localPost, localDelete} = this.state; + const respect = comment && getActionSummary('RespectActionSummary', comment); + const respected = (respect && respect.current_user && !localDelete) || localPost; + let count = respect ? respect.count : 0; + + if (localPost) {count += 1;} + if (localDelete) {count -= 1;} + + return ( +
+ +
+ ); + } +} + +const withQuery = graphql(gql` + query CommentQuery($commentId: ID!) { + comment(id: $commentId) { + action_summaries { + ... on RespectActionSummary { + count + current_user { + id + } + } + } + } + me { + status + } + } +`); + +const mapDispatchToProps = dispatch => + bindActionCreators({showSignInDialog}, dispatch); + +const enhance = compose( + connect(null, mapDispatchToProps), + deleteAction, + withPostRespect, + withQuery, +); + +export default enhance(RespectButton); + diff --git a/plugins/coral-plugin-respect/client/style.css b/plugins/coral-plugin-respect/client/components/style.css similarity index 100% rename from plugins/coral-plugin-respect/client/style.css rename to plugins/coral-plugin-respect/client/components/style.css diff --git a/plugins/coral-plugin-respect/client/config.json b/plugins/coral-plugin-respect/client/config.json deleted file mode 100644 index ef9fed04f..000000000 --- a/plugins/coral-plugin-respect/client/config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Coral Plugin Respect", - "slot": "Comment.Detail" -} diff --git a/plugins/coral-plugin-respect/client/index.js b/plugins/coral-plugin-respect/client/index.js index 56f32d857..7a8d54535 100644 --- a/plugins/coral-plugin-respect/client/index.js +++ b/plugins/coral-plugin-respect/client/index.js @@ -1,128 +1,7 @@ -import React from 'react'; -import styles from './style.css'; -import Icon from './components/Icon'; - -import {compose, gql, graphql} from 'react-apollo'; -import {connect} from 'react-redux'; -import {bindActionCreators} from 'redux'; -import {I18n} from 'coral-framework'; -import cn from 'classnames'; -import translations from './translations.json'; -import {withPostRespect} from './mutations'; - -const lang = new I18n(translations); - -import {getActionSummary} from 'coral-framework/utils'; -import {showSignInDialog} from 'coral-framework/actions/auth'; -import {deleteAction} from 'coral-framework/graphql/mutations'; - -class RespectButton extends React.Component { - constructor(props) { - super(props); - - this.state = { - localPost: null, // Set to the ID of an action if one is posted - localDelete: false // Set to true is the user deletes an action, unless localPost is already set. - }; - } - - handleClick = () => { - const {postRespect, showSignInDialog, deleteAction, commentId} = this.props; - const {me, comment} = this.props.data; - - const {localPost, localDelete} = this.state; - const respect = getActionSummary('RespectActionSummary', comment); - const respected = (respect && respect.current_user && !localDelete) || localPost; - - /** - * If the current user does not exist, trigger signIn Box - */ - - if (!me) { - const offset = document.getElementById(`c_${commentId}`).getBoundingClientRect().top - 75; - showSignInDialog(offset); - return; - } - - /** - * If the current user is banned, do nothing - */ - - if (me.status === 'BANNED') { - return; - } - - if (!respected) { - this.setState({ - localPost: 'temp' - }); - - postRespect({ - item_id: commentId, - item_type: 'COMMENTS' - }).then(({data}) => { - this.setState({ - localPost: data.createRespect.respect.id - }); - }); - - } else { - this.setState((prev) => prev.localPost ? {...prev, localPost: null} : {...prev, localDelete: true}); - deleteAction(localPost || respect.current_user.id); - } - } - - render() { - const {comment} = this.props.data; - const {localPost, localDelete} = this.state; - const respect = comment && getActionSummary('RespectActionSummary', comment); - const respected = (respect && respect.current_user && !localDelete) || localPost; - let count = respect ? respect.count : 0; - - if (localPost) {count += 1;} - if (localDelete) {count -= 1;} - - return ( -
- -
- ); - } -} - -const withQuery = graphql(gql` - query CommentQuery($commentId: ID!) { - comment(id: $commentId) { - action_summaries { - ... on RespectActionSummary { - count - current_user { - id - } - } - } - } - me { - status - } - } -`); - -const mapDispatchToProps = dispatch => - bindActionCreators({showSignInDialog}, dispatch); - -const enhance = compose( - connect(null, mapDispatchToProps), - deleteAction, - withPostRespect, - withQuery, -); - -export default enhance(RespectButton); +import RespectButton from './components/RespectButton'; +import * as reducer from './reducer'; +export default { + components: [RespectButton], + reducer, +};