From 0f82fa8bc52a71bb943e8066f66fa18c7b4701cf Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 21 Jul 2017 20:36:07 +0700 Subject: [PATCH 01/20] Get store from context --- client/coral-framework/hocs/withMutation.js | 193 +++++++++++--------- 1 file changed, 103 insertions(+), 90 deletions(-) diff --git a/client/coral-framework/hocs/withMutation.js b/client/coral-framework/hocs/withMutation.js index d3ba73ecd..80b1aa29d 100644 --- a/client/coral-framework/hocs/withMutation.js +++ b/client/coral-framework/hocs/withMutation.js @@ -5,9 +5,9 @@ import uniq from 'lodash/uniq'; import flatten from 'lodash/flatten'; import isEmpty from 'lodash/isEmpty'; import {getMutationOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry'; -import {getStore} from 'coral-framework/services/store'; import {getDefinitionName, getResponseErrors} from '../utils'; import t from 'coral-framework/services/i18n'; +const PropTypes = require('prop-types'); class ResponseErrors extends Error { constructor(errors) { @@ -36,98 +36,111 @@ export default (document, config = {}) => (WrappedComponent) => { options: config.options || {}, props: config.props || ((data) => ({mutate: data.mutate()})), }; - const wrappedProps = (data) => { - const name = getDefinitionName(document); - const callbacks = getMutationOptions(name); - const mutate = (base) => { - const variables = base.variables || config.options.variables; - const configs = callbacks.map((cb) => cb({variables, state: getStore().getState()})); - const optimisticResponse = merge( - base.optimisticResponse || config.options.optimisticResponse, - ...configs.map((cfg) => cfg.optimisticResponse), - ); - - const refetchQueries = flatten(uniq([ - base.refetchQueries || config.options.refetchQueries, - ...configs.map((cfg) => cfg.refetchQueries), - ].filter((i) => i))); - - const updateCallbacks = - [base.update || config.options.update] - .concat(...configs.map((cfg) => cfg.update)) - .filter((i) => i); - - const update = (proxy, result) => { - if (getResponseErrors(result)) { - - // Do not run updates when we have mutation errors. - return; - } - updateCallbacks.forEach((cb) => cb(proxy, result)); - }; - - const updateQueries = - [ - base.updateQueries || config.options.updateQueries, - ...configs.map((cfg) => cfg.updateQueries) - ] - .filter((i) => i) - .reduce((res, map) => { - Object.keys(map).forEach((key) => { - if (!(key in res)) { - res[key] = (prev, result) => { - if (getResponseErrors(result.mutationResult)) { - - // Do not run updates when we have mutation errors. - return prev; - } - return map[key](prev, result); - }; - } else { - const existing = res[key]; - res[key] = (prev, result) => { - const next = existing(prev, result); - return map[key](next, result); - }; - } - }); - return res; - }, {}); - - const wrappedConfig = { - variables, - optimisticResponse, - refetchQueries, - updateQueries, - update, - }; - if (isEmpty(wrappedConfig.optimisticResponse)) { - delete wrappedConfig.optimisticResponse; - } - return data.mutate(wrappedConfig) - .then((res) => { - const errors = getResponseErrors(res); - if (errors) { - throw new ResponseErrors(errors); - } - return Promise.resolve(res); - }); + return class WithMutation extends React.Component { + static contextTypes = { + eventEmitter: PropTypes.object, + store: PropTypes.object, }; - return config.props({...data, mutate}); - }; - // Lazily resolve fragments from graphRegistry to support circular dependencies. - let memoized = null; - const getWrapped = () => { - if (!memoized) { - memoized = graphql(resolveFragments(document), {...config, props: wrappedProps})(WrappedComponent); + emit = (eventName, value, context) => { + this.context.eventEmitter.emit(eventName, value, context); + }; + + // Lazily resolve fragments from graphRegistry to support circular dependencies. + memoized = null; + + wrappedProps = (data) => { + const name = getDefinitionName(document); + const callbacks = getMutationOptions(name); + const mutate = (base) => { + const variables = base.variables || config.options.variables; + const configs = callbacks.map((cb) => cb({variables, state: this.context.store.getState()})); + + const optimisticResponse = merge( + base.optimisticResponse || config.options.optimisticResponse, + ...configs.map((cfg) => cfg.optimisticResponse), + ); + + const refetchQueries = flatten(uniq([ + base.refetchQueries || config.options.refetchQueries, + ...configs.map((cfg) => cfg.refetchQueries), + ].filter((i) => i))); + + const updateCallbacks = + [base.update || config.options.update] + .concat(...configs.map((cfg) => cfg.update)) + .filter((i) => i); + + const update = (proxy, result) => { + if (getResponseErrors(result)) { + + // Do not run updates when we have mutation errors. + return; + } + updateCallbacks.forEach((cb) => cb(proxy, result)); + }; + + const updateQueries = + [ + base.updateQueries || config.options.updateQueries, + ...configs.map((cfg) => cfg.updateQueries) + ] + .filter((i) => i) + .reduce((res, map) => { + Object.keys(map).forEach((key) => { + if (!(key in res)) { + res[key] = (prev, result) => { + if (getResponseErrors(result.mutationResult)) { + + // Do not run updates when we have mutation errors. + return prev; + } + return map[key](prev, result); + }; + } else { + const existing = res[key]; + res[key] = (prev, result) => { + const next = existing(prev, result); + return map[key](next, result); + }; + } + }); + return res; + }, {}); + + const wrappedConfig = { + variables, + optimisticResponse, + refetchQueries, + updateQueries, + update, + }; + if (isEmpty(wrappedConfig.optimisticResponse)) { + delete wrappedConfig.optimisticResponse; + } + return data.mutate(wrappedConfig) + .then((res) => { + const errors = getResponseErrors(res); + if (errors) { + throw new ResponseErrors(errors); + } + return Promise.resolve(res); + }); + }; + return config.props({...data, mutate}); + }; + + getWrapped = () => { + if (!this.memoized) { + this.memoized = graphql(resolveFragments(document), {...config, props: this.wrappedProps})(WrappedComponent); + } + return this.memoized; + }; + + render() { + const Wrapped = this.getWrapped(); + return ; } - return memoized; - }; - - return (props) => { - const Wrapped = getWrapped(); - return ; }; }; From cb95e9a30842559f1edd9b36c9c2f814e1915760 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 21 Jul 2017 22:54:45 +0700 Subject: [PATCH 02/20] Implement support for events --- client/coral-embed-stream/src/index.js | 29 ++- client/coral-embed/src/index.js | 12 ++ .../components/EventEmitterProvider.js | 18 ++ client/coral-framework/hocs/index.js | 3 + client/coral-framework/hocs/withEmit.js | 23 +++ client/coral-framework/hocs/withMutation.js | 14 +- client/coral-framework/hocs/withQuery.js | 188 ++++++++++++------ client/coral-framework/services/store.js | 12 ++ package.json | 1 + views/article.ejs | 5 + yarn.lock | 4 + 11 files changed, 239 insertions(+), 70 deletions(-) create mode 100644 client/coral-framework/components/EventEmitterProvider.js create mode 100644 client/coral-framework/hocs/withEmit.js diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 9020a8457..120fe4512 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -5,15 +5,18 @@ import {ApolloProvider} from 'react-apollo'; import {checkLogin} from 'coral-framework/actions/auth'; import './graphql'; import {addExternalConfig} from 'coral-embed-stream/src/actions/config'; -import {getStore, injectReducers} from 'coral-framework/services/store'; +import {getStore, injectReducers, addListener} from 'coral-framework/services/store'; import {getClient} from 'coral-framework/services/client'; import pym from 'coral-framework/services/pym'; import AppRouter from './AppRouter'; import {loadPluginsTranslations, injectPluginsReducers} from 'coral-framework/helpers/plugins'; import reducers from './reducers'; +import EventEmitter from 'eventemitter2'; +import EventEmitterProvider from 'coral-framework/components/EventEmitterProvider'; const store = getStore(); const client = getClient(); +const eventEmitter = new EventEmitter(); loadPluginsTranslations(); injectPluginsReducers(); @@ -42,11 +45,29 @@ if (!window.opener) { } else { init(); } + + // Pass any events through our parent. + eventEmitter.onAny((eventName, value) => { + pym.sendMessage('eventEmitter', JSON.stringify({eventName, value})); + }); + + // Add a redux listener to pass through all actions to our event emitter. + addListener((action) => { + + // Ignore apollo actions. + if (action.type.startsWith('APOLLO_')) { + return; + } + + eventEmitter.emit(`redux.action.${action.type}`, {action}); + }); } render( - - - + + + + + , document.querySelector('#talk-embed-stream-container') ); diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js index eb61f3b90..7c7b0658f 100644 --- a/client/coral-embed/src/index.js +++ b/client/coral-embed/src/index.js @@ -3,6 +3,9 @@ import URLSearchParams from 'url-search-params'; import {buildUrl} from 'coral-framework/utils'; import queryString from 'query-string'; +import EventEmitter from 'eventemitter2'; + +const eventEmitter = new EventEmitter(); // TODO: Styles should live in a separate file const snackbarStyles = { @@ -57,6 +60,10 @@ function configurePymParent(pymParent, opts) { pymParent.sendMessage('config', JSON.stringify(config)); } + if (opts.events) { + opts.events(eventEmitter); + } + pymParent.onMessage('coral-auth-changed', function(message) { if (opts.onAuthChanged) { opts.onAuthChanged(message ? JSON.parse(message) : null); @@ -167,6 +174,11 @@ function configurePymParent(pymParent, opts) { window.open(url, '_blank').focus(); }); + pymParent.onMessage('eventEmitter', (raw) => { + const {eventName, value} = JSON.parse(raw); + eventEmitter.emit(eventName, value); + }); + // get dimensions of viewport const viewport = () => { let e = window, a = 'inner'; diff --git a/client/coral-framework/components/EventEmitterProvider.js b/client/coral-framework/components/EventEmitterProvider.js new file mode 100644 index 000000000..36c0bccdb --- /dev/null +++ b/client/coral-framework/components/EventEmitterProvider.js @@ -0,0 +1,18 @@ +import React from 'react'; +const PropTypes = require('prop-types'); + +class EventEmitterProvider extends React.Component { + getChildContext() { + return {eventEmitter: this.props.eventEmitter}; + } + + render() { + return this.props.children; + } +} + +EventEmitterProvider.childContextTypes = { + eventEmitter: PropTypes.object, +}; + +export default EventEmitterProvider; diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js index 782f840bd..67b47482e 100644 --- a/client/coral-framework/hocs/index.js +++ b/client/coral-framework/hocs/index.js @@ -2,3 +2,6 @@ export {default as withFragments} from './withFragments'; export {default as withMutation} from './withMutation'; export {default as withQuery} from './withQuery'; export {default as withCopyToClipboard} from './withCopyToClipboard'; +export {default as withEmit} from './withEmit'; +export {default as excludeIf} from './excludeIf'; +export {default as connect} from './connect'; diff --git a/client/coral-framework/hocs/withEmit.js b/client/coral-framework/hocs/withEmit.js new file mode 100644 index 000000000..9e47d3a07 --- /dev/null +++ b/client/coral-framework/hocs/withEmit.js @@ -0,0 +1,23 @@ +import React from 'react'; +const PropTypes = require('prop-types'); + +export default (WrappedComponent) => { + class WithCopyToClipboard extends React.Component { + static contextTypes = { + eventEmitter: PropTypes.object, + }; + + emit = (eventName, value, context) => { + this.context.eventEmitter.emit(eventName, value, context); + }; + + render() { + return ; + } + } + + return WithCopyToClipboard; +}; diff --git a/client/coral-framework/hocs/withMutation.js b/client/coral-framework/hocs/withMutation.js index 80b1aa29d..4008e3491 100644 --- a/client/coral-framework/hocs/withMutation.js +++ b/client/coral-framework/hocs/withMutation.js @@ -6,8 +6,8 @@ import flatten from 'lodash/flatten'; import isEmpty from 'lodash/isEmpty'; import {getMutationOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry'; import {getDefinitionName, getResponseErrors} from '../utils'; +import PropTypes from 'prop-types'; import t from 'coral-framework/services/i18n'; -const PropTypes = require('prop-types'); class ResponseErrors extends Error { constructor(errors) { @@ -43,10 +43,6 @@ export default (document, config = {}) => (WrappedComponent) => { store: PropTypes.object, }; - emit = (eventName, value, context) => { - this.context.eventEmitter.emit(eventName, value, context); - }; - // Lazily resolve fragments from graphRegistry to support circular dependencies. memoized = null; @@ -119,13 +115,21 @@ export default (document, config = {}) => (WrappedComponent) => { if (isEmpty(wrappedConfig.optimisticResponse)) { delete wrappedConfig.optimisticResponse; } + + this.context.eventEmitter.emit(`mutation.${name}.begin`, {variables}); + return data.mutate(wrappedConfig) .then((res) => { const errors = getResponseErrors(res); if (errors) { throw new ResponseErrors(errors); } + this.context.eventEmitter.emit(`mutation.${name}.success`, {variables}); return Promise.resolve(res); + }) + .catch((error) => { + this.context.eventEmitter.emit(`mutation.${name}.error`, {variables, error}); + throw new error; }); }; return config.props({...data, mutate}); diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js index ec190466c..9265a1656 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -2,6 +2,7 @@ import * as React from 'react'; import {graphql} from 'react-apollo'; import {getQueryOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry'; import {getDefinitionName, separateDataAndRoot, getResponseErrors} from '../utils'; +import PropTypes from 'prop-types'; const withSkipOnErrors = (reducer) => (prev, action, ...rest) => { if (action.type === 'APOLLO_MUTATION_RESULT' && getResponseErrors(action.result)) { @@ -10,76 +11,141 @@ const withSkipOnErrors = (reducer) => (prev, action, ...rest) => { return reducer(prev, action, ...rest); }; +function networkStatusToString(networkStatus) { + switch(networkStatus) { + case 1: + return 'loading'; + case 2: + return 'setVariables'; + case 3: + return 'fetchMore'; + case 4: + return 'refetch'; + case 6: + return 'poll'; + case 7: + return 'ready'; + case 8: + return 'error'; + } + throw new Error(`Unknown network status ${networkStatus}`); +} + /** * Exports a HOC with the same signature as `graphql`, that will * apply query options registered in the graphRegistry. */ export default (document, config = {}) => (WrappedComponent) => { - const wrappedConfig = { - ...config, - options: config.options || {}, - props: (args) => { - const wrappedArgs = { - ...args, - data: { - ...args.data, - subscribeToMore(stmArgs) { + const name = getDefinitionName(document); - // Resolve document fragments before passing it to `apollo-client`. - return args.data.subscribeToMore({ - ...stmArgs, - document: resolveFragments(stmArgs.document), - }); - }, - fetchMore(lmArgs) { - - // Resolve document fragments before passing it to `apollo-client`. - return args.data.fetchMore({ - ...lmArgs, - query: resolveFragments(lmArgs.query), - }); - }, - }, - }; - return config.props - ? config.props(wrappedArgs) - : separateDataAndRoot(wrappedArgs.data); - }, - }; - - const wrappedOptions = (data) => { - const base = (typeof wrappedConfig.options === 'function') - ? wrappedConfig.options(data) - : wrappedConfig.options; - const name = getDefinitionName(document); - const configs = getQueryOptions(name); - const reducerCallbacks = - [base.reducer || ((i) => i)] - .concat(...configs.map((cfg) => cfg.reducer)) - .filter((i) => i); - - const reducer = withSkipOnErrors( - reducerCallbacks.reduce( - (a, b) => (prev, ...rest) => - b(a(prev, ...rest), ...rest), - )); - - return { - ...base, - reducer, + return class WithQuery extends React.Component { + static contextTypes = { + eventEmitter: PropTypes.object, }; - }; - let memoized = null; - const getWrapped = () => { - if (!memoized) { - memoized = graphql(resolveFragments(document), {...wrappedConfig, options: wrappedOptions})(WrappedComponent); + // Lazily resolve fragments from graphRegistry to support circular dependencies. + memoized = null; + lastNetworkStatus = null; + + emitWhenNeeded(data) { + const {variables, networkStatus} = data; + if (this.lastNetworkStatus === networkStatus) { + return; + } + this.lastNetworkStatus = networkStatus; + + const status = networkStatusToString(networkStatus); + + this.context.eventEmitter.emit(`query.${name}.${status}`, {variables, networkStatus}); } - return memoized; - }; - return (props) => { - const Wrapped = getWrapped(); - return ; + wrappedConfig = { + ...config, + options: config.options || {}, + props: (args) => { + this.emitWhenNeeded(args.data); + + const wrappedArgs = { + ...args, + data: { + ...args.data, + subscribeToMore: (stmArgs) => { + const subscrName = getDefinitionName(stmArgs.document); + this.context.eventEmitter.emit( + `query.${name}.subscribeToMore.${subscrName}`, + {variables: stmArgs.variables}); + + // Resolve document fragments before passing it to `apollo-client`. + return args.data.subscribeToMore({ + ...stmArgs, + document: resolveFragments(stmArgs.document), + onError: (err) => { + this.context.eventEmitter.emit( + `query.${name}.subscribeToMore.${subscrName}.error`, + {variables: stmArgs.variables}); + + if (stmArgs.onErr) { + return stmArgs.onErr(err); + } + throw err; + }, + }); + }, + fetchMore: (lmArgs) => { + const fetchName = getDefinitionName(lmArgs.query); + this.context.eventEmitter.emit( + `query.${name}.fetchMore.${fetchName}`, + {variables: lmArgs.variables}); + + // Resolve document fragments before passing it to `apollo-client`. + return args.data.fetchMore({ + ...lmArgs, + query: resolveFragments(lmArgs.query), + }); + }, + }, + }; + return config.props + ? config.props(wrappedArgs) + : separateDataAndRoot(wrappedArgs.data); + }, + }; + + wrappedOptions = (data) => { + const base = (typeof this.wrappedConfig.options === 'function') + ? this.wrappedConfig.options(data) + : this.wrappedConfig.options; + const configs = getQueryOptions(name); + const reducerCallbacks = + [base.reducer || ((i) => i)] + .concat(...configs.map((cfg) => cfg.reducer)) + .filter((i) => i); + + const reducer = withSkipOnErrors( + reducerCallbacks.reduce( + (a, b) => (prev, ...rest) => + b(a(prev, ...rest), ...rest), + )); + + return { + ...base, + reducer, + }; + }; + + getWrapped = () => { + if (!this.memoized) { + this.memoized = graphql( + resolveFragments(document), + {...this.wrappedConfig, options: this.wrappedOptions}, + )(WrappedComponent); + } + return this.memoized; + }; + + render() { + const Wrapped = this.getWrapped(); + return ; + } }; }; diff --git a/client/coral-framework/services/store.js b/client/coral-framework/services/store.js index 4d6dafe20..eaf221e14 100644 --- a/client/coral-framework/services/store.js +++ b/client/coral-framework/services/store.js @@ -3,12 +3,18 @@ import thunk from 'redux-thunk'; import mainReducer from '../reducers'; import {getClient} from './client'; +let listeners = []; + export function injectReducers(reducers) { const store = getStore(); store.coralReducers = {...store.coralReducers, ...reducers}; store.replaceReducer(combineReducers(store.coralReducers)); } +export function addListener(cb) { + listeners.push(cb); +} + export function getStore() { if (window.coralStore) { return window.coralStore; @@ -21,11 +27,17 @@ export function getStore() { return next(action); }; + const customListener = () => (next) => (action) => { + listeners.forEach((cb) => {cb(action);}); + return next(action); + }; + const middlewares = [ applyMiddleware( getClient().middleware(), thunk, apolloErrorReporter, + customListener, ), ]; diff --git a/package.json b/package.json index 9cb065320..663efceaf 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "dotenv": "^4.0.0", "ejs": "^2.5.6", "env-rewrite": "^1.0.2", + "eventemitter2": "^4.1.2", "express": "^4.15.2", "express-session": "^1.15.1", "file-loader": "^0.11.2", diff --git a/views/article.ejs b/views/article.ejs index d803b6295..fe449dfc5 100644 --- a/views/article.ejs +++ b/views/article.ejs @@ -30,6 +30,11 @@ asset_url: '<%= asset_url ? asset_url : '' %>', asset_id: '<%= asset_id ? asset_id : '' %>', auth_token: '', + events: function(events) { + events.onAny(function(eventName, data) { + console.log(eventName, data); + }); + }, plugin_config: { /** * You can disable rendering slot components of a plugin by doing: diff --git a/yarn.lock b/yarn.lock index 710b89fa2..e13fde3ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2961,6 +2961,10 @@ event-stream@~3.3.0: stream-combiner "~0.0.4" through "~2.3.1" +eventemitter2@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-4.1.2.tgz#0e1a8477af821a6ef3995b311bf74c23a5247f15" + eventemitter3@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" From 970fe6fe0405cbdc783ddb03a25ab4a483111569 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 21 Jul 2017 23:39:28 +0700 Subject: [PATCH 03/20] Provide event emitter on admin side --- client/coral-admin/src/index.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/index.js b/client/coral-admin/src/index.js index 29df3753c..e50d29408 100644 --- a/client/coral-admin/src/index.js +++ b/client/coral-admin/src/index.js @@ -2,6 +2,8 @@ import React from 'react'; import {render} from 'react-dom'; import {ApolloProvider} from 'react-apollo'; import smoothscroll from 'smoothscroll-polyfill'; +import EventEmitter from 'eventemitter2'; +import EventEmitterProvider from 'coral-framework/components/EventEmitterProvider'; import {getClient} from './services/client'; import store from './services/store'; @@ -12,13 +14,17 @@ import 'react-mdl/extra/material.js'; import './graphql'; import {loadPluginsTranslations, injectPluginsReducers} from 'coral-framework/helpers/plugins'; +const eventEmitter = new EventEmitter(); + loadPluginsTranslations(); injectPluginsReducers(); smoothscroll.polyfill(); render( - - - + + + + + , document.querySelector('#root') ); From 8b582e85f8967ed04462ddf620ec34e7e8825439 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 21 Jul 2017 23:42:10 +0700 Subject: [PATCH 04/20] No redux prefix --- client/coral-embed-stream/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 120fe4512..94a1a032f 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -59,7 +59,7 @@ if (!window.opener) { return; } - eventEmitter.emit(`redux.action.${action.type}`, {action}); + eventEmitter.emit(`action.${action.type}`, {action}); }); } From 92d1fc5759ab91108b96ee2502c14ecc14689ff3 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 21 Jul 2017 23:55:15 +0700 Subject: [PATCH 05/20] Comment out events listener --- views/article.ejs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/views/article.ejs b/views/article.ejs index fe449dfc5..f1a7ba5d9 100644 --- a/views/article.ejs +++ b/views/article.ejs @@ -30,11 +30,17 @@ asset_url: '<%= asset_url ? asset_url : '' %>', asset_id: '<%= asset_id ? asset_id : '' %>', auth_token: '', - events: function(events) { - events.onAny(function(eventName, data) { - console.log(eventName, data); - }); - }, + /** + * You can listen to events using the example below. + * The argument passed is the event emitter from + * https://github.com/asyncly/EventEmitter2 + * + * events: function(events) { + * events.onAny(function(eventName, data) { + * console.log(eventName, data); + * }); + * }, + */ plugin_config: { /** * You can disable rendering slot components of a plugin by doing: From 6ea7e9d5645e1b1edb9372a4abb0eb642314db5b Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 21 Jul 2017 23:58:41 +0700 Subject: [PATCH 06/20] Use correct name --- client/coral-framework/hocs/withEmit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-framework/hocs/withEmit.js b/client/coral-framework/hocs/withEmit.js index 9e47d3a07..46c26c8f6 100644 --- a/client/coral-framework/hocs/withEmit.js +++ b/client/coral-framework/hocs/withEmit.js @@ -2,7 +2,7 @@ import React from 'react'; const PropTypes = require('prop-types'); export default (WrappedComponent) => { - class WithCopyToClipboard extends React.Component { + class WithEmit extends React.Component { static contextTypes = { eventEmitter: PropTypes.object, }; @@ -19,5 +19,5 @@ export default (WrappedComponent) => { } } - return WithCopyToClipboard; + return WithEmit; }; From 7b1c0dfbaa6ebd8a10374d640b9569b301f1d0b2 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Sat, 22 Jul 2017 00:05:36 +0700 Subject: [PATCH 07/20] Docs & stuff --- client/coral-embed-stream/src/index.js | 2 +- client/coral-embed/src/index.js | 3 ++- client/coral-framework/services/store.js | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 94a1a032f..f66ca9205 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -48,7 +48,7 @@ if (!window.opener) { // Pass any events through our parent. eventEmitter.onAny((eventName, value) => { - pym.sendMessage('eventEmitter', JSON.stringify({eventName, value})); + pym.sendMessage('event', JSON.stringify({eventName, value})); }); // Add a redux listener to pass through all actions to our event emitter. diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js index 7c7b0658f..8889d5331 100644 --- a/client/coral-embed/src/index.js +++ b/client/coral-embed/src/index.js @@ -174,7 +174,8 @@ function configurePymParent(pymParent, opts) { window.open(url, '_blank').focus(); }); - pymParent.onMessage('eventEmitter', (raw) => { + // Pass events from iframe to the event emitter + pymParent.onMessage('event', (raw) => { const {eventName, value} = JSON.parse(raw); eventEmitter.emit(eventName, value); }); diff --git a/client/coral-framework/services/store.js b/client/coral-framework/services/store.js index eaf221e14..f87a887a3 100644 --- a/client/coral-framework/services/store.js +++ b/client/coral-framework/services/store.js @@ -11,6 +11,10 @@ export function injectReducers(reducers) { store.replaceReducer(combineReducers(store.coralReducers)); } +/** + * Add a action listener to the redux store. + * The action is passed as the first argument to the callback. + */ export function addListener(cb) { listeners.push(cb); } From 1db61941ed7ea11049819f72a239cabb2011e9a7 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Sat, 22 Jul 2017 00:10:45 +0700 Subject: [PATCH 08/20] Comment WithEmit --- client/coral-framework/hocs/withEmit.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/coral-framework/hocs/withEmit.js b/client/coral-framework/hocs/withEmit.js index 46c26c8f6..3a3216c8a 100644 --- a/client/coral-framework/hocs/withEmit.js +++ b/client/coral-framework/hocs/withEmit.js @@ -1,14 +1,18 @@ import React from 'react'; const PropTypes = require('prop-types'); +/** + * WithEmit provides a property `emit: (eventName, value)` + * to the wrapped component. + */ export default (WrappedComponent) => { class WithEmit extends React.Component { static contextTypes = { eventEmitter: PropTypes.object, }; - emit = (eventName, value, context) => { - this.context.eventEmitter.emit(eventName, value, context); + emit = (eventName, value) => { + this.context.eventEmitter.emit(eventName, value); }; render() { From 85a10ba693d882009a04776f4614a8b80fa89e11 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 24 Jul 2017 18:49:51 -0300 Subject: [PATCH 09/20] Touch events for mobile :) --- .../client/components/Tag.css | 9 ++++++++ .../client/components/Tag.js | 23 +++++++++++++++++-- .../client/utils/index.js | 5 ++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 plugins/talk-plugin-featured-comments/client/utils/index.js diff --git a/plugins/talk-plugin-featured-comments/client/components/Tag.css b/plugins/talk-plugin-featured-comments/client/components/Tag.css index 88a019b5d..7ade3d968 100644 --- a/plugins/talk-plugin-featured-comments/client/components/Tag.css +++ b/plugins/talk-plugin-featured-comments/client/components/Tag.css @@ -24,6 +24,15 @@ cursor: pointer; } +.noSelect { + -ms-user-select:none; + -moz-user-select: none; + -webkit-user-select: none; + -webkit-touch-callout:none; + user-select: none; + -webkit-tap-highlight-color:rgba(0,0,0,0); +} + .tooltip { top: 36px; left: auto; diff --git a/plugins/talk-plugin-featured-comments/client/components/Tag.js b/plugins/talk-plugin-featured-comments/client/components/Tag.js index b7ee8cd6b..129e07851 100644 --- a/plugins/talk-plugin-featured-comments/client/components/Tag.js +++ b/plugins/talk-plugin-featured-comments/client/components/Tag.js @@ -4,6 +4,7 @@ import styles from './Tag.css'; import Tooltip from './Tooltip'; import {t} from 'plugin-api/beta/client/services'; import {isTagged} from 'plugin-api/beta/client/utils'; +import bowser from 'bowser'; export default class Tag extends React.Component { constructor() { @@ -15,6 +16,20 @@ export default class Tag extends React.Component { } + componentDidMount() { + if (bowser.mobile) { + this.tagEl.addEventListener('touchstart', this.showTooltip); + this.tagEl.addEventListener('touchend', this.hideTooltip); + } + } + + componentWillUnmount() { + if (bowser.mobile) { + this.tagEl.removeEventListener('touchstart', this.showTooltip); + this.tagEl.removeEventListener('touchend', this.hideTooltip); + } + } + showTooltip = () => { this.setState({ tooltip: true @@ -30,10 +45,14 @@ export default class Tag extends React.Component { render() { const {tooltip} = this.state; return( -
+
this.tagEl = ref} + onMouseEnter={this.showTooltip} + onMouseLeave={this.hideTooltip} + className={styles.noSelect }> { isTagged(this.props.comment.tags, 'FEATURED') ? ( - + {t('talk-plugin-featured-comments.featured')} ) : null diff --git a/plugins/talk-plugin-featured-comments/client/utils/index.js b/plugins/talk-plugin-featured-comments/client/utils/index.js new file mode 100644 index 000000000..7d7d80969 --- /dev/null +++ b/plugins/talk-plugin-featured-comments/client/utils/index.js @@ -0,0 +1,5 @@ +export const isTouchDevice = () => { + return (('ontouchstart' in window) + || (navigator.MaxTouchPoints > 0) + || (navigator.msMaxTouchPoints > 0)); +}; From 45a2e2a8a3fe8c135c94be2693cf12998cb78b49 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 25 Jul 2017 20:40:51 +0700 Subject: [PATCH 10/20] Activate wildcard matching --- client/coral-embed/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js index 8889d5331..fda97c4b3 100644 --- a/client/coral-embed/src/index.js +++ b/client/coral-embed/src/index.js @@ -5,7 +5,7 @@ import {buildUrl} from 'coral-framework/utils'; import queryString from 'query-string'; import EventEmitter from 'eventemitter2'; -const eventEmitter = new EventEmitter(); +const eventEmitter = new EventEmitter({wildcard: true}); // TODO: Styles should live in a separate file const snackbarStyles = { From 3c2229f3a7e09503f1a6a0620f8e78ce45e004d9 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 25 Jul 2017 20:48:26 +0700 Subject: [PATCH 11/20] Remove redundant networkStatus --- client/coral-framework/hocs/withQuery.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js index 9265a1656..b51467262 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -56,7 +56,7 @@ export default (document, config = {}) => (WrappedComponent) => { const status = networkStatusToString(networkStatus); - this.context.eventEmitter.emit(`query.${name}.${status}`, {variables, networkStatus}); + this.context.eventEmitter.emit(`query.${name}.${status}`, {variables}); } wrappedConfig = { From ceb83834a7c659352c80c1178f48656fe30882b5 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 25 Jul 2017 21:19:26 +0700 Subject: [PATCH 12/20] Remove subscribeToMore Event because it's incomplete --- client/coral-framework/hocs/withQuery.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js index b51467262..eba931200 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -70,20 +70,12 @@ export default (document, config = {}) => (WrappedComponent) => { data: { ...args.data, subscribeToMore: (stmArgs) => { - const subscrName = getDefinitionName(stmArgs.document); - this.context.eventEmitter.emit( - `query.${name}.subscribeToMore.${subscrName}`, - {variables: stmArgs.variables}); // Resolve document fragments before passing it to `apollo-client`. return args.data.subscribeToMore({ ...stmArgs, document: resolveFragments(stmArgs.document), onError: (err) => { - this.context.eventEmitter.emit( - `query.${name}.subscribeToMore.${subscrName}.error`, - {variables: stmArgs.variables}); - if (stmArgs.onErr) { return stmArgs.onErr(err); } From d4bf5fd4239a18dc45e74bb9a9418403ae9268a9 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 25 Jul 2017 21:20:04 +0700 Subject: [PATCH 13/20] Add subscription data events --- client/coral-embed-stream/src/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index f66ca9205..7b9ac6895 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -16,7 +16,7 @@ import EventEmitterProvider from 'coral-framework/components/EventEmitterProvide const store = getStore(); const client = getClient(); -const eventEmitter = new EventEmitter(); +const eventEmitter = new EventEmitter({wildcard: true}); loadPluginsTranslations(); injectPluginsReducers(); @@ -54,11 +54,14 @@ if (!window.opener) { // Add a redux listener to pass through all actions to our event emitter. addListener((action) => { - // Ignore apollo actions. + // Handle apollo actions. if (action.type.startsWith('APOLLO_')) { + if (action.type === 'APOLLO_SUBSCRIPTION_RESULT') { + const {operationName, variables, result: {data}} = action; + eventEmitter.emit(`subscription.${operationName}.data`, {variables, data}); + } return; } - eventEmitter.emit(`action.${action.type}`, {action}); }); } From 8ecf55610c1888a07cf42264f77201e8988c52cf Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 25 Jul 2017 22:28:28 +0700 Subject: [PATCH 14/20] More fetchMore events and more data for graphql events --- client/coral-framework/hocs/withMutation.js | 2 +- client/coral-framework/hocs/withQuery.js | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/client/coral-framework/hocs/withMutation.js b/client/coral-framework/hocs/withMutation.js index 4008e3491..4488b0e7f 100644 --- a/client/coral-framework/hocs/withMutation.js +++ b/client/coral-framework/hocs/withMutation.js @@ -124,7 +124,7 @@ export default (document, config = {}) => (WrappedComponent) => { if (errors) { throw new ResponseErrors(errors); } - this.context.eventEmitter.emit(`mutation.${name}.success`, {variables}); + this.context.eventEmitter.emit(`mutation.${name}.success`, {variables, data: res.data}); return Promise.resolve(res); }) .catch((error) => { diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js index eba931200..adb67b7b3 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -56,7 +56,8 @@ export default (document, config = {}) => (WrappedComponent) => { const status = networkStatusToString(networkStatus); - this.context.eventEmitter.emit(`query.${name}.${status}`, {variables}); + const {root} = separateDataAndRoot(data); + this.context.eventEmitter.emit(`query.${name}.${status}`, {variables, data: root}); } wrappedConfig = { @@ -86,13 +87,25 @@ export default (document, config = {}) => (WrappedComponent) => { fetchMore: (lmArgs) => { const fetchName = getDefinitionName(lmArgs.query); this.context.eventEmitter.emit( - `query.${name}.fetchMore.${fetchName}`, + `query.${name}.fetchMore.${fetchName}.begin`, {variables: lmArgs.variables}); // Resolve document fragments before passing it to `apollo-client`. return args.data.fetchMore({ ...lmArgs, query: resolveFragments(lmArgs.query), + }) + .then((res) => { + this.context.eventEmitter.emit( + `query.${name}.fetchMore.${fetchName}.success`, + {variables: lmArgs.variables, data: res.data}); + return Promise.resolve(res); + }) + .catch((err) => { + this.context.eventEmitter.emit( + `query.${name}.fetchMore.${fetchName}.error`, + {variables: lmArgs.variables, error: err}); + throw err; }); }, }, From 1473c778a56ffe77cec73a5dd487ba5daaf99aa7 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 25 Jul 2017 23:26:23 +0700 Subject: [PATCH 15/20] Refactor listener --- client/coral-admin/src/index.js | 2 ++ client/coral-embed-stream/src/index.js | 14 ++------------ client/coral-framework/services/events.js | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 client/coral-framework/services/events.js diff --git a/client/coral-admin/src/index.js b/client/coral-admin/src/index.js index e50d29408..42fa3705f 100644 --- a/client/coral-admin/src/index.js +++ b/client/coral-admin/src/index.js @@ -16,6 +16,8 @@ import {loadPluginsTranslations, injectPluginsReducers} from 'coral-framework/he const eventEmitter = new EventEmitter(); +// TODO: pass redux actions through the emitter. + loadPluginsTranslations(); injectPluginsReducers(); smoothscroll.polyfill(); diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 7b9ac6895..c40df74f1 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -7,6 +7,7 @@ import './graphql'; import {addExternalConfig} from 'coral-embed-stream/src/actions/config'; import {getStore, injectReducers, addListener} from 'coral-framework/services/store'; import {getClient} from 'coral-framework/services/client'; +import {createReduxEmitter} from 'coral-framework/services/events'; import pym from 'coral-framework/services/pym'; import AppRouter from './AppRouter'; import {loadPluginsTranslations, injectPluginsReducers} from 'coral-framework/helpers/plugins'; @@ -52,18 +53,7 @@ if (!window.opener) { }); // Add a redux listener to pass through all actions to our event emitter. - addListener((action) => { - - // Handle apollo actions. - if (action.type.startsWith('APOLLO_')) { - if (action.type === 'APOLLO_SUBSCRIPTION_RESULT') { - const {operationName, variables, result: {data}} = action; - eventEmitter.emit(`subscription.${operationName}.data`, {variables, data}); - } - return; - } - eventEmitter.emit(`action.${action.type}`, {action}); - }); + addListener(createReduxEmitter(eventEmitter)); } render( diff --git a/client/coral-framework/services/events.js b/client/coral-framework/services/events.js new file mode 100644 index 000000000..a1a2f4448 --- /dev/null +++ b/client/coral-framework/services/events.js @@ -0,0 +1,14 @@ +export function createReduxEmitter(eventEmitter) { + return (action) => { + + // Handle apollo actions. + if (action.type.startsWith('APOLLO_')) { + if (action.type === 'APOLLO_SUBSCRIPTION_RESULT') { + const {operationName, variables, result: {data}} = action; + eventEmitter.emit(`subscription.${operationName}.data`, {variables, data}); + } + return; + } + eventEmitter.emit(`action.${action.type}`, {action}); + }; +} From 48dadd857126becb5e9148767a3b0aafba9c7c0e Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 25 Jul 2017 14:00:30 -0300 Subject: [PATCH 16/20] Viewing Options for mobile --- .../src/components/Stream.css | 4 ++-- client/coral-ui/components/Tab.css | 10 ++++++++ .../client/components/ViewingOptions.css | 24 +++++++++++++++++++ .../client/components/ViewingOptions.js | 7 ++++-- .../client/index.js | 4 +++- .../client/translations.yml | 4 ++++ 6 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 plugins/coral-plugin-viewing-options/client/translations.yml diff --git a/client/coral-embed-stream/src/components/Stream.css b/client/coral-embed-stream/src/components/Stream.css index 8c5a4250b..23f620157 100644 --- a/client/coral-embed-stream/src/components/Stream.css +++ b/client/coral-embed-stream/src/components/Stream.css @@ -14,7 +14,7 @@ .filterWrapper { position: absolute; right: 0; - margin-top: 6px; + margin-top: 4px; } .highlightedContainer { @@ -26,4 +26,4 @@ margin-top: 28px; padding-bottom: 50px; min-height: 600px; -} +} \ No newline at end of file diff --git a/client/coral-ui/components/Tab.css b/client/coral-ui/components/Tab.css index b3c026b3d..fc38d192d 100644 --- a/client/coral-ui/components/Tab.css +++ b/client/coral-ui/components/Tab.css @@ -68,3 +68,13 @@ pointer-events: none; } + +@custom-media --small-viewport (max-width: 320px); + +@media (--small-viewport) { + .buttonSub, .buttonSubActive, .buttonSubActive:hover, .buttonSubActive:focus { + font-size: 0.96em; + padding-right: 5px; + padding-right: 5px; + } +} diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css index 81d373b95..607ddc303 100644 --- a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css +++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css @@ -29,3 +29,27 @@ font-size: 14px; vertical-align: middle; } + + +@custom-media --small-viewport (max-width: 425px); + +.filterText { + display: inline-block; +} + +.filterIcon { + vertical-align: middle; + display: none; +} + +@media (--small-viewport) { + .filterText { + display: none; + } + .filterIcon { + display: inline-block; + } +} + + + diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js index 87680b370..13b3094a1 100644 --- a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js +++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js @@ -1,8 +1,9 @@ import React from 'react'; import cn from 'classnames'; import styles from './ViewingOptions.css'; -import {Slot, ClickOutside} from 'plugin-api/beta/client/components'; +import {t} from 'plugin-api/beta/client/services'; import {Icon} from 'plugin-api/beta/client/components/ui'; +import {Slot, ClickOutside} from 'plugin-api/beta/client/components'; const ViewingOptions = (props) => { const toggleOpen = () => { @@ -23,7 +24,9 @@ const ViewingOptions = (props) => {
-
diff --git a/plugins/coral-plugin-viewing-options/client/index.js b/plugins/coral-plugin-viewing-options/client/index.js index 4e9001c1b..464a38d7d 100644 --- a/plugins/coral-plugin-viewing-options/client/index.js +++ b/plugins/coral-plugin-viewing-options/client/index.js @@ -1,9 +1,11 @@ import ViewingOptions from './containers/ViewingOptions'; import reducer from './reducer'; +import translations from './translations.yml'; export default { reducer, slots: { streamFilter: [ViewingOptions] - } + }, + translations }; diff --git a/plugins/coral-plugin-viewing-options/client/translations.yml b/plugins/coral-plugin-viewing-options/client/translations.yml new file mode 100644 index 000000000..6ab31fa1b --- /dev/null +++ b/plugins/coral-plugin-viewing-options/client/translations.yml @@ -0,0 +1,4 @@ +en: + viewing_options: "Viewing Options" +es: + viewing_options: "Opciones de visualización" \ No newline at end of file From 9aa5c1ec463aa3f16519fe718fd37f5e195eaaa6 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 25 Jul 2017 14:04:13 -0300 Subject: [PATCH 17/20] Viewing Options for mobile --- .../client/components/ViewingOptions.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css index 607ddc303..c51d87f11 100644 --- a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css +++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css @@ -5,6 +5,10 @@ composes: buttonReset from "coral-framework/styles/reset.css"; } +.button:hover { + cursor: pointer; +} + .list { background: white; position: absolute; From 17e6064b9ea790bd8d8a99fdbcf230ea28bca2de Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 25 Jul 2017 14:15:52 -0300 Subject: [PATCH 18/20] Adding react touch events --- .../client/components/Tag.js | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/plugins/talk-plugin-featured-comments/client/components/Tag.js b/plugins/talk-plugin-featured-comments/client/components/Tag.js index 129e07851..ee2d13b19 100644 --- a/plugins/talk-plugin-featured-comments/client/components/Tag.js +++ b/plugins/talk-plugin-featured-comments/client/components/Tag.js @@ -4,7 +4,6 @@ import styles from './Tag.css'; import Tooltip from './Tooltip'; import {t} from 'plugin-api/beta/client/services'; import {isTagged} from 'plugin-api/beta/client/utils'; -import bowser from 'bowser'; export default class Tag extends React.Component { constructor() { @@ -16,27 +15,15 @@ export default class Tag extends React.Component { } - componentDidMount() { - if (bowser.mobile) { - this.tagEl.addEventListener('touchstart', this.showTooltip); - this.tagEl.addEventListener('touchend', this.hideTooltip); - } - } - - componentWillUnmount() { - if (bowser.mobile) { - this.tagEl.removeEventListener('touchstart', this.showTooltip); - this.tagEl.removeEventListener('touchend', this.hideTooltip); - } - } - - showTooltip = () => { + showTooltip = e => { + e.preventDefault(); this.setState({ tooltip: true }); } - hideTooltip = () => { + hideTooltip = (e) => { + e.preventDefault(); this.setState({ tooltip: false }); @@ -45,10 +32,9 @@ export default class Tag extends React.Component { render() { const {tooltip} = this.state; return( -
this.tagEl = ref} - onMouseEnter={this.showTooltip} - onMouseLeave={this.hideTooltip} - className={styles.noSelect }> +
{ isTagged(this.props.comment.tags, 'FEATURED') ? ( Date: Tue, 25 Jul 2017 14:18:40 -0300 Subject: [PATCH 19/20] deleting unused utils --- plugins/talk-plugin-featured-comments/client/utils/index.js | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 plugins/talk-plugin-featured-comments/client/utils/index.js diff --git a/plugins/talk-plugin-featured-comments/client/utils/index.js b/plugins/talk-plugin-featured-comments/client/utils/index.js deleted file mode 100644 index 7d7d80969..000000000 --- a/plugins/talk-plugin-featured-comments/client/utils/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export const isTouchDevice = () => { - return (('ontouchstart' in window) - || (navigator.MaxTouchPoints > 0) - || (navigator.msMaxTouchPoints > 0)); -}; From 21d19d170dfcd58523b569dd5d2a930d73473b9a Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 26 Jul 2017 00:49:16 +0700 Subject: [PATCH 20/20] Exclude generateIntrospectionResult from dockerignore --- .dockerignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index e413ac5ea..80bb20e2c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,9 +1,10 @@ # excluded because we'll likely need to rebuild this. node_modules -# scripts are used during development and testing, not +# most scripts are used during development and testing, not # production. scripts +!scripts/generateIntrospectionResult.js # documentation should not be visable in production. docs