From e713b39846d6cb73c97acb89e090bc5982c5324f Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 09:17:30 -0300 Subject: [PATCH 001/136] clearer api - wip debug tools --- .../coral-embed-stream/src/tabs/stream/containers/Stream.js | 2 +- client/coral-framework/services/plugins.js | 6 ++++-- views/article.ejs | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 191490914..0f516f935 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -465,7 +465,7 @@ const mapStateToProps = state => ({ activeStreamTab: state.stream.activeTab, previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames, - pluginConfig: state.config.plugin_config, + pluginConfig: state.config.plugins_config, sortOrder: state.stream.sortOrder, sortBy: state.stream.sortBy, }); diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 222cc1b42..5afcd3f78 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -84,7 +84,8 @@ class PluginsService { * query datas are only passed to the component if it is defined in `component.fragments`. */ getSlotComponentProps(component, reduxState, props, queryData) { - const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig; + const pluginConfig = + get(reduxState, 'config.plugins_config') || emptyConfig; return { ...props, config: pluginConfig, @@ -98,7 +99,8 @@ class PluginsService { * Returns React Elements for given slot. */ getSlotElements(slot, reduxState, props = {}, queryData = {}, options = {}) { - const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig; + const pluginConfig = + get(reduxState, 'config.plugins_config') || emptyConfig; const { slotSize = 0 } = options; const isDisabled = component => { diff --git a/views/article.ejs b/views/article.ejs index 48d361d38..9bffc6451 100644 --- a/views/article.ejs +++ b/views/article.ejs @@ -42,7 +42,7 @@ * }); * }, */ - plugin_config: { + plugins_config: { /** * You can disable rendering slot components of a plugin by doing: * @@ -51,7 +51,7 @@ * }, */ test: 'data', - debug: false + debug: true } }) "> From 6200ba39c2985c1ae3f02a94376c54b3bfaa2270 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 10:21:42 -0300 Subject: [PATCH 002/136] Adding debug tools --- .../src/tabs/stream/containers/Stream.js | 2 +- client/coral-framework/components/Slot.css | 28 ++++++++++++++++++- client/coral-framework/components/Slot.js | 15 ++++++++-- client/coral-framework/services/plugins.js | 14 +++++----- plugin-api/beta/client/selectors/index.js | 2 +- 5 files changed, 49 insertions(+), 12 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 0f516f935..8515a2db4 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -465,7 +465,7 @@ const mapStateToProps = state => ({ activeStreamTab: state.stream.activeTab, previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames, - pluginConfig: state.config.plugins_config, + pluginsConfig: state.config.plugins_config, sortOrder: state.stream.sortOrder, sortBy: state.stream.sortBy, }); diff --git a/client/coral-framework/components/Slot.css b/client/coral-framework/components/Slot.css index 6a95d79ad..ea3a88fea 100644 --- a/client/coral-framework/components/Slot.css +++ b/client/coral-framework/components/Slot.css @@ -3,5 +3,31 @@ } .debug { - background-color: coral; + background-color: #e2e2e2; + border-style: dotted solid; + border-width: 2px; + border: dotted 2px coral; + padding: 2px; + margin: 1px; + position: relative; +} + +.debug::before { + content: "•slot: " attr(data-slot-name) "\A" "•classname: " attr(data-slot-classname); + display: inline-block; + position: absolute; + bottom: 50%; + background: #000; + color: #FFF; + padding: 5px; + border-radius: 5px; + opacity:0; transition:0.3s; overflow:hidden; + pointer-events: none; + z-index: 999!important; + white-space: pre-wrap; +} + +.debug:hover::before { + opacity:1; + bottom: 100%; } \ No newline at end of file diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 952844ed0..ca10d99c0 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -72,7 +72,7 @@ class Slot extends React.Component { } = this.props; const { plugins } = this.context; let children = this.getChildren(); - const pluginConfig = + const pluginsConfig = get(reduxState, 'config.plugins_config') || emptyConfig; if (children.length === 0 && DefaultComponent) { const props = plugins.getSlotComponentProps( @@ -88,13 +88,24 @@ class Slot extends React.Component { children = children.map(childFactory); } + const debugProps = pluginsConfig.debug + ? { + 'data-slot-name': fill, + 'data-slot-classname': `talk-slot-${kebabCase(fill)}`, + } + : {}; + return ( {children} diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 5afcd3f78..f0e6ff304 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -10,7 +10,7 @@ import get from 'lodash/get'; import { getDisplayName } from 'coral-framework/helpers/hoc'; import camelize from '../helpers/camelize'; -// This is returned for pluginConfig when it is empty. +// This is returned for pluginsConfig when it is empty. const emptyConfig = {}; // Memoize the warnings so we only show them once. @@ -84,11 +84,11 @@ class PluginsService { * query datas are only passed to the component if it is defined in `component.fragments`. */ getSlotComponentProps(component, reduxState, props, queryData) { - const pluginConfig = + const pluginsConfig = get(reduxState, 'config.plugins_config') || emptyConfig; return { ...props, - config: pluginConfig, + config: pluginsConfig, ...(component.fragments ? pick(queryData, Object.keys(component.fragments)) : withWarnings(component, queryData)), @@ -99,15 +99,15 @@ class PluginsService { * Returns React Elements for given slot. */ getSlotElements(slot, reduxState, props = {}, queryData = {}, options = {}) { - const pluginConfig = + const pluginsConfig = get(reduxState, 'config.plugins_config') || emptyConfig; const { slotSize = 0 } = options; const isDisabled = component => { if ( - pluginConfig && - pluginConfig[component.talkPluginName] && - pluginConfig[component.talkPluginName].disable_components + pluginsConfig && + pluginsConfig[component.talkPluginName] && + pluginsConfig[component.talkPluginName].disable_components ) { return true; } diff --git a/plugin-api/beta/client/selectors/index.js b/plugin-api/beta/client/selectors/index.js index 97e4623e4..c7951ca56 100644 --- a/plugin-api/beta/client/selectors/index.js +++ b/plugin-api/beta/client/selectors/index.js @@ -1 +1 @@ -export const pluginConfigSelector = state => state.config.pluginConfig; +export const pluginsConfigSelector = state => state.config.pluginsConfig; From 68fb51122350ec88fc312d66a38373449373997b Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 13:41:00 -0300 Subject: [PATCH 003/136] desc styling --- client/coral-framework/components/Slot.css | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/client/coral-framework/components/Slot.css b/client/coral-framework/components/Slot.css index ea3a88fea..69bfe937e 100644 --- a/client/coral-framework/components/Slot.css +++ b/client/coral-framework/components/Slot.css @@ -13,21 +13,26 @@ } .debug::before { - content: "•slot: " attr(data-slot-name) "\A" "•classname: " attr(data-slot-classname); + content: attr(data-slot-name); display: inline-block; position: absolute; - bottom: 50%; + background: #000; color: #FFF; padding: 5px; border-radius: 5px; - opacity:0; transition:0.3s; overflow:hidden; + opacity: 0; + transition: 0.3s; + overflow: hidden; pointer-events: none; z-index: 999!important; white-space: pre-wrap; + min-height: 16px; + top: 50%; + left: 0; } .debug:hover::before { - opacity:1; - bottom: 100%; + opacity: 1; + top: 100%; } \ No newline at end of file From 77be539710ebc71b60ee2dabd255f314eb9a4f20 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 13:49:35 -0300 Subject: [PATCH 004/136] Adding it to the docs, not sure if it should live there. --- client/coral-framework/components/Slot.js | 1 - docs/_docs/04-05-talk-slots-and-plugins-ES.md | 0 2 files changed, 1 deletion(-) create mode 100644 docs/_docs/04-05-talk-slots-and-plugins-ES.md diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index ca10d99c0..18d3163e0 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -91,7 +91,6 @@ class Slot extends React.Component { const debugProps = pluginsConfig.debug ? { 'data-slot-name': fill, - 'data-slot-classname': `talk-slot-${kebabCase(fill)}`, } : {}; diff --git a/docs/_docs/04-05-talk-slots-and-plugins-ES.md b/docs/_docs/04-05-talk-slots-and-plugins-ES.md new file mode 100644 index 000000000..e69de29bb From 5dbbf80068a6d9da3a60222c1b03f822beb3f601 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 14:27:41 -0300 Subject: [PATCH 005/136] Adding it to the docs, not sure if it should live there. --- docs/_docs/04-05-talk-slots-and-plugins-ES.md | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/docs/_docs/04-05-talk-slots-and-plugins-ES.md b/docs/_docs/04-05-talk-slots-and-plugins-ES.md index e69de29bb..846ccd033 100644 --- a/docs/_docs/04-05-talk-slots-and-plugins-ES.md +++ b/docs/_docs/04-05-talk-slots-and-plugins-ES.md @@ -0,0 +1,157 @@ +# Plugins y Slots +Los *Slots* son una parte muy importante para crear plugins. Estos nos permiten inyectar nuestros plugins en la interfaz visual de Talk. + +Talk por defecto muestra varios plugins - Observemos el archivo `plugins.default.json` : + +```json +{ + "server": [ + "talk-plugin-auth", + "talk-plugin-featured-comments", + "talk-plugin-offtopic", + "talk-plugin-respect" + ], + "client": [ + "talk-plugin-auth", + "talk-plugin-author-menu", + "talk-plugin-comment-content", + "talk-plugin-featured-comments", + "talk-plugin-flag-details", + "talk-plugin-ignore-user", + "talk-plugin-member-since", + "talk-plugin-moderation-actions", + "talk-plugin-offtopic", + "talk-plugin-permalink", + "talk-plugin-respect", + "talk-plugin-sort-most-replied", + "talk-plugin-sort-most-respected", + "talk-plugin-sort-newest", + "talk-plugin-sort-oldest", + "talk-plugin-viewing-options", + "talk-plugin-profile-settings" + ] +} +``` + +La parte que nos interesa es `client`, ya que estos son los plugins que van a utilizar los *slots* para meterse en distintas partes de Talk. + +Por ejemplo: Si observamos el plugin `talk-plugin-respect`, que está dentro de la carpeta `plugins` vamos a notar que el `client/index.js` luce asi: + +```js +import RespectButton from './RespectButton'; +import translations from './translations.yml'; + +export default { + translations, + slots: { + commentReactions: [RespectButton], + }, +}; + +``` + +Dentro de la propiedad `slots` especificamos qué *slots* utilizará el plugin. Ahí estamos diciendo que el componente `RespectButton` se incrustará en el slot `commentReactions`. + +Los slots reciben un /Array/ de componentes. Es decir, puede ser uno o varios. + +### Anatomía del component Slot +En el core de Talk tenemos 32 slots declarados. El componente Slot tiene una propiedad `fill` donde establecemos el nombre del slot. Una declaración de un `` luce así: + +```js + +``` + +Esto probablemente no lo utilices para desarrollar plugins, pero sí para buscar donde se va a embeber tu plugin. + +### Lista de Slots + +* `adminCommentDetailArea` +* `adminCommentMoreDetails` +* `adminCommentLabels` +* `adminModerationSettings` +* `adminStreamSettings` +* `adminTechSettings` +* `adminCommentInfoBar` +* `adminCommentContent` +* `adminSideActions` +* `adminModeration` +* `adminModerationIndicator` + +* `commentInputDetailArea` +* `commentAvatar` +* `commentAuthorName` +* `commentAuthorTags` +* `commentTimestamp` +* `commentInfoBar` +* `commentContent` +* `commentReactions` +* `commentActions` +* `commentInputArea` + +* `draftArea` +* `streamSettings` +* `historyCommentTimestamp` +* `profileSections` +* `embed` +* `stream` +* `streamFilter` +* `streamQuestionArea` +* `login` +* `userProfile` +* `userDetailCommentContent` + +### Cómo sé donde colocar mi plugin? + +Lo primero que debemos pensar es a qué componente afectará la funcionalidad del plugin. Por ejemplo, si queremos agregar funcionalidad a todos los comentarios que se renderizan en la lista total de comentarios, esto tendrá que ver con el componente `Comment`. + +Los slots habilitados para agregar funcionalidad a los comentarios tienen un prefix `comment` como el `commentContent` o el `commentReactions`, que vimos previamente. + +### Deshabilitando plugins por configuración + +Los plugins se eliminan borrando su entrada en el `plugins.json`. Pero si por alguna razón necesitamos hacerlo por configuración, podemos hacerlo. + +Dentro de `views/article.ejs` embebemos Talk y notarás que podemos envialarle un objeto de configuración. Para deshabilitar visualmente a los plugins podemos utilizar la propiedad `disable_components` en `true`. y se utiliza de la siguiente forma: + +```js +plugins_config: { + 'talk-plugin-love': { + disable_components: true, + }, +} +``` + +### Enviando información a los slots / plugins + +Dentro de `plugins_config` podemos pasar todas las propiedades que queramos y los plugins las recibirán. + +Por ejemplo: Si enviamos esto +```js +plugins_config: { + test: 'data' +} +``` + +El plugin recibirá por props un objeto config con las propiedades que le pasamos. Haciendo un console.log de `this.props` verás: + +```js +config: {test: 'data'} +``` + +### Debug Slots + +Podés debuggear Slots / Plugins sólo con pasar la propiedad `debug` con valor `true`. + +```js +plugins_config: { + debug: true +} +``` + +Esto mostrará todos los slots disponibles en la UI y su nombre si pasas el mouse sobre ellos. + +### Slot ClassNames +Los slots autogeneran sus clases con el prefijo `talk-slot-` seguido del nombre del slot en kebab case. +Por ejemplo, la clase autogenerada del slot `commentContent` es `talk-slot-comment-content`. \ No newline at end of file From b80184d9ee05aa7ffa0f684f312c3f02b145cfb7 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 12 Mar 2018 14:29:18 -0300 Subject: [PATCH 006/136] set debug to false --- views/article.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/article.ejs b/views/article.ejs index 9bffc6451..896be9730 100644 --- a/views/article.ejs +++ b/views/article.ejs @@ -51,7 +51,7 @@ * }, */ test: 'data', - debug: true + debug: false } }) "> From 4f844dfd58510abe7604360bcda8c5b9dba1f88c Mon Sep 17 00:00:00 2001 From: okbel Date: Wed, 14 Mar 2018 22:38:27 -0300 Subject: [PATCH 007/136] Adding deprecation notice --- .../src/tabs/stream/containers/Stream.js | 2 +- client/coral-framework/components/Slot.js | 12 +++++++++++- client/coral-framework/services/plugins.js | 10 +++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 8515a2db4..9b12349c5 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -465,7 +465,7 @@ const mapStateToProps = state => ({ activeStreamTab: state.stream.activeTab, previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames, - pluginsConfig: state.config.plugins_config, + pluginsConfig: state.config.plugins_config || state.config.plugin_config, sortOrder: state.stream.sortOrder, sortBy: state.stream.sortBy, }); diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 18d3163e0..25e6521eb 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -72,8 +72,18 @@ class Slot extends React.Component { } = this.props; const { plugins } = this.context; let children = this.getChildren(); + + if (!!get(reduxState, 'config.plugin_config')) { + console.warn( + `deprecation warning: config.plugin_config will be phased out soon, please replace calls from config.plugin_config to config.plugins_config` + ); + } + const pluginsConfig = - get(reduxState, 'config.plugins_config') || emptyConfig; + get(reduxState, 'config.plugins_config') || + get(reduxState, 'config.plugin_config') || + emptyConfig; + if (children.length === 0 && DefaultComponent) { const props = plugins.getSlotComponentProps( DefaultComponent, diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index f0e6ff304..5d2c88e55 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -84,8 +84,16 @@ class PluginsService { * query datas are only passed to the component if it is defined in `component.fragments`. */ getSlotComponentProps(component, reduxState, props, queryData) { + if (!!get(reduxState, 'config.plugin_config')) { + console.warn( + `deprecation warning: config.plugin_config will be phased out soon, please replace calls from config.plugin_config to config.plugins_config` + ); + } + const pluginsConfig = - get(reduxState, 'config.plugins_config') || emptyConfig; + get(reduxState, 'config.plugins_config') || + get(reduxState, 'config.plugin_config') || + emptyConfig; return { ...props, config: pluginsConfig, From 7d7e3f60d383ff27df4a878fd5b548c942c86ec7 Mon Sep 17 00:00:00 2001 From: okbel Date: Wed, 14 Mar 2018 23:33:06 -0300 Subject: [PATCH 008/136] show dep message only once --- client/coral-framework/components/Slot.js | 6 ------ client/coral-framework/services/plugins.js | 8 +++++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 25e6521eb..a2def60fa 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -73,12 +73,6 @@ class Slot extends React.Component { const { plugins } = this.context; let children = this.getChildren(); - if (!!get(reduxState, 'config.plugin_config')) { - console.warn( - `deprecation warning: config.plugin_config will be phased out soon, please replace calls from config.plugin_config to config.plugins_config` - ); - } - const pluginsConfig = get(reduxState, 'config.plugins_config') || get(reduxState, 'config.plugin_config') || diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 5d2c88e55..fb8ae045d 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -83,11 +83,17 @@ class PluginsService { * getSlotComponentProps calculate the props we would pass to the slot component. * query datas are only passed to the component if it is defined in `component.fragments`. */ + showPluginsConfigWarning = true; + getSlotComponentProps(component, reduxState, props, queryData) { - if (!!get(reduxState, 'config.plugin_config')) { + if ( + !!get(reduxState, 'config.plugin_config') && + this.showPluginsConfigWarning + ) { console.warn( `deprecation warning: config.plugin_config will be phased out soon, please replace calls from config.plugin_config to config.plugins_config` ); + this.showPluginsConfigWarning = false; } const pluginsConfig = From 3b2a9e5c7501ea575c6f0b3c73b7f6fdd728c6aa Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 15 Mar 2018 15:03:01 -0600 Subject: [PATCH 009/136] improved context perf --- graph/context.js | 36 ++++++++++++++++++++++++++++++++---- services/logging.js | 20 ++++++++++---------- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/graph/context.js b/graph/context.js index 52bb7fdfc..dcbbc5f6b 100644 --- a/graph/context.js +++ b/graph/context.js @@ -42,6 +42,32 @@ const decorateContextPlugins = (context, contextPlugins) => { ); }; +/** + * Some pieces of the Context are quite complex to setup, using multiple merges + * and other lodash functions. This proxies that access such that it is only + * loaded if it is used. Helpful for a query that only uses a loader, and not a + * mutator. + * + * @param {Object} ctx the graph proxy + * @param {Function} loader the loadable component that should be proxied + */ +const proxyContextLoader = (ctx, loader) => + new Proxy( + { loaded: false, data: null }, + { + get: (obj, prop) => { + if (obj.loaded) { + return obj.data[prop]; + } + + obj.data = loader(ctx); + obj.loaded = true; + + return obj.data[prop]; + }, + } + ); + /** * Stores the request context. */ @@ -61,16 +87,18 @@ class Context { this.connectors = connectors; // Create the loaders. - this.loaders = loaders(this); + this.loaders = proxyContextLoader(this, loaders); // Create the mutators. - this.mutators = mutators(this); + this.mutators = proxyContextLoader(this, mutators); // Decorate the plugin context. - this.plugins = decorateContextPlugins(this, contextPlugins); + this.plugins = proxyContextLoader(this, () => + decorateContextPlugins(this, contextPlugins) + ); // Bind the publish/subscribe to the context. - this.pubsub = getBroker(); + this.pubsub = proxyContextLoader(this, () => getBroker()); // Bind the parent context. this.parent = ctx; diff --git a/services/logging.js b/services/logging.js index 00e9c523b..47ef93af8 100644 --- a/services/logging.js +++ b/services/logging.js @@ -1,18 +1,18 @@ const { version } = require('../package.json'); const Logger = require('bunyan'); const { LOGGING_LEVEL, REVISION_HASH } = require('../config'); +const logger = new Logger({ + src: true, + name: 'talk', + version, + revision: REVISION_HASH, + level: LOGGING_LEVEL, + serializers: Logger.stdSerializers, +}); // Create the logging instance that all logger's are branched from. function createLogger(name, traceID) { - return new Logger({ - src: true, - name, - traceID, - version, - revision: REVISION_HASH, - level: LOGGING_LEVEL, - serializers: Logger.stdSerializers, - }); + return logger.child({ origin: name, traceID }); } -module.exports = { createLogger }; +module.exports = { logger, createLogger }; From 96dff8ed25c0ab7efe66c0c619c0041e83fbbcdf Mon Sep 17 00:00:00 2001 From: okbel Date: Fri, 16 Mar 2018 20:16:28 -0300 Subject: [PATCH 010/136] wip :0 --- .../coral-embed-stream/src/constants/debug.js | 2 ++ .../coral-embed-stream/src/reducers/debug.js | 20 +++++++++++++++++++ .../coral-embed-stream/src/reducers/index.js | 2 ++ client/coral-embed/src/Stream.js | 4 ++++ client/coral-embed/src/StreamInterface.js | 4 ++++ client/coral-framework/components/Slot.js | 4 +++- client/coral-framework/services/bootstrap.js | 6 ++++++ 7 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 client/coral-embed-stream/src/constants/debug.js create mode 100644 client/coral-embed-stream/src/reducers/debug.js diff --git a/client/coral-embed-stream/src/constants/debug.js b/client/coral-embed-stream/src/constants/debug.js new file mode 100644 index 000000000..d31784af9 --- /dev/null +++ b/client/coral-embed-stream/src/constants/debug.js @@ -0,0 +1,2 @@ +export const ENABLE_PLUGINS_DEBUG = 'ENABLE_PLUGINS_DEBUG'; +export const DISABLE_PLUGINS_DEBUG = 'DISABLE_PLUGINS_DEBUG'; diff --git a/client/coral-embed-stream/src/reducers/debug.js b/client/coral-embed-stream/src/reducers/debug.js new file mode 100644 index 000000000..bcd919ebf --- /dev/null +++ b/client/coral-embed-stream/src/reducers/debug.js @@ -0,0 +1,20 @@ +import * as actions from '../constants/debug'; + +const initialState = { + plugins: false, +}; + +export default function DEBUG(state = initialState, action) { + switch (action.type) { + case actions.ENABLE_PLUGINS_DEBUG: + return { + plugins: true, + }; + case actions.DISABLE_PLUGINS_DEBUG: + return { + plugins: false, + }; + default: + return state; + } +} diff --git a/client/coral-embed-stream/src/reducers/index.js b/client/coral-embed-stream/src/reducers/index.js index 15056d414..a896e64c0 100644 --- a/client/coral-embed-stream/src/reducers/index.js +++ b/client/coral-embed-stream/src/reducers/index.js @@ -3,6 +3,7 @@ import embed from './embed'; import configure from './configure'; import stream from './stream'; import profile from './profile'; +import debug from './debug'; export default { login, @@ -10,4 +11,5 @@ export default { configure, stream, profile, + debug, }; diff --git a/client/coral-embed/src/Stream.js b/client/coral-embed/src/Stream.js index fc8c0d5e7..fffa26aba 100644 --- a/client/coral-embed/src/Stream.js +++ b/client/coral-embed/src/Stream.js @@ -161,6 +161,10 @@ export default class Stream { ); } + dispatch(action) { + this.pym.sendMessage('dispatch', action); + } + login(token) { this.pym.sendMessage('login', token); } diff --git a/client/coral-embed/src/StreamInterface.js b/client/coral-embed/src/StreamInterface.js index 4c6e29970..67e2fff1b 100644 --- a/client/coral-embed/src/StreamInterface.js +++ b/client/coral-embed/src/StreamInterface.js @@ -3,6 +3,10 @@ export default class StreamInterface { this._stream = stream; } + dispatch(action) { + return this._stream.dispatch(action); + } + on(eventName, callback) { return this._stream.emitter.on(eventName, callback); } diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index a2def60fa..4f56f7994 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -69,6 +69,7 @@ class Slot extends React.Component { defaultComponent: DefaultComponent, queryData, fill, + debugPlugins, } = this.props; const { plugins } = this.context; let children = this.getChildren(); @@ -103,7 +104,7 @@ class Slot extends React.Component { className={cn( { [styles.inline]: inline, - [styles.debug]: pluginsConfig.debug, + [styles.debug]: debugPlugins || pluginsConfig.debug, }, className, `talk-slot-${kebabCase(fill)}` @@ -156,6 +157,7 @@ Slot.propTypes = { const mapStateToProps = state => ({ reduxState: state, + debugPlugins: state.debug.plugins, }); export default connect(mapStateToProps, null)(Slot); diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index 691404369..fc06ea441 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -215,6 +215,12 @@ export async function createContext({ pym.onMessage('logout', () => { store.dispatch(logout()); }); + + pym.onMessage('dispatch', action => { + store.dispatch({ + type: action, + }); + }); } const preInitList = []; From a700e91911517debb15f5bcbccd4e89fb4f73e27 Mon Sep 17 00:00:00 2001 From: okbel Date: Sat, 17 Mar 2018 16:26:46 -0300 Subject: [PATCH 011/136] adding debugPlugins --- client/coral-framework/components/Slot.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 4f56f7994..c69b10cf1 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -93,11 +93,12 @@ class Slot extends React.Component { children = children.map(childFactory); } - const debugProps = pluginsConfig.debug - ? { - 'data-slot-name': fill, - } - : {}; + const debugProps = + debugPlugins || pluginsConfig.debug + ? { + 'data-slot-name': fill, + } + : {}; return ( Date: Sat, 17 Mar 2018 16:44:01 -0300 Subject: [PATCH 012/136] refactor due to auth probs --- client/coral-framework/components/Slot.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index c69b10cf1..3719db8c2 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -69,7 +69,7 @@ class Slot extends React.Component { defaultComponent: DefaultComponent, queryData, fill, - debugPlugins, + debug = {}, } = this.props; const { plugins } = this.context; let children = this.getChildren(); @@ -94,7 +94,7 @@ class Slot extends React.Component { } const debugProps = - debugPlugins || pluginsConfig.debug + debug.plugins || pluginsConfig.debug ? { 'data-slot-name': fill, } @@ -105,7 +105,7 @@ class Slot extends React.Component { className={cn( { [styles.inline]: inline, - [styles.debug]: debugPlugins || pluginsConfig.debug, + [styles.debug]: debug.plugins || pluginsConfig.debug, }, className, `talk-slot-${kebabCase(fill)}` @@ -123,7 +123,7 @@ Slot.defaultProps = { }; Slot.propTypes = { - debugPlugins: PropTypes.bool, + debug: PropTypes.object, fill: PropTypes.string.isRequired, inline: PropTypes.bool, className: PropTypes.string, @@ -159,7 +159,7 @@ Slot.propTypes = { const mapStateToProps = state => ({ reduxState: state, - debugPlugins: state.debug.plugins, + debug: state.debug, }); export default connect(mapStateToProps, null)(Slot); From 40782a5d0b6aff92d29e7884ae0b6e08df9f47d2 Mon Sep 17 00:00:00 2001 From: okbel Date: Sat, 17 Mar 2018 18:55:39 -0300 Subject: [PATCH 013/136] Adding off method to the Stream Interface --- client/coral-embed/src/StreamInterface.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/coral-embed/src/StreamInterface.js b/client/coral-embed/src/StreamInterface.js index 67e2fff1b..57339cf90 100644 --- a/client/coral-embed/src/StreamInterface.js +++ b/client/coral-embed/src/StreamInterface.js @@ -11,6 +11,10 @@ export default class StreamInterface { return this._stream.emitter.on(eventName, callback); } + off(eventName, callback) { + return this._stream.emitter.off(eventName, callback); + } + login(token) { return this._stream.login(token); } From 8aa8517d1d17f6e1f791643531c85e1bc58d099e Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 19 Mar 2018 00:29:01 +0100 Subject: [PATCH 014/136] Implement Coral RTE --- .gitignore | 1 + plugins/talk-plugin-rich-text-coral/README.md | 47 ++++++ .../client/.eslintrc.json | 3 + .../client/components/Button.css | 20 +++ .../client/components/Button.js | 29 ++++ .../client/components/CommentContent.js | 23 +++ .../client/components/Editor.css | 12 ++ .../client/components/Editor.js | 137 ++++++++++++++++++ .../client/components/Toolbar.css | 7 + .../client/components/Toolbar.js | 17 +++ .../client/constants.js | 1 + .../client/containers/CommentContent.js | 12 ++ .../client/containers/Editor.js | 12 ++ .../client/index.js | 70 +++++++++ .../client/utils.js | 31 ++++ plugins/talk-plugin-rich-text-coral/index.js | 1 + .../talk-plugin-rich-text-coral/package.json | 12 ++ .../talk-plugin-rich-text/server/config.js | 5 +- yarn.lock | 4 + 19 files changed, 443 insertions(+), 1 deletion(-) create mode 100644 plugins/talk-plugin-rich-text-coral/README.md create mode 100644 plugins/talk-plugin-rich-text-coral/client/.eslintrc.json create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Button.css create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Button.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/CommentContent.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Editor.css create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Editor.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Toolbar.css create mode 100644 plugins/talk-plugin-rich-text-coral/client/components/Toolbar.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/constants.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/containers/CommentContent.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/containers/Editor.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/index.js create mode 100644 plugins/talk-plugin-rich-text-coral/client/utils.js create mode 100644 plugins/talk-plugin-rich-text-coral/index.js create mode 100644 plugins/talk-plugin-rich-text-coral/package.json diff --git a/.gitignore b/.gitignore index 7c0007dc9..230acae6e 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ plugins/* !plugins/talk-plugin-toxic-comments !plugins/talk-plugin-viewing-options !plugins/talk-plugin-rich-text +!plugins/talk-plugin-rich-text-coral !plugins/talk-plugin-rich-text-pell **/node_modules/* diff --git a/plugins/talk-plugin-rich-text-coral/README.md b/plugins/talk-plugin-rich-text-coral/README.md new file mode 100644 index 000000000..f953a65cd --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/README.md @@ -0,0 +1,47 @@ +--- +title: talk-plugin-rich-text-coral +permalink: /plugin/talk-plugin-rich-text-coral/ +layout: plugin +plugin: + name: talk-plugin-rich-text-coral + depends: + - name: talk-plugin-rich-text + provides: + - Client +--- + +Enables rich text support client-side by using a simple RTE. + +## Installation + +Add `"talk-plugin-rich-text-coral"` to the `plugins.json` in your Talk +installation. Remember to add this in the `client` property since this plugin +only covers the client side. To add server support, please use +[talk-plugin-rich-text](/talk/plugin/talk-plugin-rich-text). + +_Note: Ensure that you don't have any other plugins utilizing the +`commentContent` slot, as it would result in duplicate comments._ + +## How does this work? + +This plugin contains 2 important components: + +- The Editor (`./components/Editor.js`) +- The Comment Content Renderer (`./components/CommentContent.js`) + +The editor component uses [contentEditable](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content). + +If you check our `index.js` you will notice that we inject this editor in the +`commentBox` slot. We do this to replace the core comment box with this one. + +Now, in order to render the new styled comments we need a comment renderer. For +this task we will have to replace our core comment renderer by using the +`commentContent` slot. + +If you are not familiar with GraphQL `client/index.js` will look complicated, +but fear not! With those functions we specify what to expect from the server +schema, how to perform optimistic updates and how keep the client store updated +with the latest changes. + +We encourage you to see the files and check how easy is to build plugins! If you +have any feedback, please let us know. diff --git a/plugins/talk-plugin-rich-text-coral/client/.eslintrc.json b/plugins/talk-plugin-rich-text-coral/client/.eslintrc.json new file mode 100644 index 000000000..c8a6db18a --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "@coralproject/eslint-config-talk/client" +} diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Button.css b/plugins/talk-plugin-rich-text-coral/client/components/Button.css new file mode 100644 index 000000000..f9f1ba186 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Button.css @@ -0,0 +1,20 @@ +.button > i { + vertical-align: middle; +} + +.button { + background-color: transparent; + padding: 3px; + border: none; + color: #4e4e4e; + margin-right: 3px; +} + +.button:hover{ + cursor: pointer; + border-radius: 3px; + background-color: #eae8e8; +} +.icon { + font-size: 20px; +} diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Button.js b/plugins/talk-plugin-rich-text-coral/client/components/Button.js new file mode 100644 index 000000000..330e3d993 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Button.js @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './Button.css'; +import { Icon, BareButton } from 'plugin-api/beta/client/components/ui'; +import cn from 'classnames'; + +class Button extends React.Component { + render() { + const { className, icon, title, onClick } = this.props; + return ( + + + + ); + } +} + +Button.propTypes = { + icon: PropTypes.string.isRequired, + className: PropTypes.string, + title: PropTypes.string, + onClick: PropTypes.func, +}; + +export default Button; diff --git a/plugins/talk-plugin-rich-text-coral/client/components/CommentContent.js b/plugins/talk-plugin-rich-text-coral/client/components/CommentContent.js new file mode 100644 index 000000000..a0e045c6f --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/CommentContent.js @@ -0,0 +1,23 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { PLUGIN_NAME } from '../constants'; + +class CommentContent extends React.Component { + render() { + const { comment } = this.props; + return comment.richTextBody ? ( +
+ ) : ( +
{comment.body}
+ ); + } +} + +CommentContent.propTypes = { + comment: PropTypes.object.isRequired, +}; + +export default CommentContent; diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Editor.css b/plugins/talk-plugin-rich-text-coral/client/components/Editor.css new file mode 100644 index 000000000..f102067a5 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Editor.css @@ -0,0 +1,12 @@ +.contentEditable { + background: #fff; + border: solid 1px #bbb; + min-height: 120px; + box-sizing: border-box; + outline: 0; + overflow-y: auto; + width: 100%; + padding: 10px; + font-style: unset; +} + diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Editor.js b/plugins/talk-plugin-rich-text-coral/client/components/Editor.js new file mode 100644 index 000000000..f6491572b --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Editor.js @@ -0,0 +1,137 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './Editor.css'; +import cn from 'classnames'; +import { PLUGIN_NAME } from '../constants'; +import { htmlNormalizer } from '../utils'; +import ContentEditable from 'react-contenteditable'; +import Toolbar from './Toolbar'; +import Button from './Button'; +import bowser from 'bowser'; + +class Editor extends React.Component { + ref = null; + handleRef = ref => (this.ref = ref); + state = { + html: + !this.props.isReply && this.props.comment + ? this.props.comment.richTextBody || this.props.comment.body || '' + : '', + }; + + handleChange = evt => { + const html = evt.target.value; + this.setState({ html }); + this.props.onChange(this.ref.htmlEl.innerText, { + richTextBody: htmlNormalizer(html), + }); + }; + + componentDidMount() { + if (this.props.registerHook) { + this.clearInputHook = this.props.registerHook( + 'postSubmit', + (res, handleBodyChange) => { + this.setState({ html: '' }); + handleBodyChange('', { richTextBody: '' }); + } + ); + } + } + + shouldComponentUpdate(nextProps) { + if (this.props.value !== nextProps.value) { + return false; + } + return true; + } + + componentWillUnmount() { + this.props.unregisterHook(this.clearInputHook); + } + + getCurrentTagName() { + const sel = window.getSelection(); + const range = sel.getRangeAt(0); + if (range.startContainer.nodeName !== '#text') { + return range.startContainer.nodeName; + } + return range.startContainer.parentNode.tagName; + } + + formatBold = () => { + document.execCommand('bold'); + this.ref.htmlEl.focus(); + }; + + formatItalic = () => { + document.execCommand('italic'); + this.ref.htmlEl.focus(); + }; + + formatBlockquote = () => { + const currentTag = this.getCurrentTagName(); + if (currentTag === 'BLOCKQUOTE') { + document.execCommand('outdent'); + } else { + if (bowser.msie) { + document.execCommand('indent'); + } else { + document.execCommand('formatBlock', false, 'blockquote'); + } + } + this.ref.htmlEl.focus(); + }; + + outdentOnEnter = e => { + if (e.key === 'Enter' && !e.shiftKey) { + setTimeout(() => document.execCommand('outdent')); + } + }; + + render() { + const { id } = this.props; + return ( +
+ +
+ ); + } +} + +Editor.propTypes = { + rows: PropTypes.number, // TODO: should not be passed. + id: PropTypes.string, // TODO: should not be passed. + value: PropTypes.string, + placeholder: PropTypes.string, + onChange: PropTypes.func, + disabled: PropTypes.bool, + comment: PropTypes.object, + classNames: PropTypes.object, + registerHook: PropTypes.func, + unregisterHook: PropTypes.func, + isReply: PropTypes.bool, +}; + +export default Editor; diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.css b/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.css new file mode 100644 index 000000000..f4880f432 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.css @@ -0,0 +1,7 @@ +.toolbar { + user-select: none; + padding: 5px 10px; + border-top: 1px solid #bbb; + border-left: 1px solid #bbb; + border-right: 1px solid #bbb; +} diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.js b/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.js new file mode 100644 index 000000000..f3112e813 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/components/Toolbar.js @@ -0,0 +1,17 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './Toolbar.css'; +import cn from 'classnames'; + +class Toolbar extends React.Component { + render() { + const { className, ...rest } = this.props; + return
; + } +} + +Toolbar.propTypes = { + className: PropTypes.string, +}; + +export default Toolbar; diff --git a/plugins/talk-plugin-rich-text-coral/client/constants.js b/plugins/talk-plugin-rich-text-coral/client/constants.js new file mode 100644 index 000000000..40ea81053 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/constants.js @@ -0,0 +1 @@ +export const PLUGIN_NAME = 'talk-plugin-rich-text-coral'; diff --git a/plugins/talk-plugin-rich-text-coral/client/containers/CommentContent.js b/plugins/talk-plugin-rich-text-coral/client/containers/CommentContent.js new file mode 100644 index 000000000..99b65c22c --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/containers/CommentContent.js @@ -0,0 +1,12 @@ +import { gql } from 'react-apollo'; +import { withFragments } from 'plugin-api/beta/client/hocs'; +import CommentContent from '../components/CommentContent'; + +export default withFragments({ + comment: gql` + fragment TalkPluginRichTextCoral_CommentContent_comment on Comment { + body + richTextBody + } + `, +})(CommentContent); diff --git a/plugins/talk-plugin-rich-text-coral/client/containers/Editor.js b/plugins/talk-plugin-rich-text-coral/client/containers/Editor.js new file mode 100644 index 000000000..f748e7344 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/containers/Editor.js @@ -0,0 +1,12 @@ +import { gql } from 'react-apollo'; +import { withFragments } from 'plugin-api/beta/client/hocs'; +import Editor from '../components/Editor'; + +export default withFragments({ + comment: gql` + fragment TalkPluginRichTextCoral_Editor_comment on Comment { + body + richTextBody + } + `, +})(Editor); diff --git a/plugins/talk-plugin-rich-text-coral/client/index.js b/plugins/talk-plugin-rich-text-coral/client/index.js new file mode 100644 index 000000000..8cab68f8d --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/index.js @@ -0,0 +1,70 @@ +import Editor from './containers/Editor'; +import CommentContent from './containers/CommentContent'; +import { gql } from 'react-apollo'; + +export default { + slots: { + draftArea: [Editor], + commentContent: [CommentContent], + adminCommentContent: [CommentContent], + userDetailCommentContent: [CommentContent], + }, + fragments: { + CreateCommentResponse: gql` + fragment TalkRichTextCoral_CreateCommentResponse on CreateCommentResponse { + comment { + richTextBody + } + } + `, + EditCommentResponse: gql` + fragment TalkRichTextCoral_EditCommentResponse on EditCommentResponse { + comment { + richTextBody + } + } + `, + }, + mutations: { + PostComment: ({ variables: { input } }) => { + return { + optimisticResponse: { + createComment: { + comment: { + richTextBody: input.richTextBody, + }, + }, + }, + }; + }, + EditComment: ({ variables: { id, edit } }) => { + return { + optimisticResponse: { + editComment: { + comment: { + richTextBody: edit.richTextBody, + }, + }, + }, + update: proxy => { + const editCommentFragment = gql` + fragment TalkRichTextCoral_EditComment on Comment { + richTextBody + } + `; + + const fragmentId = `Comment_${id}`; + + proxy.writeFragment({ + fragment: editCommentFragment, + id: fragmentId, + data: { + __typename: 'Comment', + richTextBody: edit.richTextBody, + }, + }); + }, + }; + }, + }, +}; diff --git a/plugins/talk-plugin-rich-text-coral/client/utils.js b/plugins/talk-plugin-rich-text-coral/client/utils.js new file mode 100644 index 000000000..a617d618e --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/client/utils.js @@ -0,0 +1,31 @@ +export function htmlNormalizer(htmlInput) { + let str = htmlInput; + // We are normalizing the input from contenteditable of each browser, also removing unnecesary html tags + // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content#Differences_in_markup_generation + + // Old browsers uses `p` normalize to `div` instead. + str = str + .replace(/

/g, '

') // IE and old browsers outputs

instead of

s + .replace(/<\/p>/g, '
'); // IE and old browsers outputs

instead of

s + + // Harmonize all to tag. + str = str + .replace(//g, '') // IE + .replace(/<\/strong>/g, ''); // IE + + // Harmonize all to tag. + str = str + .replace(//g, '') // IE + .replace(/<\/em>/g, ''); // IE + + // Remove first opening tag, otherwise + // with the following transformation below + // we might add an unintended first empty line. + if (str.startsWith('
')) { + str = str.replace('
', ''); + } + + // Normalize
s to
. + // return str.replace(/
/g, '
').replace(/<\/div>/g, ''); + return str; +} diff --git a/plugins/talk-plugin-rich-text-coral/index.js b/plugins/talk-plugin-rich-text-coral/index.js new file mode 100644 index 000000000..f053ebf79 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/index.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/plugins/talk-plugin-rich-text-coral/package.json b/plugins/talk-plugin-rich-text-coral/package.json new file mode 100644 index 000000000..f15b565e7 --- /dev/null +++ b/plugins/talk-plugin-rich-text-coral/package.json @@ -0,0 +1,12 @@ +{ + "name": "@coralproject/talk-plugin-rich-text-coral", + "pluginName": "talk-plugin-rich-text-coral", + "version": "0.0.1", + "description": "Simple Rich Text Editor for Talk", + "main": "index.js", + "author": "The Coral Project Team ", + "license": "Apache-2.0", + "dependencies": { + "react-contenteditable": "^2.0.7" + } +} diff --git a/plugins/talk-plugin-rich-text/server/config.js b/plugins/talk-plugin-rich-text/server/config.js index 8d0fc87d3..347502d08 100644 --- a/plugins/talk-plugin-rich-text/server/config.js +++ b/plugins/talk-plugin-rich-text/server/config.js @@ -13,7 +13,10 @@ const config = { // TODO: move to admin eventually // Super strict rules to make sure users only submit the tags they are allowed - dompurify: { ALLOWED_TAGS: ['b', 'i', 'blockquote', 'br'] }, + dompurify: { + ALLOWED_TAGS: ['b', 'i', 'blockquote', 'br', 'div'], + ALLOWED_ATTR: [], + }, // Secure config for jsdom even when DOMPurify creates a document without a browsing context jsdom: { diff --git a/yarn.lock b/yarn.lock index 135719b51..e8093adf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9142,6 +9142,10 @@ react-apollo@^1.4.12: object-assign "^4.0.1" prop-types "^15.5.8" +react-contenteditable@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/react-contenteditable/-/react-contenteditable-2.0.7.tgz#a8d1c1d7b9a393f336c5ecdb74e5e336d786676b" + react-dom@>=0.14.0: version "16.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" From 3821477ef0114eb16bcb6097af4907c3343d99b2 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 19 Mar 2018 00:32:06 +0100 Subject: [PATCH 015/136] Remove some comments --- .../talk-plugin-rich-text-coral/client/components/Editor.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/talk-plugin-rich-text-coral/client/components/Editor.js b/plugins/talk-plugin-rich-text-coral/client/components/Editor.js index f6491572b..b22fd0df4 100644 --- a/plugins/talk-plugin-rich-text-coral/client/components/Editor.js +++ b/plugins/talk-plugin-rich-text-coral/client/components/Editor.js @@ -111,9 +111,9 @@ class Editor extends React.Component { className={styles.contentEditable} id={id} ref={this.handleRef} - html={this.state.html} // innerHTML of the editable div - disabled={false} // use true to disable edition - onChange={this.handleChange} // handle innerHTML change + html={this.state.html} + disabled={false} + onChange={this.handleChange} />
); From 346a099c1b9d05d2ecedaf6162977ed82617d225 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 19 Mar 2018 15:10:14 -0300 Subject: [PATCH 016/136] changes - ready to be tested --- client/coral-embed-stream/src/tabs/stream/containers/Stream.js | 2 ++ client/coral-framework/components/Slot.js | 2 ++ client/coral-framework/services/plugins.js | 1 + 3 files changed, 5 insertions(+) diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 9b12349c5..1eae7d816 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -465,6 +465,8 @@ const mapStateToProps = state => ({ activeStreamTab: state.stream.activeTab, previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames, + + // @Deprecated plugin_config pluginsConfig: state.config.plugins_config || state.config.plugin_config, sortOrder: state.stream.sortOrder, sortBy: state.stream.sortBy, diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index 3719db8c2..b4b0a725a 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -74,6 +74,8 @@ class Slot extends React.Component { const { plugins } = this.context; let children = this.getChildren(); + // @Deprecated plugin_config + const pluginsConfig = get(reduxState, 'config.plugins_config') || get(reduxState, 'config.plugin_config') || diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index fb8ae045d..7f1323587 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -87,6 +87,7 @@ class PluginsService { getSlotComponentProps(component, reduxState, props, queryData) { if ( + process.env.NODE_ENV !== 'production' && !!get(reduxState, 'config.plugin_config') && this.showPluginsConfigWarning ) { From eb331551c5675968c90e56702c7fb70b470008b7 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 19 Mar 2018 15:14:48 -0300 Subject: [PATCH 017/136] support for both --- plugin-api/beta/client/selectors/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin-api/beta/client/selectors/index.js b/plugin-api/beta/client/selectors/index.js index c7951ca56..5ae41f6e0 100644 --- a/plugin-api/beta/client/selectors/index.js +++ b/plugin-api/beta/client/selectors/index.js @@ -1 +1,3 @@ -export const pluginsConfigSelector = state => state.config.pluginsConfig; +// @Deprecated plugin_config +export const pluginsConfigSelector = state => + state.config.plugins_config || state.config.plugin_config; From 8a2fa2432f611abed0a0ec0282094544d2d65326 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 19 Mar 2018 15:26:18 -0300 Subject: [PATCH 018/136] @Deprecated plugin_config --- client/coral-framework/services/plugins.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 7f1323587..fbd2ae21f 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -97,6 +97,7 @@ class PluginsService { this.showPluginsConfigWarning = false; } + // @Deprecated plugin_config const pluginsConfig = get(reduxState, 'config.plugins_config') || get(reduxState, 'config.plugin_config') || From 497225139dd1a7bf09d4d82f6fe3120dca7a5b55 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 20 Mar 2018 10:14:41 -0400 Subject: [PATCH 019/136] Adding upvote and downvote plugins and sorting plugins for each --- .gitignore | 5 +- .../client/.eslintrc.json | 23 ++++++++ .../client/components/DownvoteButton.css | 39 +++++++++++++ .../client/components/DownvoteButton.js | 56 +++++++++++++++++++ .../client/components/Icon.js | 9 +++ plugins/talk-plugin-downvote/client/index.js | 25 +++++++++ plugins/talk-plugin-downvote/index.js | 2 + .../client/.eslintrc.json | 3 + .../client/index.js | 19 +++++++ .../client/translations.yml | 3 + .../talk-plugin-sort-most-downvoted/index.js | 1 + .../client/.eslintrc.json | 3 + .../client/index.js | 19 +++++++ .../client/translations.yml | 3 + .../talk-plugin-sort-most-upvoted/index.js | 1 + .../talk-plugin-upvote/client/.eslintrc.json | 23 ++++++++ .../client/components/Icon.js | 6 ++ .../client/components/UpvoteButton.css | 39 +++++++++++++ .../client/components/UpvoteButton.js | 54 ++++++++++++++++++ plugins/talk-plugin-upvote/client/index.js | 25 +++++++++ plugins/talk-plugin-upvote/index.js | 2 + 21 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 plugins/talk-plugin-downvote/client/.eslintrc.json create mode 100644 plugins/talk-plugin-downvote/client/components/DownvoteButton.css create mode 100644 plugins/talk-plugin-downvote/client/components/DownvoteButton.js create mode 100644 plugins/talk-plugin-downvote/client/components/Icon.js create mode 100644 plugins/talk-plugin-downvote/client/index.js create mode 100644 plugins/talk-plugin-downvote/index.js create mode 100644 plugins/talk-plugin-sort-most-downvoted/client/.eslintrc.json create mode 100644 plugins/talk-plugin-sort-most-downvoted/client/index.js create mode 100644 plugins/talk-plugin-sort-most-downvoted/client/translations.yml create mode 100644 plugins/talk-plugin-sort-most-downvoted/index.js create mode 100644 plugins/talk-plugin-sort-most-upvoted/client/.eslintrc.json create mode 100644 plugins/talk-plugin-sort-most-upvoted/client/index.js create mode 100644 plugins/talk-plugin-sort-most-upvoted/client/translations.yml create mode 100644 plugins/talk-plugin-sort-most-upvoted/index.js create mode 100644 plugins/talk-plugin-upvote/client/.eslintrc.json create mode 100644 plugins/talk-plugin-upvote/client/components/Icon.js create mode 100644 plugins/talk-plugin-upvote/client/components/UpvoteButton.css create mode 100644 plugins/talk-plugin-upvote/client/components/UpvoteButton.js create mode 100644 plugins/talk-plugin-upvote/client/index.js create mode 100644 plugins/talk-plugin-upvote/index.js diff --git a/.gitignore b/.gitignore index 7c0007dc9..50e490095 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ plugins/* !plugins/talk-plugin-author-menu !plugins/talk-plugin-comment-content !plugins/talk-plugin-deep-reply-count +!plugins/talk-plugin-downvote !plugins/talk-plugin-facebook-auth !plugins/talk-plugin-featured-comments !plugins/talk-plugin-flag-details @@ -52,14 +53,16 @@ plugins/* !plugins/talk-plugin-remember-sort !plugins/talk-plugin-respect !plugins/talk-plugin-slack-notifications -!plugins/talk-plugin-sort-most-liked +!plugins/talk-plugin-sort-most-downvoted !plugins/talk-plugin-sort-most-loved !plugins/talk-plugin-sort-most-replied !plugins/talk-plugin-sort-most-respected +!plugins/talk-plugin-sort-most-upvoted !plugins/talk-plugin-sort-newest !plugins/talk-plugin-sort-oldest !plugins/talk-plugin-subscriber !plugins/talk-plugin-toxic-comments +!plugins/talk-plugin-upvote !plugins/talk-plugin-viewing-options !plugins/talk-plugin-rich-text !plugins/talk-plugin-rich-text-pell diff --git a/plugins/talk-plugin-downvote/client/.eslintrc.json b/plugins/talk-plugin-downvote/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/plugins/talk-plugin-downvote/client/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} diff --git a/plugins/talk-plugin-downvote/client/components/DownvoteButton.css b/plugins/talk-plugin-downvote/client/components/DownvoteButton.css new file mode 100644 index 000000000..71090306b --- /dev/null +++ b/plugins/talk-plugin-downvote/client/components/DownvoteButton.css @@ -0,0 +1,39 @@ +.container { + display: inline-block; +} + +.button { + color: #2a2a2a; + margin: 5px 10px 5px 0px; + background: none; + padding: 0px; + border: none; + font-size: inherit; + vertical-align: middle; + + &:hover { + color: #767676; + cursor: pointer; + } + + &.downvoted { + color: #cc0000; + + &:hover { + color: #ff3232; + cursor: pointer; + } + } +} + +.icon { + font-size: 12px; + padding: 0 3px; +} + +@media (max-width: 425px) { + .label { + display: none; + } +} + diff --git a/plugins/talk-plugin-downvote/client/components/DownvoteButton.js b/plugins/talk-plugin-downvote/client/components/DownvoteButton.js new file mode 100644 index 000000000..2bf34260b --- /dev/null +++ b/plugins/talk-plugin-downvote/client/components/DownvoteButton.js @@ -0,0 +1,56 @@ +import React from 'react'; +import Icon from './Icon'; +import styles from './DownvoteButton.css'; +import { withReaction } from 'plugin-api/beta/client/hocs'; +import cn from 'classnames'; + +const plugin = 'talk-plugin-downvote'; + +class DownvoteButton extends React.Component { + handleClick = () => { + const { + postReaction, + deleteReaction, + showSignInDialog, + alreadyReacted, + user, + } = this.props; + + // If the current user does not exist, trigger sign in dialog. + if (!user) { + showSignInDialog(); + return; + } + + if (alreadyReacted) { + deleteReaction(); + } else { + postReaction(); + } + }; + + render() { + const { count, alreadyReacted } = this.props; + return ( +
+ +
+ ); + } +} + +export default withReaction('downvote')(DownvoteButton); diff --git a/plugins/talk-plugin-downvote/client/components/Icon.js b/plugins/talk-plugin-downvote/client/components/Icon.js new file mode 100644 index 000000000..13d91fa9c --- /dev/null +++ b/plugins/talk-plugin-downvote/client/components/Icon.js @@ -0,0 +1,9 @@ +import React from 'react'; +import cn from 'classnames'; + +export default ({ className }) => ( +