From 60608af8b2a98af5f94a66c6b956c420c6532a90 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 6 Jul 2017 11:40:35 -0300 Subject: [PATCH 01/32] Filter out the ones that have slots --- client/coral-framework/helpers/plugins.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js index fcd0e6db6..2e5934bc4 100644 --- a/client/coral-framework/helpers/plugins.js +++ b/client/coral-framework/helpers/plugins.js @@ -16,7 +16,7 @@ function getSlotComponents(slot) { // Filter out components that have been disabled in `plugin_config` return flatten(plugins - // Filter out components that have been disabled in `plugin_config` + // Filter out components that have slots and have been disabled in `plugin_config` .filter((o) => o.module.slots && (!pluginConfig || !pluginConfig[o.plugin] || !pluginConfig[o.plugin].disable_components)) .filter((o) => o.module.slots[slot]) @@ -78,8 +78,7 @@ export function getSlotsFragments(slots) { } const components = uniq(flattenDeep(slots.map((slot) => { return plugins - .filter((o) => o.module.slots) - .filter((o) => o.module.slots[slot]) + .filter((o) => o.module.slots ? o.module.slots[slot] : false) .map((o) => o.module.slots[slot]); }))); From 99425e89ca2be99a399ce911e41e35cbf8c6de12 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 6 Jul 2017 13:09:43 -0300 Subject: [PATCH 02/32] More questions --- bin/cli-plugins | 35 +++++++++++++++++-- .../templates/plugin-client/client/.babelrc | 14 ++++++++ .../plugin-client/client/.eslintrc.json | 23 ++++++++++++ scripts/templates/plugin-client/index.js | 1 + scripts/templates/plugin-server/index.js | 1 + .../client/components/MyPluginComponent.js | 15 ++++++++ scripts/templates/plugin/client/index.js | 10 ++++-- 7 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 scripts/templates/plugin-client/client/.babelrc create mode 100644 scripts/templates/plugin-client/client/.eslintrc.json create mode 100644 scripts/templates/plugin-client/index.js create mode 100644 scripts/templates/plugin-server/index.js create mode 100644 scripts/templates/plugin/client/components/MyPluginComponent.js diff --git a/bin/cli-plugins b/bin/cli-plugins index 6dc127697..aa2d8f234 100755 --- a/bin/cli-plugins +++ b/bin/cli-plugins @@ -286,7 +286,7 @@ async function createSeedPlugin() { { type: 'input', name: 'pluginName', - message: 'Plugin Name: i.e talk-plugin-permalink', + message: 'Plugin Name:', validate: (input) => { if (pluginNameExists(input)) { @@ -299,18 +299,47 @@ async function createSeedPlugin() { return 'Plugin Name is required.'; } + }, + { + type: 'confirm', + name: 'server', + message: 'Is this a server-side plugin?' + }, + { + type: 'confirm', + name: 'client', + message: 'Is this a client-side plugin?' + }, + { + type: 'confirm', + name: 'client', + message: 'Should we add it to the plugins.json?' } ]); + //============================================================================== // Creating plugin seed + //============================================================================== const seedTemplatePlugin = path.join(dir, 'scripts/templates/plugin'); const newPluginPath = path.join(pluginsDir, answers.pluginName); try { - fs.copySync(seedTemplatePlugin, newPluginPath); + fs.copySync(seedTemplatePlugin, newPluginPath, { + filter: (p) => { + if (!answers.server) { + return /templates\/plugin\/server/.test(p); + } - console.log('Success!'); + if (!answers.client) { + return /templates\/plugin\/client/.test(p); + } + + return true; + } + }); + + console.log(`✨ Yay! Talk Plugin Seed created! Find your plugin in the /plugins folder`); } catch (err) { console.error(err); } diff --git a/scripts/templates/plugin-client/client/.babelrc b/scripts/templates/plugin-client/client/.babelrc new file mode 100644 index 000000000..60be246eb --- /dev/null +++ b/scripts/templates/plugin-client/client/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "add-module-exports", + "transform-class-properties", + "transform-decorators-legacy", + "transform-object-assign", + "transform-object-rest-spread", + "transform-async-to-generator", + "transform-react-jsx" + ] +} \ No newline at end of file diff --git a/scripts/templates/plugin-client/client/.eslintrc.json b/scripts/templates/plugin-client/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/scripts/templates/plugin-client/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/scripts/templates/plugin-client/index.js b/scripts/templates/plugin-client/index.js new file mode 100644 index 000000000..f053ebf79 --- /dev/null +++ b/scripts/templates/plugin-client/index.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/scripts/templates/plugin-server/index.js b/scripts/templates/plugin-server/index.js new file mode 100644 index 000000000..f053ebf79 --- /dev/null +++ b/scripts/templates/plugin-server/index.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/scripts/templates/plugin/client/components/MyPluginComponent.js b/scripts/templates/plugin/client/components/MyPluginComponent.js new file mode 100644 index 000000000..ff0fe6c3c --- /dev/null +++ b/scripts/templates/plugin/client/components/MyPluginComponent.js @@ -0,0 +1,15 @@ +import React from 'react'; +import {Icon} from 'plugin-api/beta/client/components/ui'; + +class MyPluginComponent extends React.Component { + render() { + return ( +
+ + Plugin Created by Talk CLI +
+ ); + } +} + +export default MyPluginComponent; diff --git a/scripts/templates/plugin/client/index.js b/scripts/templates/plugin/client/index.js index 931cd722c..acd0910be 100644 --- a/scripts/templates/plugin/client/index.js +++ b/scripts/templates/plugin/client/index.js @@ -1,6 +1,6 @@ /** - This is a client index example file and it should look like this: + This is a client index example file and it could look like this: ``` import LoveButton from './components/LoveButton'; @@ -17,4 +17,10 @@ To read more info on how to build client plugins. Please, go to: https://coralproject.github.io/talk/plugins-client.html */ -export default {}; +import MyPluginComponent from './components/MyPluginComponent'; + +export default { + slots: { + stream: [MyPluginComponent] + } +}; From a8e5ad451d8e50391a089312dcc63738677bc2b8 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 6 Jul 2017 15:08:39 -0300 Subject: [PATCH 03/32] Plugins CLI :D --- bin/cli-plugins | 78 +++++++++++++++++++++++++++++++++++++------------ package.json | 1 + yarn.lock | 22 ++++++++++++++ 3 files changed, 83 insertions(+), 18 deletions(-) diff --git a/bin/cli-plugins b/bin/cli-plugins index aa2d8f234..e5c3dd657 100755 --- a/bin/cli-plugins +++ b/bin/cli-plugins @@ -20,6 +20,7 @@ const path = require('path'); const spawn = require('cross-spawn'); const semver = require('semver'); const resolve = require('resolve'); +const stringify = require("json-stringify-pretty-compact"); const {plugins, itteratePlugins, isInternal} = require('../plugins'); function existsInNodeModules(name) { @@ -312,7 +313,7 @@ async function createSeedPlugin() { }, { type: 'confirm', - name: 'client', + name: 'addPluginsJson', message: 'Should we add it to the plugins.json?' } ]); @@ -321,28 +322,69 @@ async function createSeedPlugin() { // Creating plugin seed //============================================================================== - const seedTemplatePlugin = path.join(dir, 'scripts/templates/plugin'); + const seedPlugin = path.join(dir, 'scripts/templates/plugin'); const newPluginPath = path.join(pluginsDir, answers.pluginName); - - try { - fs.copySync(seedTemplatePlugin, newPluginPath, { - filter: (p) => { - if (!answers.server) { - return /templates\/plugin\/server/.test(p); - } - if (!answers.client) { - return /templates\/plugin\/client/.test(p); + if (fs.existsSync(seedPlugin)) { + + if (answers.server && answers.client) { + + // This is a server-side and client-side plugin!, let's copy the template + fs.copySync(seedPlugin, newPluginPath); + } else { + + fs.copySync(seedPlugin, newPluginPath, {filter: (p) => { + + // Allowing plugin folder and files with no subfolders + const rootRx = /plugin$|plugin\/[^/]*(\.).{2,3}/igm; + if (rootRx.test(p) && (fs.lstatSync(p).isDirectory() || fs.lstatSync(p).isFile())) { + return true; } - return true; - } - }); + // If it's a client-side plugin, copying client folder + if (answers.client) { + return /client/.test(p); + } + + // If it's a server-side plugin, copying server folder + if (answers.server) { + return /server/.test(p); + } + + }}); + } + + // Let's add this to the plugins.json + if (answers.addPluginsJson) { + const pluginsJson = path.join(dir, 'plugins.json'); + + fs.readJson(pluginsJson) + .then(j => { + + // This is a client-side plugin, let's push this. + if (answers.client) { + j.client.push(answers.pluginName); + + const output = stringify(j); + fs.writeFileSync(pluginsJson, output); + } + + // This is a server-side plugin, let's push this. + if (answers.server) { + j.server.push(answers.pluginName); + + const output = stringify(j); + fs.writeFileSync(pluginsJson, output); + } + }) + .catch(err => { + console.error(err); + }); + } + + console.log(`✨ Yay! Plugin created! Find your plugin: ${answers.pluginName} in the /plugins folder`); + } - console.log(`✨ Yay! Talk Plugin Seed created! Find your plugin in the /plugins folder`); - } catch (err) { - console.error(err); - } } //============================================================================== diff --git a/package.json b/package.json index 218381896..7c99b46a2 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "immutability-helper": "^2.2.0", "inquirer": "^3.0.6", "joi": "^10.4.1", + "json-stringify-pretty-compact": "^1.0.4", "jsonwebtoken": "^7.3.0", "jwt-decode": "^2.2.0", "kue": "^0.11.5", diff --git a/yarn.lock b/yarn.lock index 9de2c35fa..c536e2901 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3290,6 +3290,14 @@ fs-extra@^0.26.4: path-is-absolute "^1.0.0" rimraf "^2.2.8" +fs-extra@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^3.0.0" + universalify "^0.1.0" + fs-promise@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/fs-promise/-/fs-promise-0.3.1.tgz#bf34050368f24d6dc9dfc6688ab5cead8f86842a" @@ -4614,6 +4622,10 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" +json-stringify-pretty-compact@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.0.4.tgz#d5161131be27fd9748391360597fcca250c6c5ce" + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -4632,6 +4644,12 @@ jsonfile@^2.1.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -8263,6 +8281,10 @@ uniqs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" +universalify@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.0.tgz#9eb1c4651debcc670cc94f1a75762332bb967778" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" From aad2455654a15eafc5dcadf771162c1f16fc6e0a Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 6 Jul 2017 15:19:26 -0300 Subject: [PATCH 04/32] Linting --- bin/cli-plugins | 9 ++++----- package.json | 1 - .../plugin/client/components/MyPluginComponent.js | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/bin/cli-plugins b/bin/cli-plugins index e5c3dd657..54bf0f1ed 100755 --- a/bin/cli-plugins +++ b/bin/cli-plugins @@ -20,7 +20,6 @@ const path = require('path'); const spawn = require('cross-spawn'); const semver = require('semver'); const resolve = require('resolve'); -const stringify = require("json-stringify-pretty-compact"); const {plugins, itteratePlugins, isInternal} = require('../plugins'); function existsInNodeModules(name) { @@ -359,13 +358,13 @@ async function createSeedPlugin() { const pluginsJson = path.join(dir, 'plugins.json'); fs.readJson(pluginsJson) - .then(j => { + .then((j) => { // This is a client-side plugin, let's push this. if (answers.client) { j.client.push(answers.pluginName); - const output = stringify(j); + const output = JSON.stringify(j, null, 2); fs.writeFileSync(pluginsJson, output); } @@ -373,11 +372,11 @@ async function createSeedPlugin() { if (answers.server) { j.server.push(answers.pluginName); - const output = stringify(j); + const output = JSON.stringify(j, null, 2); fs.writeFileSync(pluginsJson, output); } }) - .catch(err => { + .catch((err) => { console.error(err); }); } diff --git a/package.json b/package.json index 7c99b46a2..218381896 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,6 @@ "immutability-helper": "^2.2.0", "inquirer": "^3.0.6", "joi": "^10.4.1", - "json-stringify-pretty-compact": "^1.0.4", "jsonwebtoken": "^7.3.0", "jwt-decode": "^2.2.0", "kue": "^0.11.5", diff --git a/scripts/templates/plugin/client/components/MyPluginComponent.js b/scripts/templates/plugin/client/components/MyPluginComponent.js index ff0fe6c3c..42bfc09fb 100644 --- a/scripts/templates/plugin/client/components/MyPluginComponent.js +++ b/scripts/templates/plugin/client/components/MyPluginComponent.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Icon} from 'plugin-api/beta/client/components/ui'; +import {CoralLogo} from 'plugin-api/beta/client/components/ui'; class MyPluginComponent extends React.Component { render() { From d2b4c2bcac030777f671ab631d6ca792f945d5ab Mon Sep 17 00:00:00 2001 From: David Erwin Date: Fri, 7 Jul 2017 13:43:05 -0400 Subject: [PATCH 05/32] Add architecture docs --- docs/_data/sidebars/talk_sidebar.yml | 8 +++ docs/architecture-tags.md | 7 +++ docs/architecture.md | 75 ++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 docs/architecture-tags.md create mode 100644 docs/architecture.md diff --git a/docs/_data/sidebars/talk_sidebar.yml b/docs/_data/sidebars/talk_sidebar.yml index b44c9648f..dd311cbef 100644 --- a/docs/_data/sidebars/talk_sidebar.yml +++ b/docs/_data/sidebars/talk_sidebar.yml @@ -37,6 +37,14 @@ entries: url: /install-setup.html output: web + - title: Architecture + output: web + folderitems: + - title: Overview + url: /architecture.html + output: web + + - title: Plugins output: web folderitems: diff --git a/docs/architecture-tags.md b/docs/architecture-tags.md new file mode 100644 index 000000000..7b402b436 --- /dev/null +++ b/docs/architecture-tags.md @@ -0,0 +1,7 @@ +--- +title: Architecture Overview +keywords: architecture +sidebar: talk_sidebar +permalink: architecture-tags.html +summary: +--- diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 000000000..dbaee1b3e --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,75 @@ +--- +title: Architecture Overview +keywords: architecture +sidebar: talk_sidebar +permalink: architecture.html +summary: +--- + +## Talk's Plugin/Plugin API/Core Architecture + +Talk consists of three distinct layers of code: + +* Plugins +* Plugin API +* Core + +### Plugins + +Talk plugins deliver the features and functionality that can be changed or removed. Much of the default functionality is delivered by plugins allowing developers to change behavior along product lines that we've found to be important. + +### Plugin API + +Talk plugins interact exclusively with the Plugin API. Maintaining this layer of separation between plugins and core allows us to consciously design the api that we want it publish to plugin authors. We can then expose just the elements of core that make sense and maintain that contract as core changes. + +### Core + +Talk core consists of architecture and functionality that deliver stability, security, scalability, extendability, etc... In addition, the Core contains features and functionality that is essential to the operation of Talk as a product. + +Our goal is to continually extend our plugin infrastructure making the Core as pluggable as possible. Ultimately, a day may come where the Core of Talk is simply a framework for delivering a certain flavor of web applications. + +## Thinking about Plugins, the Plugin API and Core? + +The following is a template for a thought process that may help clarify your ideas against the backdrop of Talk's architecture. + +Think of a feature of capability. It could be something that's already in Talk or not. It could be something you want to build, or something you'd think would be a terrible idea. The important part here is to have something to interrogate. + +``` +wait(60000); +``` + +Now, ask these questions: + +### Is it a Plugin? + +Most work for Talk these days happens in the Plugin space. If the answers to any of these questions is Yes, then you're thinking of a Plugin. + +* Does Talk's exiting Plugin APIs support the thing you want to build? +* Is this something that only some users will want/need? +* Is this something that we want devs to iterate on widely? + +You should [build it as a plugin](plugins-quickstart.html). Feel free to explore here on your own or reach out to us. We love to advise on plugins, so please feel free to [file an issue](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md) and we will start a conversation. We will help you conceptualize, architect and promote your plugin if it is in line with our values. + +### Does it need updates to the Plugin API? + +If you answered yes above: + +* Do I need to extend the Plugin API to support my plugin? + +Often times all the functionality a plugin needs is in the Core, but the Plugin API doesn't expose it. In these cases, we seek to iteratively extend the Talk Plugin API. All Plugin API contributions from the community must begin by [filing an Issue](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md). + +Note: we are stabilizing the process by which new Plugin API bindings are created, agreed upon and ultimately made part of our Plugins Contract. If you are interested in this process, please reach out to us. + +### Does it need updates to the Plugin API _and_ Core? + +3) What, if any, changes need to be made to Core so that the API can be extended? + +Quite often the only things missing from Core are things like _events_, _slots_, _CSS classes_, etc... Adding these is a great way to become a Core Contributor and break new ground as a Plugin Developer. + +We seek to keep Core as lean as possible. + +### Is my idea really just Core? + +Amazing! We are always looking to extend the capabilities of Talk. We look forward to discussing what you've got to bring! + +Please see our [contributing guide](](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md)) for more information about contributing Core code. From c3ec772ea27072c40c646b471bf8eb422d28084a Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 7 Jul 2017 14:43:46 -0300 Subject: [PATCH 06/32] new cta for questions --- bin/cli-plugins | 4 ++-- .../templates/plugin-client/client/.babelrc | 14 ----------- .../plugin-client/client/.eslintrc.json | 23 ------------------- scripts/templates/plugin-client/index.js | 1 - scripts/templates/plugin-server/index.js | 1 - 5 files changed, 2 insertions(+), 41 deletions(-) delete mode 100644 scripts/templates/plugin-client/client/.babelrc delete mode 100644 scripts/templates/plugin-client/client/.eslintrc.json delete mode 100644 scripts/templates/plugin-client/index.js delete mode 100644 scripts/templates/plugin-server/index.js diff --git a/bin/cli-plugins b/bin/cli-plugins index 54bf0f1ed..e4b5cae51 100755 --- a/bin/cli-plugins +++ b/bin/cli-plugins @@ -303,12 +303,12 @@ async function createSeedPlugin() { { type: 'confirm', name: 'server', - message: 'Is this a server-side plugin?' + message: 'Is this plugin extending the server capabilities?' }, { type: 'confirm', name: 'client', - message: 'Is this a client-side plugin?' + message: 'Is this plugin extending the client capabilities?' }, { type: 'confirm', diff --git a/scripts/templates/plugin-client/client/.babelrc b/scripts/templates/plugin-client/client/.babelrc deleted file mode 100644 index 60be246eb..000000000 --- a/scripts/templates/plugin-client/client/.babelrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "presets": [ - "es2015" - ], - "plugins": [ - "add-module-exports", - "transform-class-properties", - "transform-decorators-legacy", - "transform-object-assign", - "transform-object-rest-spread", - "transform-async-to-generator", - "transform-react-jsx" - ] -} \ No newline at end of file diff --git a/scripts/templates/plugin-client/client/.eslintrc.json b/scripts/templates/plugin-client/client/.eslintrc.json deleted file mode 100644 index 9fe56bd14..000000000 --- a/scripts/templates/plugin-client/client/.eslintrc.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "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/scripts/templates/plugin-client/index.js b/scripts/templates/plugin-client/index.js deleted file mode 100644 index f053ebf79..000000000 --- a/scripts/templates/plugin-client/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; diff --git a/scripts/templates/plugin-server/index.js b/scripts/templates/plugin-server/index.js deleted file mode 100644 index f053ebf79..000000000 --- a/scripts/templates/plugin-server/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; From 52d7ba8965ef245374284b732e28e7955c5e0d3d Mon Sep 17 00:00:00 2001 From: David Erwin Date: Fri, 7 Jul 2017 16:59:18 -0400 Subject: [PATCH 07/32] Add Tags docs --- docs/_data/sidebars/talk_sidebar.yml | 3 ++ docs/architecture-tags.md | 64 +++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/docs/_data/sidebars/talk_sidebar.yml b/docs/_data/sidebars/talk_sidebar.yml index dd311cbef..ed307b88e 100644 --- a/docs/_data/sidebars/talk_sidebar.yml +++ b/docs/_data/sidebars/talk_sidebar.yml @@ -43,6 +43,9 @@ entries: - title: Overview url: /architecture.html output: web + - title: Tags + url: /architecture-tags.html + output: web - title: Plugins diff --git a/docs/architecture-tags.md b/docs/architecture-tags.md index 7b402b436..38205c7eb 100644 --- a/docs/architecture-tags.md +++ b/docs/architecture-tags.md @@ -1,7 +1,69 @@ --- -title: Architecture Overview +title: Tags keywords: architecture sidebar: talk_sidebar permalink: architecture-tags.html summary: --- + +Tags can be added to Users, Comments and Assets. + +## Tag Definitions + +When handling tags, the Talk Server references a set of definitions that describe how tags are handled. These definitions are keyed off the tag `name`, the simple string that is stored on items. + +The schema for Tag definitions [can be found here](https://github.com/coralproject/talk/blob/3545bf01cd91044fdb738d337a0ac94d9f71fbc3/models/schema/tag.js). + +Note that along with the `name`, tag definitions contains: + +* `permissions` information about who can see and set the tag, +* `models` which `ITEM_TYPES` this tag can be applied to, and + +Whenever a tag is 'handled' by the server, it references this definition to determine that tag's behavior. + +See [Plugin API Documentation](plugins-server.html#field-tags) for more information. + +### Creating a Tag Definition + +Tag Definitions must be created before tags can be used. This ensures that users cannot push arbitrary tags to hack the system. It also allows devs to specify the behavioral characteristics of the tag. + +Take the tag created by `coral-plugin-offtopic` as an example. + +``` +// coral-plugin-offtopic/index.js +module.exports = { + tags: [ + { + name: 'OFF_TOPIC', + permissions: { + public: true, + self: true, + roles: [] + }, + models: ['COMMENTS'], + created_at: new Date() + } + ] +}; +``` + +This plugin allows users to self-report that their comment is "off topic" at the time of creation, then display a badge on those comments. + +To accomplish this, the plugin creates the tag `OFF_TOPIC` with: + +* `public: true` - will be sent over the wire to the client side) +* `self: true` - can be added by the active user to themselves or assets they own +* `roles: []` - cannot be added by anyone based on their roles +* `models: ['COMMENTS']` - can only be added to COMMENTS (not to users/assets/etc...) + +And viola! This tag is something that can only be created by the logged in user on their own comments and is sent over the wire to the client so it can display the badge. + +## Tag Links + +When tags are stored on objects in the database, they are represented by [TagLinks](https://github.com/coralproject/talk/blob/master/models/schema/tag_link.js). + +A TagLinks says that `tag` was `assigned_by` a specific user at a specific time (`created_at`). + +Note that the `tag` field in the TagLinkSchema is the full TagSchema itself. This allows for another level of flexibility. Server code may generate Tags on the fly, complete with programmatically generated permissions and item behaviors. + +If a Tag definitions exists in the global/asset context then that definition will be used regardless of what is stored here. This allows high level controls on the behavior of tags. From 83d7e599c79f0765f074a2379feceb2b0b047ef8 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Sun, 9 Jul 2017 12:09:07 -0300 Subject: [PATCH 08/32] pushing client template --- .../client/components/MyPluginComponent.css | 27 +++++++++++++++++++ .../client/components/MyPluginComponent.js | 16 ++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 scripts/templates/plugin/client/components/MyPluginComponent.css diff --git a/scripts/templates/plugin/client/components/MyPluginComponent.css b/scripts/templates/plugin/client/components/MyPluginComponent.css new file mode 100644 index 000000000..187e68750 --- /dev/null +++ b/scripts/templates/plugin/client/components/MyPluginComponent.css @@ -0,0 +1,27 @@ +.myPluginContainer { + padding: 10px; + background: #f0f0f0; + border: 1px solid #d6d6d6; + margin: 10px 0; + text-align: center; + border-radius: 3px; +} + +.logo { + position: block; + animation: spin 2s infinite ease; + animation-delay: 1s; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.description { + color: #444444; +} diff --git a/scripts/templates/plugin/client/components/MyPluginComponent.js b/scripts/templates/plugin/client/components/MyPluginComponent.js index 42bfc09fb..d586ab473 100644 --- a/scripts/templates/plugin/client/components/MyPluginComponent.js +++ b/scripts/templates/plugin/client/components/MyPluginComponent.js @@ -1,12 +1,22 @@ import React from 'react'; import {CoralLogo} from 'plugin-api/beta/client/components/ui'; +import styles from './MyPluginComponent.css'; class MyPluginComponent extends React.Component { render() { return ( -
- - Plugin Created by Talk CLI +
+ +
+

Plugin created by Talk CLI

+ + + To read more about client plugins check{' '} + + our docs and guides! + + +
); } From 1e3c1feadfe4336717162cfb1fcb4e800eba0971 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Sun, 9 Jul 2017 12:09:54 -0300 Subject: [PATCH 09/32] cta --- scripts/templates/plugin/client/components/MyPluginComponent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/templates/plugin/client/components/MyPluginComponent.js b/scripts/templates/plugin/client/components/MyPluginComponent.js index d586ab473..89e274e1a 100644 --- a/scripts/templates/plugin/client/components/MyPluginComponent.js +++ b/scripts/templates/plugin/client/components/MyPluginComponent.js @@ -11,7 +11,7 @@ class MyPluginComponent extends React.Component {

Plugin created by Talk CLI

- To read more about client plugins check{' '} + To read more about plugins check{' '} our docs and guides! From 1e4e1cb41399547d1368e5df5af0edf03241c1f0 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 5 Jul 2017 22:23:19 +0700 Subject: [PATCH 10/32] Implement Stream TabBar --- .../coral-embed-stream/src/actions/stream.js | 4 + .../src/components/AllCommentsPane.js | 202 +++++++++++++ .../src/components/Comment.css | 20 +- .../src/components/Comment.js | 18 +- .../src/components/EditableCommentContent.js | 8 +- .../src/components/Embed.js | 75 ++--- .../src/components/Stream.css | 12 + .../src/components/Stream.js | 270 ++++++------------ .../src/constants/stream.js | 1 + .../src/containers/Embed.js | 2 - .../src/containers/Stream.js | 6 +- .../coral-embed-stream/src/reducers/stream.js | 10 +- client/coral-embed-stream/style/default.css | 10 +- client/coral-framework/styles/reset.css | 29 ++ client/coral-plugin-best/BestButton.css | 4 + client/coral-plugin-best/BestButton.js | 5 +- .../CommentCount.js | 17 -- .../coral-plugin-flags/components/styles.css | 1 + client/coral-plugin-replies/ReplyButton.css | 4 + client/coral-plugin-replies/ReplyButton.js | 8 +- client/coral-ui/components/Tab.css | 67 ++++- client/coral-ui/components/Tab.js | 94 +++++- client/coral-ui/components/TabBar.css | 40 +-- client/coral-ui/components/TabBar.js | 75 +++-- client/coral-ui/components/TabContent.css | 3 + client/coral-ui/components/TabContent.js | 31 +- client/coral-ui/components/TabCount.css | 18 ++ client/coral-ui/components/TabCount.js | 29 ++ client/coral-ui/components/TabPane.js | 24 ++ client/coral-ui/index.js | 2 + locales/en.yml | 1 + locales/es.yml | 1 + .../coral-plugin-like/client/LikeButton.js | 2 +- plugins/coral-plugin-like/client/styles.css | 4 + .../coral-plugin-love/client/LoveButton.js | 2 +- plugins/coral-plugin-love/client/styles.css | 4 + .../coral-plugin-respect/client/styles.css | 2 +- .../client/components/ViewingOptions.css | 8 +- .../client/components/styles.css | 2 +- 39 files changed, 765 insertions(+), 350 deletions(-) create mode 100644 client/coral-embed-stream/src/components/AllCommentsPane.js create mode 100644 client/coral-embed-stream/src/components/Stream.css create mode 100644 client/coral-framework/styles/reset.css create mode 100644 client/coral-plugin-best/BestButton.css delete mode 100644 client/coral-plugin-comment-count/CommentCount.js create mode 100644 client/coral-plugin-replies/ReplyButton.css create mode 100644 client/coral-ui/components/TabContent.css create mode 100644 client/coral-ui/components/TabCount.css create mode 100644 client/coral-ui/components/TabCount.js create mode 100644 client/coral-ui/components/TabPane.js diff --git a/client/coral-embed-stream/src/actions/stream.js b/client/coral-embed-stream/src/actions/stream.js index 9b5d94a9b..af905a53f 100644 --- a/client/coral-embed-stream/src/actions/stream.js +++ b/client/coral-embed-stream/src/actions/stream.js @@ -47,3 +47,7 @@ export const removeCommentClassName = (idx) => ({ type: actions.REMOVE_COMMENT_CLASSNAME, idx }); + +export const setActiveTab = (tab) => (dispatch) => { + dispatch({type: actions.SET_ACTIVE_TAB, tab}); +}; diff --git a/client/coral-embed-stream/src/components/AllCommentsPane.js b/client/coral-embed-stream/src/components/AllCommentsPane.js new file mode 100644 index 000000000..6af72475a --- /dev/null +++ b/client/coral-embed-stream/src/components/AllCommentsPane.js @@ -0,0 +1,202 @@ +import React from 'react'; + +import LoadMore from './LoadMore'; +import IgnoredCommentTombstone from './IgnoredCommentTombstone'; +import NewCount from './NewCount'; +import {TransitionGroup} from 'react-transition-group'; +import {forEachError} from 'coral-framework/utils'; +import Comment from '../components/Comment'; + +const hasComment = (nodes, id) => nodes.some((node) => node.id === id); + +// resetCursors will return the id cursors of the first and second comment of +// the current comment list. The cursors are used to dertermine which +// comments to show. The spare cursor functions as a backup in case one +// of the comments gets deleted. +function resetCursors(state, props) { + const comments = props.root.asset.comments; + if (comments && comments.nodes.length) { + const idCursors = [comments.nodes[0].id]; + if (comments.nodes[1]) { + idCursors.push(comments.nodes[1].id); + } + return {idCursors}; + } + return {idCursors: []}; +} + +// invalidateCursor is called whenever a comment is removed which is referenced +// by one of the 2 id cursors. It returns a new set of id cursors calculated +// using the help of the backup cursor. +function invalidateCursor(invalidated, state, props) { + const alt = invalidated === 1 ? 0 : 1; + const comments = props.root.asset.comments; + const idCursors = []; + if (state.idCursors[alt]) { + idCursors.push(state.idCursors[alt]); + const index = comments.nodes.findIndex((node) => node.id === idCursors[0]); + const nextInLine = comments.nodes[index + 1]; + if (nextInLine) { + idCursors.push(nextInLine.id); + } + } + return {idCursors}; +} + +class AllCommentsPane extends React.Component { + + constructor(props) { + super(props); + this.state = { + ...resetCursors(this.state, props), + loadingState: '', + }; + } + + componentWillReceiveProps(next) { + const {comments: prevComments} = this.props; + const {comments: nextComments} = next; + + if (!prevComments && nextComments) { + this.setState(resetCursors); + return; + } + + if ( + prevComments && nextComments && + nextComments.nodes.length < prevComments.nodes.length + ) { + + // Invalidate first cursor if referenced comment was removed. + if (this.state.idCursors[0] && !hasComment(nextComments.nodes, this.state.idCursors[0])) { + this.setState(invalidateCursor(0, this.state, next)); + } + + // Invalidate second cursor if referenced comment was removed. + if (this.state.idCursors[1] && !hasComment(nextComments.nodes, this.state.idCursors[1])) { + this.setState(invalidateCursor(1, this.state, next)); + } + } + } + + loadMore = () => { + this.setState({loadingState: 'loading'}); + this.props.loadMore() + .then(() => { + this.setState({loadingState: 'success'}); + }) + .catch((error) => { + this.setState({loadingState: 'error'}); + forEachError(error, ({msg}) => {this.props.addNotification('error', msg);}); + }); + } + + viewNewComments = () => { + this.setState(resetCursors); + }; + + // getVisibileComments returns a list containing comments + // which were authored by current user or comes after the `idCursor`. + getVisibleComments() { + const {comments, currentUser: user} = this.props; + const idCursor = this.state.idCursors[0]; + const userId = user ? user.id : null; + + if (!comments) { + return []; + } + + const view = []; + let pastCursor = false; + comments.nodes.forEach((comment) => { + if (comment.id === idCursor) { + pastCursor = true; + } + if (pastCursor || comment.user.id === userId) { + view.push(comment); + } + }); + return view; + } + + render() { + const { + data, + root, + comments, + commentClassNames, + addTag, + removeTag, + ignoreUser, + setActiveReplyBox, + activeReplyBox, + addNotification, + disableReply, + postComment, + asset, + currentUser, + postFlag, + postDontAgree, + loadNewReplies, + deleteAction, + showSignInDialog, + commentIsIgnored, + charCountEnable, + maxCharCount, + editComment, + } = this.props; + + const {loadingState} = this.state; + const view = this.getVisibleComments(); + + return ( +
+ + + {view.map((comment) => { + return commentIsIgnored(comment) + ? + : ; + })} + + +
+ ); + } +} + +export default AllCommentsPane; diff --git a/client/coral-embed-stream/src/components/Comment.css b/client/coral-embed-stream/src/components/Comment.css index 4c8a9fc86..d397185dc 100644 --- a/client/coral-embed-stream/src/components/Comment.css +++ b/client/coral-embed-stream/src/components/Comment.css @@ -1,8 +1,17 @@ .root { - margin-top: 16px; margin-left: 20px; - margin-bottom: 15px; + margin-bottom: 16px; position: relative; + border-top: 1px solid rgba(0, 0, 0, 0.1); + padding-top: 12px; +} + +.rootLevel0:first-child { + padding-top: 0; +} + +.root:first-child { + border: 0; } .rootLevel0 { @@ -52,6 +61,13 @@ font-style: italic; } +.hr { + border: 0; + height: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid rgba(255, 255, 255, 0.3); +} + /* element in the top right of the Comment */ .topRight { float: right; diff --git a/client/coral-embed-stream/src/components/Comment.js b/client/coral-embed-stream/src/components/Comment.js index 0e6201caf..bad2b6e4a 100644 --- a/client/coral-embed-stream/src/components/Comment.js +++ b/client/coral-embed-stream/src/components/Comment.js @@ -134,7 +134,6 @@ export default class Comment extends React.Component { } static propTypes = { - reactKey: PropTypes.string.isRequired, // id of currently opened ReplyBox. tracked in Stream.js activeReplyBox: PropTypes.string.isRequired, @@ -148,12 +147,8 @@ export default class Comment extends React.Component { addNotification: PropTypes.func.isRequired, postComment: PropTypes.func.isRequired, depth: PropTypes.number.isRequired, - liveUpdates: PropTypes.bool.isRequired, - asset: PropTypes.shape({ - id: PropTypes.string, - title: PropTypes.string, - url: PropTypes.string - }).isRequired, + liveUpdates: PropTypes.bool, + asset: PropTypes.object.isRequired, currentUser: PropTypes.shape({ id: PropTypes.string.isRequired }), @@ -335,7 +330,6 @@ export default class Comment extends React.Component { const view = this.getVisibileReplies(); const {loadingState} = this.state; - const isReply = !!parentId; const isPending = comment.id.indexOf('pending') >= 0; const isHighlighted = highlighted === comment.id; @@ -372,7 +366,7 @@ export default class Comment extends React.Component { addTag({ id: comment.id, name: BEST_TAG, - assetId: asset.id + assetId: asset.id, }), () => 'Failed to tag comment as best' ); @@ -382,7 +376,7 @@ export default class Comment extends React.Component { removeTag({ id: comment.id, name: BEST_TAG, - assetId: asset.id + assetId: asset.id, }), () => 'Failed to remove best comment tag' ); @@ -422,7 +416,6 @@ export default class Comment extends React.Component { className={cn(...rootClassNames)} id={`c_${comment.id}`} > - {!isReply &&
}
; diff --git a/client/coral-embed-stream/src/components/EditableCommentContent.js b/client/coral-embed-stream/src/components/EditableCommentContent.js index 7e6035d58..06d8e6b9d 100644 --- a/client/coral-embed-stream/src/components/EditableCommentContent.js +++ b/client/coral-embed-stream/src/components/EditableCommentContent.js @@ -18,11 +18,6 @@ export class EditableCommentContent extends React.Component { // show notification to the user (e.g. for errors) addNotification: PropTypes.func.isRequired, - asset: PropTypes.shape({ - settings: PropTypes.shape({ - charCountEnable: PropTypes.bool, - }), - }).isRequired, // comment that is being edited comment: PropTypes.shape({ @@ -39,6 +34,7 @@ export class EditableCommentContent extends React.Component { currentUser: PropTypes.shape({ id: PropTypes.string.isRequired }), + charCountEnable: PropTypes.bool, maxCharCount: PropTypes.number, // edit a comment, passed {{ body }} @@ -121,7 +117,7 @@ export class EditableCommentContent extends React.Component {
{ - switch (tab) { - case 0: - this.props.setActiveTab('stream'); - break; - case 1: - this.props.setActiveTab('profile'); - // TODO: move data fetching to profile container. + // TODO: move data fetching to appropiate containers. + switch (tab) { + case 'profile': this.props.data.refetch(); break; - case 2: - this.props.setActiveTab('config'); - - // TODO: move data fetching to config container. + case 'config': this.props.data.refetch(); break; } + this.props.setActiveTab(tab); }; - handleShowProfile = () => this.props.setActiveTab('profile'); - render() { - const {activeTab, viewAllComments, commentId} = this.props; - const {asset: {totalCommentCount}} = this.props.root; + const {activeTab} = this.props; const {user} = this.props.auth; return (
- - - {t('framework.my_profile')} - {t('framework.configure_stream')} + + + {t('embed_comments_tab')} + + + {t('framework.my_profile')} + + {can(user, 'UPDATE_CONFIG') && + + {t('framework.configure_stream')} + + } - {commentId && - } - - - - - - - - + + + + + + + + + + +
diff --git a/client/coral-embed-stream/src/components/Stream.css b/client/coral-embed-stream/src/components/Stream.css new file mode 100644 index 000000000..f2c4c48c5 --- /dev/null +++ b/client/coral-embed-stream/src/components/Stream.css @@ -0,0 +1,12 @@ +.root { + margin-top: 6px; +} + +.viewAllButton { + position: absolute; + right: 0px; +} + +.tabPanel { + margin-top: 8px; +} diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 4b04f5e7b..1c84c2d47 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -1,5 +1,4 @@ import React, {PropTypes} from 'react'; -import LoadMore from './LoadMore'; import {StreamError} from './StreamError'; import Comment from '../components/Comment'; import SuspendedAccount from './SuspendedAccount'; @@ -12,161 +11,71 @@ import RestrictedMessageBox import t, {timeago} from 'coral-framework/services/i18n'; import CommentBox from 'coral-plugin-commentbox/CommentBox'; import QuestionBox from 'coral-plugin-questionbox/QuestionBox'; -import IgnoredCommentTombstone from './IgnoredCommentTombstone'; -import NewCount from './NewCount'; -import {TransitionGroup} from 'react-transition-group'; -import {forEachError} from 'coral-framework/utils'; +import {Button, TabBar, Tab, TabCount, TabContent, TabPane} from 'coral-ui'; + import {getTopLevelParent} from '../graphql/utils'; +import AllCommentsPane from './AllCommentsPane'; -const hasComment = (nodes, id) => nodes.some((node) => node.id === id); - -// resetCursors will return the id cursors of the first and second comment of -// the current comment list. The cursors are used to dertermine which -// comments to show. The spare cursor functions as a backup in case one -// of the comments gets deleted. -function resetCursors(state, props) { - const comments = props.root.asset.comments; - if (comments && comments.nodes.length) { - const idCursors = [comments.nodes[0].id]; - if (comments.nodes[1]) { - idCursors.push(comments.nodes[1].id); - } - return {idCursors}; - } - return {idCursors: []}; -} - -// invalidateCursor is called whenever a comment is removed which is referenced -// by one of the 2 id cursors. It returns a new set of id cursors calculated -// using the help of the backup cursor. -function invalidateCursor(invalidated, state, props) { - const alt = invalidated === 1 ? 0 : 1; - const comments = props.root.asset.comments; - const idCursors = []; - if (state.idCursors[alt]) { - idCursors.push(state.idCursors[alt]); - const index = comments.nodes.findIndex((node) => node.id === idCursors[0]); - const nextInLine = comments.nodes[index + 1]; - if (nextInLine) { - idCursors.push(nextInLine.id); - } - } - return {idCursors}; -} +import styles from './Stream.css'; class Stream extends React.Component { constructor(props) { super(props); this.state = { - ...resetCursors(this.state, props), keepCommentBox: false, - loadingState: '', }; } componentWillReceiveProps(next) { - const {root: {asset: {comments: prevComments}}} = this.props; - const {root: {asset: {comments: nextComments}}} = next; - - if (!prevComments && nextComments) { - this.setState(resetCursors); - return; - } // Keep comment box when user was live suspended, banned, ... if (!this.userIsDegraged(this.props) && this.userIsDegraged(next)) { this.setState({keepCommentBox: true}); } - - if ( - prevComments && nextComments && - nextComments.nodes.length < prevComments.nodes.length - ) { - - // Invalidate first cursor if referenced comment was removed. - if (this.state.idCursors[0] && !hasComment(nextComments.nodes, this.state.idCursors[0])) { - this.setState(invalidateCursor(0, this.state, next)); - } - - // Invalidate second cursor if referenced comment was removed. - if (this.state.idCursors[1] && !hasComment(nextComments.nodes, this.state.idCursors[1])) { - this.setState(invalidateCursor(1, this.state, next)); - } - } } - viewNewComments = () => { - this.setState(resetCursors); - }; - - setActiveReplyBox = (reactKey) => { + setActiveReplyBox = (id) => { if (!this.props.auth.user) { this.props.showSignInDialog(); } else { - this.props.setActiveReplyBox(reactKey); + this.props.setActiveReplyBox(id); } }; - loadMoreComments = () => { - this.setState({loadingState: 'loading'}); - this.props.loadMoreComments() - .then(() => { - this.setState({loadingState: 'success'}); - }) - .catch((error) => { - this.setState({loadingState: 'error'}); - forEachError(error, ({msg}) => {this.props.addNotification('error', msg);}); - }); - } - - // getVisibileComments returns a list containing comments - // which were authored by current user or comes after the `idCursor`. - getVisibleComments() { - const {root: {asset: {comments}}, auth: {user}} = this.props; - const idCursor = this.state.idCursors[0]; - const userId = user ? user.id : null; - - if (!comments) { - return []; - } - - const view = []; - let pastCursor = false; - comments.nodes.forEach((comment) => { - if (comment.id === idCursor) { - pastCursor = true; - } - if (pastCursor || comment.user.id === userId) { - view.push(comment); - } - }); - return view; - } - userIsDegraged({auth: {user}} = this.props) { return !can(user, 'INTERACT_WITH_COMMUNITY'); } render() { const { + data, + root, + activeReplyBox, + setActiveReplyBox, + appendItemArray, commentClassNames, - root: {asset, asset: {comments}, comment, me}, + root: {asset, asset: {comments, totalCommentCount}, comment, me}, postComment, addNotification, + editComment, postFlag, postDontAgree, deleteAction, showSignInDialog, + updateItem, addTag, ignoreUser, + activeStreamTab, + setActiveStreamTab, + loadNewReplies, + loadMoreComments, + viewAllComments, auth: {loggedIn, user}, removeTag, - pluginProps, editName } = this.props; - const {keepCommentBox, loadingState} = this.state; - const view = this.getVisibleComments(); + const {keepCommentBox} = this.state; const open = asset.closedAt === null; // even though the permalinked comment is the highlighted one, we're displaying its parent + replies @@ -194,7 +103,15 @@ class Stream extends React.Component { } return ( -
+
+ {comment && + } {open ?
@@ -211,7 +128,7 @@ class Stream extends React.Component { {t( 'stream.temporarily_suspended', - this.props.root.settings.organizationName, + root.settings.organizationName, timeago(user.suspension.until) )} } @@ -223,10 +140,10 @@ class Stream extends React.Component { />} {showCommentBox && - :
- - - {view.map((comment) => { - return commentIsIgnored(comment) - ? - : ; - })} - - -
} + :
+ + + Featured + + + All Comments {totalCommentCount} + + + + + TODO + + + + + +
+ }
); } diff --git a/client/coral-embed-stream/src/constants/stream.js b/client/coral-embed-stream/src/constants/stream.js index 992767455..622a86c18 100644 --- a/client/coral-embed-stream/src/constants/stream.js +++ b/client/coral-embed-stream/src/constants/stream.js @@ -4,3 +4,4 @@ export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS'; export const ADD_COMMENT_CLASSNAME = 'ADD_COMMENT_CLASSNAME'; export const REMOVE_COMMENT_CLASSNAME = 'REMOVE_COMMENT_CLASSNAME'; export const THREADING_LEVEL = process.env.TALK_THREADING_LEVEL; +export const SET_ACTIVE_TAB = 'CORAL_STREAM_SET_ACTIVE_TAB'; diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js index bf05caf29..fff925a0f 100644 --- a/client/coral-embed-stream/src/containers/Embed.js +++ b/client/coral-embed-stream/src/containers/Embed.js @@ -16,7 +16,6 @@ import {addNotification} from 'coral-framework/actions/notification'; import t from 'coral-framework/services/i18n'; import {setActiveTab} from '../actions/embed'; -import {viewAllComments} from '../actions/stream'; const {logout, checkLogin} = authActions; const {fetchAssetSuccess} = assetActions; @@ -185,7 +184,6 @@ const mapDispatchToProps = (dispatch) => logout, checkLogin, setActiveTab, - viewAllComments, fetchAssetSuccess, addNotification, }, diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js index 28947b77e..21a29dd38 100644 --- a/client/coral-embed-stream/src/containers/Stream.js +++ b/client/coral-embed-stream/src/containers/Stream.js @@ -10,7 +10,7 @@ import { import {notificationActions, authActions} from 'coral-framework'; import {editName} from 'coral-framework/actions/user'; -import {setActiveReplyBox} from '../actions/stream'; +import {setActiveReplyBox, setActiveTab, viewAllComments} from '../actions/stream'; import Stream from '../components/Stream'; import Comment from './Comment'; import {withFragments} from 'coral-framework/hocs'; @@ -308,6 +308,8 @@ const mapStateToProps = (state) => ({ assetUrl: state.stream.assetUrl, activeTab: state.embed.activeTab, previousTab: state.embed.previousTab, + activeStreamTab: state.stream.activeTab, + previousStreamTab: state.stream.previousTab, commentClassNames: state.stream.commentClassNames }); @@ -317,6 +319,8 @@ const mapDispatchToProps = (dispatch) => addNotification, setActiveReplyBox, editName, + viewAllComments, + setActiveStreamTab: setActiveTab, }, dispatch); export default compose( diff --git a/client/coral-embed-stream/src/reducers/stream.js b/client/coral-embed-stream/src/reducers/stream.js index 2d6e3e8b2..4f4fcf47f 100644 --- a/client/coral-embed-stream/src/reducers/stream.js +++ b/client/coral-embed-stream/src/reducers/stream.js @@ -20,11 +20,19 @@ const initialState = { assetId: getQueryVariable('asset_id'), assetUrl: getQueryVariable('asset_url'), commentId: getQueryVariable('comment_id'), - commentClassNames: [] + commentClassNames: [], + activeTab: 'all', + previousTab: '', }; export default function stream(state = initialState, action) { switch (action.type) { + case actions.SET_ACTIVE_TAB: + return { + ...state, + activeTab: action.tab, + previousTab: state.activeTab, + }; case authActions.LOGOUT: return { ...state, diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index bd56cb748..4d7d534dd 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -25,24 +25,24 @@ body { min-height: 600px; } -button { - margin: 5px 0px 5px 0px; +.coralButton { + margin: 5px 10px 5px 0px; background: none; padding: 0px; border: none; font-size: inherit; } -button:hover { +.coralButton:hover { border-radius: 2px; color: #767676; } -button i { +.coralButton i { margin-right: 3px; } -hr { +.coralHr { border: 0; height: 0; border-top: 1px solid rgba(0, 0, 0, 0.1); diff --git a/client/coral-framework/styles/reset.css b/client/coral-framework/styles/reset.css new file mode 100644 index 000000000..14ae4e723 --- /dev/null +++ b/client/coral-framework/styles/reset.css @@ -0,0 +1,29 @@ +.buttonReset { + + /* reset button */ + user-select: none; + outline: invert none medium; + border: none; + touch-action: manipulation; + padding: 0; + + position: relative; + overflow: hidden; + + /* Unify anchor and button. */ + cursor: pointer; + display: inline-block; + box-sizing: border-box; + text-align: center; + text-decoration: none; + align-items: flex-start; + vertical-align: middle; + whiteSpace: nowrap; + background: transparent; + font-size: inherit; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0) !important; + &::-moz-focus-inner: { + border: 0; + } +} diff --git a/client/coral-plugin-best/BestButton.css b/client/coral-plugin-best/BestButton.css new file mode 100644 index 000000000..b1f169ff6 --- /dev/null +++ b/client/coral-plugin-best/BestButton.css @@ -0,0 +1,4 @@ +.button { + composes: buttonReset from "coral-framework/styles/reset.css"; + margin: 5px 10px 5px 0px; +} diff --git a/client/coral-plugin-best/BestButton.js b/client/coral-plugin-best/BestButton.js index 59204bbb8..ac6d03fd8 100644 --- a/client/coral-plugin-best/BestButton.js +++ b/client/coral-plugin-best/BestButton.js @@ -3,7 +3,8 @@ import React, {Component, PropTypes} from 'react'; import t from 'coral-framework/services/i18n'; import {Icon} from 'coral-ui'; -import classnames from 'classnames'; +import cn from 'classnames'; +import styles from './BestButton.css'; // tag string for best comments export const BEST_TAG = 'BEST'; @@ -95,7 +96,7 @@ export class BestButton extends Component { return ( diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js deleted file mode 100644 index cca99e729..000000000 --- a/client/coral-plugin-comment-count/CommentCount.js +++ /dev/null @@ -1,17 +0,0 @@ -import React, {PropTypes} from 'react'; - -import t from 'coral-framework/services/i18n'; - -const name = 'coral-plugin-comment-count'; - -const CommentCount = ({count}) => { - return
- {`${count} ${count === 1 ? t('comment_singular') : t('comment_plural')}`} -
; -}; - -CommentCount.propTypes = { - count: PropTypes.number.isRequired -}; - -export default CommentCount; diff --git a/client/coral-plugin-flags/components/styles.css b/client/coral-plugin-flags/components/styles.css index 6de9df387..0990252b4 100644 --- a/client/coral-plugin-flags/components/styles.css +++ b/client/coral-plugin-flags/components/styles.css @@ -1,4 +1,5 @@ .button { + composes: buttonReset from "coral-framework/styles/reset.css"; margin: 5px 0px 5px 10px; } diff --git a/client/coral-plugin-replies/ReplyButton.css b/client/coral-plugin-replies/ReplyButton.css new file mode 100644 index 000000000..b1f169ff6 --- /dev/null +++ b/client/coral-plugin-replies/ReplyButton.css @@ -0,0 +1,4 @@ +.button { + composes: buttonReset from "coral-framework/styles/reset.css"; + margin: 5px 10px 5px 0px; +} diff --git a/client/coral-plugin-replies/ReplyButton.js b/client/coral-plugin-replies/ReplyButton.js index a66e3c156..5c0b6b918 100644 --- a/client/coral-plugin-replies/ReplyButton.js +++ b/client/coral-plugin-replies/ReplyButton.js @@ -2,15 +2,17 @@ import React, {PropTypes} from 'react'; import t from 'coral-framework/services/i18n'; -import classnames from 'classnames'; +import styles from './ReplyButton.css'; +import cn from 'classnames'; const name = 'coral-plugin-replies'; const ReplyButton = ({onClick}) => { return ( + + ); + } +} + +Tab.propTypes = { + className: PropTypes.string, + classNames: PropTypes.shape({ + root: PropTypes.string, + rootActive: PropTypes.string, + rootSub: PropTypes.string, + rootSubActive: PropTypes.string, + button: PropTypes.string, + buttonActive: PropTypes.string, + buttonSub: PropTypes.string, + buttonSubActive: PropTypes.string, + }), + active: PropTypes.bool, + onTabClick: PropTypes.func, + sub: PropTypes.bool, +}; + +export default Tab; diff --git a/client/coral-ui/components/TabBar.css b/client/coral-ui/components/TabBar.css index b5d44d570..181fa1ddc 100644 --- a/client/coral-ui/components/TabBar.css +++ b/client/coral-ui/components/TabBar.css @@ -1,44 +1,14 @@ -.base { +.root { list-style: none; border-bottom: solid 1px #D8D8D8; padding: 0; + margin: 0; } -.base li { - color: #4E5259; - border: solid 1px #D8D8D8; - background: #F0F0F0; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - display: inline-block; - border-bottom: none; - padding: 8px 10px; - margin-right: -1px; - user-select: none; - font-size: 13px; -} - -.base li:hover { - background: #d5d5d5; - cursor: pointer; -} - -.material { +.rootSub { list-style: none; border: none; padding: 0; -} - -.material li { - color: black; - border: none; - border-bottom: solid 2px white; - background: white; - padding: 8px 0; - margin-right: 40px; -} - -.material li:hover { - background: white; - border-bottom: solid 2px grey; + margin: 0; + border-bottom: solid 2px #eee; } diff --git a/client/coral-ui/components/TabBar.js b/client/coral-ui/components/TabBar.js index 08f466ffc..433213a30 100644 --- a/client/coral-ui/components/TabBar.js +++ b/client/coral-ui/components/TabBar.js @@ -1,37 +1,66 @@ import React from 'react'; import styles from './TabBar.css'; +import cn from 'classnames'; +import Tab from './Tab'; +import PropTypes from 'prop-types'; class TabBar extends React.Component { - constructor(props) { - super(props); - this.handleClickTab = this.handleClickTab.bind(this); - } - handleClickTab(tabId) { - if (this.props.onChange) { - this.props.onChange(tabId); - } + getRootClassName({className, classNames = {}, sub} = this.props) { + return cn( + 'talk-tab-bar', + className, + { + [classNames.root || styles.root]: !sub, + [classNames.rootSub || styles.rootSub]: sub, + } + ); } render() { - const {children, activeTab, cStyle = 'base'} = this.props; + const { + children, + activeTab, + tabClassNames, + classNames: _a, + onTabClick: _b, + 'aria-controls': ariaControls, + sub, + ...rest, + } = this.props; + return ( -
-
    - {React.Children.toArray(children) - .filter((child) => !child.props.restricted) - .map((child, tabId) => - React.cloneElement(child, { - tabId, - active: child.props.id === activeTab, - onTabClick: this.handleClickTab, - cStyle - }) - )} -
-
+
    + {React.Children.toArray(children) + .map((child, i) => + React.cloneElement(child, { + tabId: (child.props.tabId !== undefined) ? child.props.tabId : i, + active: child.props.tabId === activeTab, + onTabClick: this.props.onTabClick, + classNames: tabClassNames, + 'aria-controls': ariaControls, + sub, + }) + )} +
); } } +TabBar.propTypes = { + className: PropTypes.string, + classNames: PropTypes.shape({ + root: PropTypes.string, + rootSub: PropTypes.string, + }), + tabClassNames: Tab.propTypes.classNames, + activeTab: PropTypes.string, + onTabClick: PropTypes.func, + sub: PropTypes.bool, +}; + export default TabBar; diff --git a/client/coral-ui/components/TabContent.css b/client/coral-ui/components/TabContent.css new file mode 100644 index 000000000..debafa97c --- /dev/null +++ b/client/coral-ui/components/TabContent.css @@ -0,0 +1,3 @@ +.root { + padding-top: 10px; +} diff --git a/client/coral-ui/components/TabContent.js b/client/coral-ui/components/TabContent.js index 737c9a5e6..768000cb6 100644 --- a/client/coral-ui/components/TabContent.js +++ b/client/coral-ui/components/TabContent.js @@ -1,6 +1,33 @@ import React from 'react'; +import cn from 'classnames'; +import PropTypes from 'prop-types'; +import styles from './TabContent.css'; -export default ({children, show = true}) => ( - show ?
{children}
: null +function getRootClassName(className) { + return cn('talk-tab-content', className, styles.root); +} + +const TabContent = ({children, className, activeTab, sub, ...rest}) => ( +
+ { + React.Children.toArray(children) + .filter((child) => child.props.tabId === activeTab) + .map((child, i) => + React.cloneElement(child, { + tabId: (child.props.tabId !== undefined) ? child.props.tabId : i, + sub, + })) + } +
); +TabContent.propTypes = { + className: PropTypes.string, + activeTab: PropTypes.string, + sub: PropTypes.bool, +}; + +export default TabContent; diff --git a/client/coral-ui/components/TabCount.css b/client/coral-ui/components/TabCount.css new file mode 100644 index 000000000..f95792a0b --- /dev/null +++ b/client/coral-ui/components/TabCount.css @@ -0,0 +1,18 @@ + +.root, .rootSub { + display: inline-block; + position: relative; + top: -2px; + background: #616161; + color: white; + font-weight: normal; + font-size: 10px; + padding: 2px; + margin-left: 2px; + margin-top: -2px; + min-width: 20px; +} + +.rootSubActive { + background: #10589b; +} diff --git a/client/coral-ui/components/TabCount.js b/client/coral-ui/components/TabCount.js new file mode 100644 index 000000000..89ccf1479 --- /dev/null +++ b/client/coral-ui/components/TabCount.js @@ -0,0 +1,29 @@ +import React from 'react'; +import cn from 'classnames'; +import styles from './TabCount.css'; + +function getNumber(no) { + let result = Number.parseInt(no); + if (no >= 1000) { + result = `${Math.round(result / 100) / 10}k`; + } + return result; +} + +function getRootClassName({className, active, sub}) { + return cn( + 'talk-tab-count', + className, + { + [styles.root]: !sub, + [styles.rootSub]: sub, + [styles.rootActive]: active && !sub, + [styles.rootSubActive]: active && sub, + 'talk-tab-active': active, + } + ); +} + +export default ({children, active, sub, className}) => ( + {getNumber(children)} +); diff --git a/client/coral-ui/components/TabPane.js b/client/coral-ui/components/TabPane.js new file mode 100644 index 000000000..1bdf19f0a --- /dev/null +++ b/client/coral-ui/components/TabPane.js @@ -0,0 +1,24 @@ +import React from 'react'; +import cn from 'classnames'; +import PropTypes from 'prop-types'; + +function getRootClassName(className) { + return cn('talk-pane', className); +} + +const TabPane = ({children, className, tabId: _a, sub: _b, ...rest}) => ( +
+ {children} +
+); + +TabPane.propTypes = { + className: PropTypes.string, + tabId: PropTypes.string, + sub: PropTypes.bool, +}; + +export default TabPane; diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js index 3e9456727..8a538d120 100644 --- a/client/coral-ui/index.js +++ b/client/coral-ui/index.js @@ -4,7 +4,9 @@ export {default as CoralLogo} from './components/CoralLogo'; export {default as FabButton} from './components/FabButton'; export {default as TabBar} from './components/TabBar'; export {default as Tab} from './components/Tab'; +export {default as TabCount} from './components/TabCount'; export {default as TabContent} from './components/TabContent'; +export {default as TabPane} from './components/TabPane'; export {default as Button} from './components/Button'; export {default as Spinner} from './components/Spinner'; export {default as Tooltip} from './components/Tooltip'; diff --git a/locales/en.yml b/locales/en.yml index 44db5e1b8..e3bf42e5e 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -2,6 +2,7 @@ en: your_account_has_been_suspended: Your account has been temporarily suspended. your_account_has_been_banned: Your account has been banned. your_username_has_been_rejected: Your account has been suspended because your username has been deemed inappropriate. To restore your account please enter a new username. + embed_comments_tab: Comments bandialog: are_you_sure: "Are you sure you would like to ban {0}?" ban_user: "Ban User?" diff --git a/locales/es.yml b/locales/es.yml index a6b1bc4cd..fe6030b74 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -2,6 +2,7 @@ es: your_account_has_been_suspended: Su cuenta ha sido temporalmente suspendida. your_account_has_been_banned: Su cuenta ha sido suspendida. your_username_has_been_rejected: Su cuenta ha sido suspendida porque tu nombre de usuario ha sido considerado no apropiado para el espacio. Para recuperar la cuenta, por favor ingresar un nuevo nombre de usuario. + embed_comments_tab: Comentarios bandialog: are_you_sure: "¿Estás segura que quieres suspender a {0}?" ban_user: "¿Quieres suspender el Usuario?" diff --git a/plugins/coral-plugin-like/client/LikeButton.js b/plugins/coral-plugin-like/client/LikeButton.js index 7d17ef056..fd5c7d2c8 100644 --- a/plugins/coral-plugin-like/client/LikeButton.js +++ b/plugins/coral-plugin-like/client/LikeButton.js @@ -46,7 +46,7 @@ class LikeButton extends React.Component { onClick={this.handleClick} > {t(alreadyReacted ? 'coral-plugin-like.liked' : 'coral-plugin-like.like')} - + {count > 0 && count}
diff --git a/plugins/coral-plugin-like/client/styles.css b/plugins/coral-plugin-like/client/styles.css index cb372fa47..6e9fdb517 100644 --- a/plugins/coral-plugin-like/client/styles.css +++ b/plugins/coral-plugin-like/client/styles.css @@ -23,3 +23,7 @@ } } } + +.icon { + padding: 0 2px; +} diff --git a/plugins/coral-plugin-love/client/LoveButton.js b/plugins/coral-plugin-love/client/LoveButton.js index 7450ad668..d97803195 100644 --- a/plugins/coral-plugin-love/client/LoveButton.js +++ b/plugins/coral-plugin-love/client/LoveButton.js @@ -46,7 +46,7 @@ class LoveButton extends React.Component { onClick={this.handleClick} > {t(alreadyReacted ? 'coral-plugin-love.loved' : 'coral-plugin-love.love')} - + {count > 0 && count}
diff --git a/plugins/coral-plugin-love/client/styles.css b/plugins/coral-plugin-love/client/styles.css index e16e17ca4..316104e95 100644 --- a/plugins/coral-plugin-love/client/styles.css +++ b/plugins/coral-plugin-love/client/styles.css @@ -23,3 +23,7 @@ } } } + +.icon { + padding: 0 2px; +} diff --git a/plugins/coral-plugin-respect/client/styles.css b/plugins/coral-plugin-respect/client/styles.css index f0b780b86..e984dc3af 100644 --- a/plugins/coral-plugin-respect/client/styles.css +++ b/plugins/coral-plugin-respect/client/styles.css @@ -26,5 +26,5 @@ } .icon { - padding: 0 5px; + padding: 0 2px; } diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css index 47cda9a1a..a657bbeea 100644 --- a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css +++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css @@ -1,11 +1,9 @@ .root { - float: right; - text-align: right; + display: flex; + justify-content: flex-end; position: relative; - display: inline-block; - min-width: 220px; + width: 100%; z-index: 10; - position: relative; cursor: pointer; } diff --git a/plugins/talk-plugin-permalink/client/components/styles.css b/plugins/talk-plugin-permalink/client/components/styles.css index f14e87881..244cc2acc 100644 --- a/plugins/talk-plugin-permalink/client/components/styles.css +++ b/plugins/talk-plugin-permalink/client/components/styles.css @@ -42,8 +42,8 @@ } .button { + composes: buttonReset from "coral-framework/styles/reset.css"; margin: 5px 0px 5px 10px; - cursor: pointer; } .copyButton { From ef172ae5f8d2b09b35c0600cc3cb7e8f2ba6d532 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 16:48:34 +0700 Subject: [PATCH 11/32] Change streamBox to streamFilter --- .../src/components/Stream.css | 6 +++ .../src/components/Stream.js | 10 ++-- client/coral-embed-stream/style/default.css | 4 -- .../client/components/ViewingOptions.css | 14 ++--- .../client/components/ViewingOptions.js | 53 +++++++++++-------- .../client/index.js | 2 +- 6 files changed, 51 insertions(+), 38 deletions(-) diff --git a/client/coral-embed-stream/src/components/Stream.css b/client/coral-embed-stream/src/components/Stream.css index f2c4c48c5..6c8ca66f0 100644 --- a/client/coral-embed-stream/src/components/Stream.css +++ b/client/coral-embed-stream/src/components/Stream.css @@ -10,3 +10,9 @@ .tabPanel { margin-top: 8px; } + +.filterWrapper { + position: absolute; + right: 0; + margin-top: 6px; +} diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 1c84c2d47..97678a597 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -12,6 +12,7 @@ import t, {timeago} from 'coral-framework/services/i18n'; import CommentBox from 'coral-plugin-commentbox/CommentBox'; import QuestionBox from 'coral-plugin-questionbox/QuestionBox'; import {Button, TabBar, Tab, TabCount, TabContent, TabPane} from 'coral-ui'; +import cn from 'classnames'; import {getTopLevelParent} from '../graphql/utils'; import AllCommentsPane from './AllCommentsPane'; @@ -163,10 +164,6 @@ class Stream extends React.Component { /> )} -
- -
- {/* the highlightedComment is isolated after the user followed a permalink */} {highlightedComment ? :
+
+ +
Featured diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 4d7d534dd..023a2cc3d 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -181,10 +181,6 @@ body { display: none; } -.talk-stream-wrapper-box { - padding: 10px 0; -} - .talk-stream-comments-container { position: relative; } diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css index a657bbeea..9c302d07e 100644 --- a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css +++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css @@ -1,18 +1,17 @@ .root { - display: flex; - justify-content: flex-end; - position: relative; - width: 100%; - z-index: 10; - cursor: pointer; +} + +.button { + composes: buttonReset from "coral-framework/styles/reset.css"; } .list { background: white; position: absolute; box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.15); - right: 3px; + right: 0px; top: 20px; + z-index: 10; } .list > ul, .list > ul > li { @@ -23,4 +22,5 @@ .list > ul > li { padding: 10px; list-style: none; + white-space: nowrap; } diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js index dc63a2373..b64123fdb 100644 --- a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js +++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js @@ -1,7 +1,7 @@ import React from 'react'; import cn from 'classnames'; import styles from './ViewingOptions.css'; -import {Slot} from 'plugin-api/beta/client/components'; +import {Slot, ClickOutside} from 'plugin-api/beta/client/components'; import {Icon} from 'plugin-api/beta/client/components/ui'; const ViewingOptions = (props) => { @@ -12,29 +12,38 @@ const ViewingOptions = (props) => { props.closeViewingOptions(); } }; + + const handleClickOutside = () => { + if (props.open) { + props.closeViewingOptions(); + } + }; + return ( -
-
- Viewing Options - {props.open ? : } - + +
+
+ +
+ { + props.open ? ( +
+
    + { + React.Children.map(, (component) => { + return React.createElement('li', { + className: 'coral-plugin-viewing-options-item' + }, component); + }) + } +
+
+ ) : null + }
- { - props.open ? ( -
-
    - { - React.Children.map(, (component) => { - return React.createElement('li', { - className: 'coral-plugin-viewing-options-item' - }, component); - }) - } -
-
- ) : null - } -
+ ); }; diff --git a/plugins/coral-plugin-viewing-options/client/index.js b/plugins/coral-plugin-viewing-options/client/index.js index 519ce42aa..4e9001c1b 100644 --- a/plugins/coral-plugin-viewing-options/client/index.js +++ b/plugins/coral-plugin-viewing-options/client/index.js @@ -4,6 +4,6 @@ import reducer from './reducer'; export default { reducer, slots: { - streamBox: [ViewingOptions] + streamFilter: [ViewingOptions] } }; From 362c07218180fedce5934e9bea93d13ddbfc3a42 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 16:54:55 +0700 Subject: [PATCH 12/32] If only one tab left make it not clickable --- client/coral-ui/components/Tab.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/coral-ui/components/Tab.css b/client/coral-ui/components/Tab.css index d1ef5ea9c..b5bf2463c 100644 --- a/client/coral-ui/components/Tab.css +++ b/client/coral-ui/components/Tab.css @@ -62,3 +62,8 @@ margin-bottom: 0px; } +.root:only-child .button, .rootSub:only-child .buttonSub { + cursor: default; + pointer-events: none; +} + From aac47c46865cab213e6dd4dcecb7123af2265570 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 17:57:45 +0700 Subject: [PATCH 13/32] Add margin --- client/coral-embed-stream/src/components/Stream.css | 4 ++++ client/coral-embed-stream/src/components/Stream.js | 2 +- plugins/coral-plugin-auth/client/components/styles.css | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client/coral-embed-stream/src/components/Stream.css b/client/coral-embed-stream/src/components/Stream.css index 6c8ca66f0..afedab0eb 100644 --- a/client/coral-embed-stream/src/components/Stream.css +++ b/client/coral-embed-stream/src/components/Stream.css @@ -16,3 +16,7 @@ right: 0; margin-top: 6px; } + +.tabContainer { + margin-top: 28px; +} diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 97678a597..ae457ffbe 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -195,7 +195,7 @@ class Stream extends React.Component { editComment={editComment} liveUpdates={true} /> - :
+ :
diff --git a/plugins/coral-plugin-auth/client/components/styles.css b/plugins/coral-plugin-auth/client/components/styles.css index c8fbf9a30..735f34d31 100644 --- a/plugins/coral-plugin-auth/client/components/styles.css +++ b/plugins/coral-plugin-auth/client/components/styles.css @@ -66,7 +66,7 @@ input.error{ } .userBox { - padding: 10px 0 20px; + margin: 10px 0 20px; letter-spacing: 0.1px; } From ab72995a2c63ad2f6bd818014e9d5ef745c2bf2e Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 20:46:28 +0700 Subject: [PATCH 14/32] Implement streamTabs and streamTabPanes slot --- .eslintignore | 1 + .gitignore | 1 + .../src/components/Stream.js | 26 ++++++++++++++----- .../src/containers/Stream.js | 10 +++++++ client/coral-framework/helpers/plugins.js | 26 ++++++++++++++----- .../coral-framework/loaders/plugins-loader.js | 2 +- client/coral-ui/components/Tab.css | 1 + plugins/talk-plugin-featured/client/.babelrc | 14 ++++++++++ .../client/.eslintrc.json | 23 ++++++++++++++++ .../client/components/Tab.js | 8 ++++++ .../client/components/TabPane.js | 14 ++++++++++ .../client/containers/Tab.js | 16 ++++++++++++ .../client/containers/TabPane.js | 21 +++++++++++++++ plugins/talk-plugin-featured/client/index.js | 9 +++++++ plugins/talk-plugin-featured/index.js | 2 ++ 15 files changed, 161 insertions(+), 13 deletions(-) create mode 100644 plugins/talk-plugin-featured/client/.babelrc create mode 100644 plugins/talk-plugin-featured/client/.eslintrc.json create mode 100644 plugins/talk-plugin-featured/client/components/Tab.js create mode 100644 plugins/talk-plugin-featured/client/components/TabPane.js create mode 100644 plugins/talk-plugin-featured/client/containers/Tab.js create mode 100644 plugins/talk-plugin-featured/client/containers/TabPane.js create mode 100644 plugins/talk-plugin-featured/client/index.js create mode 100644 plugins/talk-plugin-featured/index.js diff --git a/.eslintignore b/.eslintignore index 3a259c106..b58e4f058 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,4 +13,5 @@ plugins/* !plugins/coral-plugin-viewing-options !plugins/coral-plugin-comment-content !plugins/talk-plugin-permalink +!plugins/talk-plugin-featured node_modules diff --git a/.gitignore b/.gitignore index 0be1266c6..0e809b43a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,5 +26,6 @@ plugins/* !plugins/coral-plugin-viewing-options !plugins/coral-plugin-comment-content !plugins/talk-plugin-permalink +!plugins/talk-plugin-featured **/node_modules/* diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index ae457ffbe..6205a2a10 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -9,6 +9,7 @@ import {ModerationLink} from 'coral-plugin-moderation'; import RestrictedMessageBox from 'coral-framework/components/RestrictedMessageBox'; import t, {timeago} from 'coral-framework/services/i18n'; +import {getSlotComponents} from 'coral-framework/helpers/plugins'; import CommentBox from 'coral-plugin-commentbox/CommentBox'; import QuestionBox from 'coral-plugin-questionbox/QuestionBox'; import {Button, TabBar, Tab, TabCount, TabContent, TabPane} from 'coral-ui'; @@ -202,17 +203,30 @@ class Stream extends React.Component {
- - Featured - + {getSlotComponents('streamTabs').map((PluginComponent) => ( + + + + ))} All Comments {totalCommentCount} - - TODO - + {getSlotComponents('streamTabPanes').map((PluginComponent) => ( + + + + ))} !pluginConfig || !pluginConfig[o.plugin] || !pluginConfig[o.plugin].disable_components) - + .filter((o) => !pluginConfig || !pluginConfig[o.name] || !pluginConfig[o.name].disable_components) .filter((o) => o.module.slots[slot]) - .map((o) => o.module.slots[slot])); + .map((o) => o.module.slots[slot]) + ); } export function isSlotEmpty(slot) { @@ -113,7 +112,22 @@ export function injectPluginsReducers() { const reducers = merge( ...plugins .filter((o) => o.module.reducer) - .map((o) => ({[camelize(o.plugin)] : o.module.reducer})) + .map((o) => ({[camelize(o.name)] : o.module.reducer})) ); injectReducers(reducers); } + +function addMetaDataToSlotComponents() { + + // Add talkPluginName to Slot Components. + plugins.forEach((plugin) => { + const slots = plugin.module.slots; + slots && Object.keys(slots).forEach((slot) => { + slots[slot].forEach((component) => { + component.talkPluginName = plugin.name; + }); + }); + }); +} + +addMetaDataToSlotComponents(); diff --git a/client/coral-framework/loaders/plugins-loader.js b/client/coral-framework/loaders/plugins-loader.js index 4feae42ca..a9fed95ab 100644 --- a/client/coral-framework/loaders/plugins-loader.js +++ b/client/coral-framework/loaders/plugins-loader.js @@ -22,7 +22,7 @@ module.exports = function(source) { const config = this.exec(source, this.resourcePath); const plugins = getPluginList(config).map((plugin) => `{ module: require('${plugin}/client'), - plugin: '${plugin}' + name: '${plugin}' }`); return stripIndent` diff --git a/client/coral-ui/components/Tab.css b/client/coral-ui/components/Tab.css index b5bf2463c..b3c026b3d 100644 --- a/client/coral-ui/components/Tab.css +++ b/client/coral-ui/components/Tab.css @@ -60,6 +60,7 @@ font-weight: bold; border-bottom: solid 3px #10589b; margin-bottom: 0px; + padding: 6px 10px; } .root:only-child .button, .rootSub:only-child .buttonSub { diff --git a/plugins/talk-plugin-featured/client/.babelrc b/plugins/talk-plugin-featured/client/.babelrc new file mode 100644 index 000000000..63b1c53de --- /dev/null +++ b/plugins/talk-plugin-featured/client/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "add-module-exports", + "transform-class-properties", + "transform-decorators-legacy", + "transform-object-assign", + "transform-object-rest-spread", + "transform-async-to-generator", + "transform-react-jsx" + ] +} diff --git a/plugins/talk-plugin-featured/client/.eslintrc.json b/plugins/talk-plugin-featured/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/plugins/talk-plugin-featured/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-featured/client/components/Tab.js b/plugins/talk-plugin-featured/client/components/Tab.js new file mode 100644 index 000000000..f5306ebaa --- /dev/null +++ b/plugins/talk-plugin-featured/client/components/Tab.js @@ -0,0 +1,8 @@ +import React from 'react'; +import {TabCount} from 'plugin-api/beta/client/components/ui'; + +export default ({active, asset: {recentComments}}) => ( + + Featured {recentComments.length} + +); diff --git a/plugins/talk-plugin-featured/client/components/TabPane.js b/plugins/talk-plugin-featured/client/components/TabPane.js new file mode 100644 index 000000000..7e6f5834b --- /dev/null +++ b/plugins/talk-plugin-featured/client/components/TabPane.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export default ({asset: {recentComments}}) => ( +
+ {recentComments.map((comment) => ( +

+

{comment.user.username}
+
+ {comment.body} +
+

+ ))} +
+); diff --git a/plugins/talk-plugin-featured/client/containers/Tab.js b/plugins/talk-plugin-featured/client/containers/Tab.js new file mode 100644 index 000000000..2a38933c6 --- /dev/null +++ b/plugins/talk-plugin-featured/client/containers/Tab.js @@ -0,0 +1,16 @@ +import {compose, gql} from 'react-apollo'; +import withFragments from 'coral-framework/hocs/withFragments'; +import Tab from '../components/Tab'; + +const enhance = compose( + withFragments({ + asset: gql` + fragment TalkFeatured_Tab_asset on Asset { + recentComments { + id + } + }`, + }), +); + +export default enhance(Tab); diff --git a/plugins/talk-plugin-featured/client/containers/TabPane.js b/plugins/talk-plugin-featured/client/containers/TabPane.js new file mode 100644 index 000000000..13046cb00 --- /dev/null +++ b/plugins/talk-plugin-featured/client/containers/TabPane.js @@ -0,0 +1,21 @@ +import {compose, gql} from 'react-apollo'; +import withFragments from 'coral-framework/hocs/withFragments'; +import TabPane from '../components/TabPane'; + +const enhance = compose( + withFragments({ + asset: gql` + fragment TalkFeatured_TabPane_asset on Asset { + recentComments { + id + body + user { + id + username + } + } + }`, + }), +); + +export default enhance(TabPane); diff --git a/plugins/talk-plugin-featured/client/index.js b/plugins/talk-plugin-featured/client/index.js new file mode 100644 index 000000000..69dfaa6cb --- /dev/null +++ b/plugins/talk-plugin-featured/client/index.js @@ -0,0 +1,9 @@ +import Tab from './containers/Tab'; +import TabPane from './containers/TabPane'; + +export default { + slots: { + streamTabs: [Tab], + streamTabPanes: [TabPane], + } +}; diff --git a/plugins/talk-plugin-featured/index.js b/plugins/talk-plugin-featured/index.js new file mode 100644 index 000000000..85dfb349b --- /dev/null +++ b/plugins/talk-plugin-featured/index.js @@ -0,0 +1,2 @@ +module.exports = {}; + From 34a4a005d1b5600387916a15aa1b1c512c8b8e25 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 21:12:52 +0700 Subject: [PATCH 15/32] Add margin to highlighted comment --- .../src/components/Stream.css | 4 ++ .../src/components/Stream.js | 62 ++++++++++--------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/client/coral-embed-stream/src/components/Stream.css b/client/coral-embed-stream/src/components/Stream.css index afedab0eb..06f1efd1f 100644 --- a/client/coral-embed-stream/src/components/Stream.css +++ b/client/coral-embed-stream/src/components/Stream.css @@ -17,6 +17,10 @@ margin-top: 6px; } +.highlightedContainer { + margin-top: 28px; +} + .tabContainer { margin-top: 28px; } diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index 6205a2a10..67e22ab6a 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -167,35 +167,39 @@ class Stream extends React.Component { {/* the highlightedComment is isolated after the user followed a permalink */} {highlightedComment - ? + ? ( +
+ +
+ ) :
Date: Mon, 10 Jul 2017 21:53:55 +0700 Subject: [PATCH 16/32] Don't use div inside p --- plugins/talk-plugin-featured/client/components/TabPane.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/talk-plugin-featured/client/components/TabPane.js b/plugins/talk-plugin-featured/client/components/TabPane.js index 7e6f5834b..54ac013e6 100644 --- a/plugins/talk-plugin-featured/client/components/TabPane.js +++ b/plugins/talk-plugin-featured/client/components/TabPane.js @@ -3,12 +3,13 @@ import React from 'react'; export default ({asset: {recentComments}}) => (
{recentComments.map((comment) => ( -

+

{comment.user.username}
{comment.body}
-

+
+
))}
); From 883dfedfab4db9e4a75abcfe1149c9a4169b8bce Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 21:57:25 +0700 Subject: [PATCH 17/32] Fix new count styles --- .../src/components/NewCount.js | 5 +++-- client/coral-embed-stream/style/default.css | 16 ++-------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/client/coral-embed-stream/src/components/NewCount.js b/client/coral-embed-stream/src/components/NewCount.js index 8f3b77dcb..328c76481 100644 --- a/client/coral-embed-stream/src/components/NewCount.js +++ b/client/coral-embed-stream/src/components/NewCount.js @@ -1,4 +1,5 @@ import React, {PropTypes} from 'react'; +import {Button} from 'coral-ui'; import t from 'coral-framework/services/i18n'; @@ -6,11 +7,11 @@ const NewCount = ({count, loadMore}) => { return
{ count ? - + : null }
; diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 023a2cc3d..2a73c761c 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -422,9 +422,8 @@ button.comment__action-button[disabled], .talk-new-comments { width: 100%; - display: flex; - justify-content: center; - cursor: pointer; + text-align: center; + margin: 4px 0; } .talk-load-more-replies { @@ -433,12 +432,6 @@ button.comment__action-button[disabled], box-sizing: border-box; } -.talk-new-comments { - position: relative; - top: 1.8em; - z-index: 100; -} - .talk-load-more-replies .talk-load-more-button { background-color: transparent; color: #979797; @@ -451,11 +444,6 @@ button.comment__action-button[disabled], color: white; } -.talk-new-comments button.talk-load-more{ - width: initial; -} - - @media (min-device-width : 300px) and (max-device-width : 420px) { .commentActionsLeft.comment__action-container .coral-plugin-replies-reply-button { visibility: collapse; From 55e70dfbec1f6cb0456c81f841239757cc6e543e Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 22:13:04 +0700 Subject: [PATCH 18/32] Fix perma view live updates --- client/coral-embed-stream/src/graphql/utils.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/client/coral-embed-stream/src/graphql/utils.js b/client/coral-embed-stream/src/graphql/utils.js index 03d9fe02a..523cc571f 100644 --- a/client/coral-embed-stream/src/graphql/utils.js +++ b/client/coral-embed-stream/src/graphql/utils.js @@ -1,10 +1,18 @@ import update from 'immutability-helper'; -import {THREADING_LEVEL} from '../constants/stream'; +function determineCommentDepth(comment) { + let depth = 0; + let cur = comment; + while (cur.parent) { + cur = cur.parent; + depth++; + } + return depth; +} function applyToCommentsOrigin(root, callback) { if (root.comment) { let comment = root.comment; - for (let depth = 0; depth <= THREADING_LEVEL; depth++) { + for (let depth = 0; depth <= determineCommentDepth(comment); depth++) { let changes = {$apply: (node) => node ? callback(node) : node}; for (let i = 0; i < depth; i++) { changes = {parent: changes}; From e10c1f1a1f45d0b8e0c305ed96616081a5d4d723 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 22:45:28 +0700 Subject: [PATCH 19/32] Docs --- client/coral-ui/components/Tab.js | 21 +++++++++++++++++++++ client/coral-ui/components/TabBar.js | 20 ++++++++++++++++++++ client/coral-ui/components/TabContent.js | 10 ++++++++++ client/coral-ui/components/TabCount.js | 20 +++++++++++++++++++- client/coral-ui/components/TabPane.js | 10 ++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) diff --git a/client/coral-ui/components/Tab.js b/client/coral-ui/components/Tab.js index 75b30085a..4fd85b303 100644 --- a/client/coral-ui/components/Tab.js +++ b/client/coral-ui/components/Tab.js @@ -3,6 +3,10 @@ import styles from './Tab.css'; import cn from 'classnames'; import PropTypes from 'prop-types'; +/** + * The `Tab` component is used inside the `TabBar` Component, to + * render tabs. + */ class Tab extends React.Component { handleTabClick = () => { if (this.props.onTabClick) { @@ -69,7 +73,11 @@ class Tab extends React.Component { } Tab.propTypes = { + + // className to be added to the root element. className: PropTypes.string, + + // classNames allows full design customization of the component. classNames: PropTypes.shape({ root: PropTypes.string, rootActive: PropTypes.string, @@ -80,9 +88,22 @@ Tab.propTypes = { buttonSub: PropTypes.string, buttonSubActive: PropTypes.string, }), + + // active indicates that this tab is currently active. + // This is injected by the `TabBar` component. active: PropTypes.bool, + + // onTabClick is fired whenever the tab was clicked. The tabId is passed as + // the first argument. onTabClick: PropTypes.func, + + // Sub indicates that this is a tab of a sub-tab-panel. + // This is injected by the `TabBar` component. sub: PropTypes.bool, + + // `aria-controls` should be set to the `id` of the `TabContent` for accessibility. + // This is injected by the `TabBar` component. + 'aria-controls': PropTypes.string, }; export default Tab; diff --git a/client/coral-ui/components/TabBar.js b/client/coral-ui/components/TabBar.js index 433213a30..1a51c60f2 100644 --- a/client/coral-ui/components/TabBar.js +++ b/client/coral-ui/components/TabBar.js @@ -4,6 +4,10 @@ import cn from 'classnames'; import Tab from './Tab'; import PropTypes from 'prop-types'; +/** + * The `TabBar` component accepts `Tab` components to create + * a tab bar. + */ class TabBar extends React.Component { getRootClassName({className, classNames = {}, sub} = this.props) { @@ -52,15 +56,31 @@ class TabBar extends React.Component { } TabBar.propTypes = { + + // className to be added to the root element. className: PropTypes.string, + + // classNames allows full design customization of the component. classNames: PropTypes.shape({ root: PropTypes.string, rootSub: PropTypes.string, }), + + // classNames to be passed to the children. tabClassNames: Tab.propTypes.classNames, + + // activeTab should be set to the currently active tabId. activeTab: PropTypes.string, + + // onTabClick is fired whenever the tab was clicked. The tabId is passed as + // the first argument. onTabClick: PropTypes.func, + + // Sub indicates that this is a sub-tab-panel. sub: PropTypes.bool, + + // `aria-controls` should be set to the `id` of the `TabContent` for accessibility. + 'aria-controls': PropTypes.string, }; export default TabBar; diff --git a/client/coral-ui/components/TabContent.js b/client/coral-ui/components/TabContent.js index 768000cb6..99676f7cc 100644 --- a/client/coral-ui/components/TabContent.js +++ b/client/coral-ui/components/TabContent.js @@ -7,6 +7,10 @@ function getRootClassName(className) { return cn('talk-tab-content', className, styles.root); } +/** + * The `TabContent` component accepts `TabPane` components to render + * the content of a `Tab`. + */ const TabContent = ({children, className, activeTab, sub, ...rest}) => (
( ); TabContent.propTypes = { + + // className to be added to the root element. className: PropTypes.string, + + // activeTab should be set to the currently active tabId. activeTab: PropTypes.string, + + // Sub indicates that this component belongs to a sub-tab-panel. sub: PropTypes.bool, }; diff --git a/client/coral-ui/components/TabCount.js b/client/coral-ui/components/TabCount.js index 89ccf1479..01a3840a9 100644 --- a/client/coral-ui/components/TabCount.js +++ b/client/coral-ui/components/TabCount.js @@ -1,6 +1,7 @@ import React from 'react'; import cn from 'classnames'; import styles from './TabCount.css'; +import PropTypes from 'prop-types'; function getNumber(no) { let result = Number.parseInt(no); @@ -24,6 +25,23 @@ function getRootClassName({className, active, sub}) { ); } -export default ({children, active, sub, className}) => ( +/** + * The `TabCount` renders a count number next to a tab name. + */ +const TabCount = ({children, active, sub, className}) => ( {getNumber(children)} ); + +TabCount.propTypes = { + + // className to be added to the root element. + className: PropTypes.string, + + // active indicates that the related tab is currently active. + active: PropTypes.bool, + + // Sub indicates that this component belongs to a sub-tab-panel. + sub: PropTypes.bool, +}; + +export default TabCount; diff --git a/client/coral-ui/components/TabPane.js b/client/coral-ui/components/TabPane.js index 1bdf19f0a..16750b335 100644 --- a/client/coral-ui/components/TabPane.js +++ b/client/coral-ui/components/TabPane.js @@ -6,6 +6,10 @@ function getRootClassName(className) { return cn('talk-pane', className); } +/** + * The `TabPane` component is used inside the `TabContent` component to render + * the content of a `Tab`. + */ const TabPane = ({children, className, tabId: _a, sub: _b, ...rest}) => (
( ); TabPane.propTypes = { + + // className to be added to the root element. className: PropTypes.string, + tabId: PropTypes.string, + + // Sub indicates that this component belongs to a sub-tab-panel. + // This is injected by the `TabContent` component. sub: PropTypes.bool, }; From 8960f67a4ddc337a8450959e87ce74fd8bcb14a7 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 10 Jul 2017 22:49:06 +0700 Subject: [PATCH 20/32] Add todo comments --- plugins/talk-plugin-featured/client/components/Tab.js | 1 + plugins/talk-plugin-featured/client/components/TabPane.js | 1 + plugins/talk-plugin-featured/client/containers/Tab.js | 1 + plugins/talk-plugin-featured/client/containers/TabPane.js | 1 + 4 files changed, 4 insertions(+) diff --git a/plugins/talk-plugin-featured/client/components/Tab.js b/plugins/talk-plugin-featured/client/components/Tab.js index f5306ebaa..af11368b9 100644 --- a/plugins/talk-plugin-featured/client/components/Tab.js +++ b/plugins/talk-plugin-featured/client/components/Tab.js @@ -1,6 +1,7 @@ import React from 'react'; import {TabCount} from 'plugin-api/beta/client/components/ui'; +// TODO: This is just example code, and needs to replaced by an actual implementation. export default ({active, asset: {recentComments}}) => ( Featured {recentComments.length} diff --git a/plugins/talk-plugin-featured/client/components/TabPane.js b/plugins/talk-plugin-featured/client/components/TabPane.js index 54ac013e6..107c806b4 100644 --- a/plugins/talk-plugin-featured/client/components/TabPane.js +++ b/plugins/talk-plugin-featured/client/components/TabPane.js @@ -1,5 +1,6 @@ import React from 'react'; +// TODO: This is just example code, and needs to replaced by an actual implementation. export default ({asset: {recentComments}}) => (
{recentComments.map((comment) => ( diff --git a/plugins/talk-plugin-featured/client/containers/Tab.js b/plugins/talk-plugin-featured/client/containers/Tab.js index 2a38933c6..0bcb49e13 100644 --- a/plugins/talk-plugin-featured/client/containers/Tab.js +++ b/plugins/talk-plugin-featured/client/containers/Tab.js @@ -2,6 +2,7 @@ import {compose, gql} from 'react-apollo'; import withFragments from 'coral-framework/hocs/withFragments'; import Tab from '../components/Tab'; +// TODO: This is just example code, and needs to replaced by an actual implementation. const enhance = compose( withFragments({ asset: gql` diff --git a/plugins/talk-plugin-featured/client/containers/TabPane.js b/plugins/talk-plugin-featured/client/containers/TabPane.js index 13046cb00..19c4d855d 100644 --- a/plugins/talk-plugin-featured/client/containers/TabPane.js +++ b/plugins/talk-plugin-featured/client/containers/TabPane.js @@ -2,6 +2,7 @@ import {compose, gql} from 'react-apollo'; import withFragments from 'coral-framework/hocs/withFragments'; import TabPane from '../components/TabPane'; +// TODO: This is just example code, and needs to replaced by an actual implementation. const enhance = compose( withFragments({ asset: gql` From a686bc75a7710c7dc1cd9b04c2d1e2d10a3b9e8c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 10 Jul 2017 10:30:20 -0600 Subject: [PATCH 21/32] Some changes to server side stuff + tempalte location --- bin/cli-plugins | 10 +++++----- {scripts => bin}/templates/plugin/client/.babelrc | 0 .../templates/plugin/client/.eslintrc.json | 0 .../plugin/client/components/MyPluginComponent.css | 0 .../plugin/client/components/MyPluginComponent.js | 0 {scripts => bin}/templates/plugin/client/index.js | 0 .../templates/plugin/client/translations.yml | 0 {scripts => bin}/templates/plugin/index.js | 0 8 files changed, 5 insertions(+), 5 deletions(-) rename {scripts => bin}/templates/plugin/client/.babelrc (100%) rename {scripts => bin}/templates/plugin/client/.eslintrc.json (100%) rename {scripts => bin}/templates/plugin/client/components/MyPluginComponent.css (100%) rename {scripts => bin}/templates/plugin/client/components/MyPluginComponent.js (100%) rename {scripts => bin}/templates/plugin/client/index.js (100%) rename {scripts => bin}/templates/plugin/client/translations.yml (100%) rename {scripts => bin}/templates/plugin/index.js (100%) diff --git a/bin/cli-plugins b/bin/cli-plugins index e4b5cae51..942036d29 100755 --- a/bin/cli-plugins +++ b/bin/cli-plugins @@ -274,7 +274,7 @@ async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote } async function createSeedPlugin() { - const pluginsDir = path.join(dir, 'plugins'); + const pluginsDir = path.join(__dirname, 'plugins'); function pluginNameExists(pluginName) { const pluginNames = fs.readdirSync(pluginsDir); @@ -321,7 +321,7 @@ async function createSeedPlugin() { // Creating plugin seed //============================================================================== - const seedPlugin = path.join(dir, 'scripts/templates/plugin'); + const seedPlugin = path.join(__dirname, 'bin/templates/plugin'); const newPluginPath = path.join(pluginsDir, answers.pluginName); if (fs.existsSync(seedPlugin)) { @@ -363,7 +363,7 @@ async function createSeedPlugin() { // This is a client-side plugin, let's push this. if (answers.client) { j.client.push(answers.pluginName); - + const output = JSON.stringify(j, null, 2); fs.writeFileSync(pluginsJson, output); } @@ -381,8 +381,8 @@ async function createSeedPlugin() { }); } - console.log(`✨ Yay! Plugin created! Find your plugin: ${answers.pluginName} in the /plugins folder`); - } + console.log(`✨ Yay! Plugin created! Find your plugin: ${answers.pluginName} in the ./plugins folder`); + } } diff --git a/scripts/templates/plugin/client/.babelrc b/bin/templates/plugin/client/.babelrc similarity index 100% rename from scripts/templates/plugin/client/.babelrc rename to bin/templates/plugin/client/.babelrc diff --git a/scripts/templates/plugin/client/.eslintrc.json b/bin/templates/plugin/client/.eslintrc.json similarity index 100% rename from scripts/templates/plugin/client/.eslintrc.json rename to bin/templates/plugin/client/.eslintrc.json diff --git a/scripts/templates/plugin/client/components/MyPluginComponent.css b/bin/templates/plugin/client/components/MyPluginComponent.css similarity index 100% rename from scripts/templates/plugin/client/components/MyPluginComponent.css rename to bin/templates/plugin/client/components/MyPluginComponent.css diff --git a/scripts/templates/plugin/client/components/MyPluginComponent.js b/bin/templates/plugin/client/components/MyPluginComponent.js similarity index 100% rename from scripts/templates/plugin/client/components/MyPluginComponent.js rename to bin/templates/plugin/client/components/MyPluginComponent.js diff --git a/scripts/templates/plugin/client/index.js b/bin/templates/plugin/client/index.js similarity index 100% rename from scripts/templates/plugin/client/index.js rename to bin/templates/plugin/client/index.js diff --git a/scripts/templates/plugin/client/translations.yml b/bin/templates/plugin/client/translations.yml similarity index 100% rename from scripts/templates/plugin/client/translations.yml rename to bin/templates/plugin/client/translations.yml diff --git a/scripts/templates/plugin/index.js b/bin/templates/plugin/index.js similarity index 100% rename from scripts/templates/plugin/index.js rename to bin/templates/plugin/index.js From a2dabcac0e5e63cbd4a66a966cf69f2a6a4ce26c Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 11 Jul 2017 01:26:25 +0700 Subject: [PATCH 22/32] Hide action labels on mobile --- client/coral-embed-stream/style/default.css | 11 ----------- client/coral-plugin-replies/ReplyButton.css | 5 +++++ client/coral-plugin-replies/ReplyButton.js | 9 ++++++--- plugins/coral-plugin-like/client/LikeButton.js | 4 +++- plugins/coral-plugin-like/client/styles.css | 6 ++++++ plugins/coral-plugin-love/client/LoveButton.js | 4 +++- plugins/coral-plugin-love/client/styles.css | 6 ++++++ plugins/coral-plugin-respect/client/RespectButton.js | 4 ++-- plugins/coral-plugin-respect/client/styles.css | 6 ++++++ 9 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 client/coral-plugin-replies/ReplyButton.css diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index bd56cb748..3038e0da4 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -459,14 +459,3 @@ button.comment__action-button[disabled], width: initial; } - -@media (min-device-width : 300px) and (max-device-width : 420px) { - .commentActionsLeft.comment__action-container .coral-plugin-replies-reply-button { - visibility: collapse; - margin-left: -30px; - } - - .commentActionsLeft.comment__action-container .coral-plugin-replies-reply-button .coral-plugin-replies-icon { - visibility: visible; - } -} diff --git a/client/coral-plugin-replies/ReplyButton.css b/client/coral-plugin-replies/ReplyButton.css new file mode 100644 index 000000000..4f71e6630 --- /dev/null +++ b/client/coral-plugin-replies/ReplyButton.css @@ -0,0 +1,5 @@ +@media (max-width: 425px) { + .label { + display: none; + } +} diff --git a/client/coral-plugin-replies/ReplyButton.js b/client/coral-plugin-replies/ReplyButton.js index a66e3c156..4641ae02d 100644 --- a/client/coral-plugin-replies/ReplyButton.js +++ b/client/coral-plugin-replies/ReplyButton.js @@ -2,16 +2,19 @@ import React, {PropTypes} from 'react'; import t from 'coral-framework/services/i18n'; -import classnames from 'classnames'; +import cn from 'classnames'; +import styles from './ReplyButton.css'; const name = 'coral-plugin-replies'; const ReplyButton = ({onClick}) => { return ( diff --git a/plugins/coral-plugin-like/client/LikeButton.js b/plugins/coral-plugin-like/client/LikeButton.js index 7d17ef056..34b5e40f2 100644 --- a/plugins/coral-plugin-like/client/LikeButton.js +++ b/plugins/coral-plugin-like/client/LikeButton.js @@ -45,7 +45,9 @@ class LikeButton extends React.Component { className={cn(styles.button, {[styles.liked]: alreadyReacted}, `${plugin}-button`)} onClick={this.handleClick} > - {t(alreadyReacted ? 'coral-plugin-like.liked' : 'coral-plugin-like.like')} + + {t(alreadyReacted ? 'coral-plugin-like.liked' : 'coral-plugin-like.like')} + {count > 0 && count} diff --git a/plugins/coral-plugin-like/client/styles.css b/plugins/coral-plugin-like/client/styles.css index cb372fa47..b44c170e3 100644 --- a/plugins/coral-plugin-like/client/styles.css +++ b/plugins/coral-plugin-like/client/styles.css @@ -23,3 +23,9 @@ } } } + +@media (max-width: 425px) { + .label { + display: none; + } +} diff --git a/plugins/coral-plugin-love/client/LoveButton.js b/plugins/coral-plugin-love/client/LoveButton.js index 7450ad668..d67160773 100644 --- a/plugins/coral-plugin-love/client/LoveButton.js +++ b/plugins/coral-plugin-love/client/LoveButton.js @@ -45,7 +45,9 @@ class LoveButton extends React.Component { className={cn(styles.button, {[styles.loved]: alreadyReacted}, `${plugin}-button`)} onClick={this.handleClick} > - {t(alreadyReacted ? 'coral-plugin-love.loved' : 'coral-plugin-love.love')} + + {t(alreadyReacted ? 'coral-plugin-love.loved' : 'coral-plugin-love.love')} + {count > 0 && count} diff --git a/plugins/coral-plugin-love/client/styles.css b/plugins/coral-plugin-love/client/styles.css index e16e17ca4..ac5a86370 100644 --- a/plugins/coral-plugin-love/client/styles.css +++ b/plugins/coral-plugin-love/client/styles.css @@ -23,3 +23,9 @@ } } } + +@media (max-width: 425px) { + .label { + display: none; + } +} diff --git a/plugins/coral-plugin-respect/client/RespectButton.js b/plugins/coral-plugin-respect/client/RespectButton.js index 9bc556b67..878525051 100644 --- a/plugins/coral-plugin-respect/client/RespectButton.js +++ b/plugins/coral-plugin-respect/client/RespectButton.js @@ -45,11 +45,11 @@ class RespectButton extends React.Component { className={cn(styles.button, {[styles.respected]: alreadyReacted}, `${plugin}-button`)} onClick={this.handleClick} > - + {t(alreadyReacted ? 'coral-plugin-respect.respected' : 'coral-plugin-respect.respect')} - {count > 0 && count} + {count > 0 && count}
); diff --git a/plugins/coral-plugin-respect/client/styles.css b/plugins/coral-plugin-respect/client/styles.css index f0b780b86..d08d648c3 100644 --- a/plugins/coral-plugin-respect/client/styles.css +++ b/plugins/coral-plugin-respect/client/styles.css @@ -28,3 +28,9 @@ .icon { padding: 0 5px; } + +@media (max-width: 425px) { + .label { + display: none; + } +} From b05a6c00ce5ab7e58d117684261749fb1e5e9856 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 11 Jul 2017 20:37:19 +0700 Subject: [PATCH 23/32] Correct aria-controls --- client/coral-embed-stream/src/components/Embed.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index 85c8d3ca5..57632d199 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -35,7 +35,7 @@ export default class Embed extends React.Component { onTabClick={this.changeTab} activeTab={activeTab} className='talk-stream-tabbar' - aria-controls='talk-embed-tab' + aria-controls='talk-embed-stream-tab-content' > {t('embed_comments_tab')} @@ -53,7 +53,7 @@ export default class Embed extends React.Component { From 8b70dc688de2650ac507703b5e8a5ca7176e8df3 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 11 Jul 2017 21:03:27 +0700 Subject: [PATCH 24/32] Adapt CSS classnames for Embed --- .../src/components/Embed.js | 55 ++++++++++--------- client/coral-embed-stream/src/index.js | 2 +- views/embed/stream.ejs | 2 +- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index 145e8340a..a98dbf976 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -9,6 +9,7 @@ import Count from 'coral-plugin-comment-count/CommentCount'; import ProfileContainer from 'coral-settings/containers/ProfileContainer'; import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer'; +import cn from 'classnames'; export default class Embed extends React.Component { changeTab = (tab) => { @@ -37,34 +38,36 @@ export default class Embed extends React.Component { const {activeTab, viewAllComments, commentId} = this.props; const {asset: {totalCommentCount}} = this.props.root; const {user} = this.props.auth; + const hasHighlightedComment = !!commentId; return ( -
-
- - - {t('framework.my_profile')} - {t('framework.configure_stream')} - - {commentId && - } - - - - - - - - - - -
+
+ + + {t('framework.my_profile')} + + {t('framework.configure_stream')} + + + {commentId && + } + + + + + + + + + +
); } diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index f5be3922d..9020a8457 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -48,5 +48,5 @@ render( - , document.querySelector('#coralStream') + , document.querySelector('#talk-embed-stream-container') ); diff --git a/views/embed/stream.ejs b/views/embed/stream.ejs index 03897d85f..99ca2072a 100644 --- a/views/embed/stream.ejs +++ b/views/embed/stream.ejs @@ -15,7 +15,7 @@ -
+
From 40f4419c9de896213a3574536f4606abe9ebb00d Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 11 Jul 2017 21:04:19 +0700 Subject: [PATCH 25/32] Add flagged unflagged CSS classnames --- client/coral-embed-stream/src/components/Embed.js | 4 ++-- client/coral-plugin-flags/components/FlagButton.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index a98dbf976..98137624f 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -42,7 +42,7 @@ export default class Embed extends React.Component { return (
- + {t('framework.my_profile')} @@ -54,7 +54,7 @@ export default class Embed extends React.Component { cStyle="darkGrey" style={{float: 'right'}} onClick={viewAllComments} - className={'talk-embed-stream-show-all-comments-button'} + className={'talk-stream-show-all-comments-button'} > {t('framework.show_all_comments')} } diff --git a/client/coral-plugin-flags/components/FlagButton.js b/client/coral-plugin-flags/components/FlagButton.js index 25fcf21a9..33499e83d 100644 --- a/client/coral-plugin-flags/components/FlagButton.js +++ b/client/coral-plugin-flags/components/FlagButton.js @@ -148,7 +148,7 @@ export default class FlagButton extends Component {