Merge branch 'master' into subscriptions

This commit is contained in:
Wyatt Johnson
2017-04-06 15:39:24 -06:00
committed by GitHub
14 changed files with 115 additions and 53 deletions
+1
View File
@@ -14,5 +14,6 @@ dump.rdb
test/e2e/reports
coverage/
plugins.json
plugins/*
!plugins/coral-plugin-facebook-auth
+11 -4
View File
@@ -3,11 +3,18 @@
Plugins for Talk can take various forms, currently we are only supporting server
side plugins.
## Plugin Registration: `plugins.json`
## Plugin Registration
All plugins must be registered in the root file `plugins.json`.
The parsing order for the plugin registration is as follows:
The format for this file is thus:
- `TALK_PLUGINS_JSON` environment variable
- `plugins.json` file
- `plugins.default.json` file
If you need to "disable all plugins", you can simply provide `{}` as the
contents of `process.env.TALK_PLUGINS_JSON` or the `plugins.json`.
The format for this is thus:
```json
{
@@ -18,7 +25,7 @@ The format for this file is thus:
```
Where we have a `server` key with an array of plugins that match the folder
name in the `plugins/` folder. For example, the above `plugins.json` would
name in the `plugins/` folder. For example, the above config would
require a plugin from `plugins/people`, which must provide a `index.js` file
that returns an object that matches the Plugin Specification.
+1
View File
@@ -37,6 +37,7 @@ available in the format: `<scheme>://<host>` without the path.
- `TALK_INSTALL_LOCK` (_optional for dynamic setup_) - Defaults to `FALSE`. When `TRUE`, disables the dynamic setup endpoint.
- `TALK_RECAPTCHA_SECRET` (*required for reCAPTCHA support*) - server secret used for enabling reCAPTCHA powered logins. If not provided it will instead default to providing only a time based lockout.
- `TALK_RECAPTCHA_PUBLIC` (*required for reCAPTCHA support*) - client secret used for enabling reCAPTCHA powered logins. If not provided it will instead default to providing only a time based lockout.
- `TALK_PLUGINS_JSON` (_optional_) - used to specify the plugin config via the environment
Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for
alternative methods of loading configuration during development.
+4 -3
View File
@@ -41,9 +41,10 @@ class Embed extends Component {
state = {activeTab: 0, showSignInDialog: false, activeReplyBox: ''};
changeTab = (tab) => {
const {isAdmin} = this.props.auth;
// Everytime the comes from another tab, the Stream needs to be updated.
if (tab === 0) {
if (tab === 0 && isAdmin) {
this.props.data.refetch();
}
@@ -116,7 +117,7 @@ class Embed extends Component {
render () {
const {activeTab} = this.state;
const {closedAt, countCache = {}} = this.props.asset;
const {loading, asset, refetch, comment} = this.props.data;
const {asset, refetch, comment} = this.props.data;
const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth;
// even though the permalinked comment is the highlighted one, we're displaying its parent + replies
@@ -130,7 +131,7 @@ class Embed extends Component {
minHeight: document.body.scrollHeight + 200
} : {};
if (loading || !asset) {
if (!asset) {
return <Spinner />;
}
+25 -37
View File
@@ -1,5 +1,7 @@
import pym from 'pym.js';
import {stringify} from 'querystring';
const snackbarStyles = {
position: 'fixed',
cursor: 'default',
@@ -26,30 +28,21 @@ const Coral = {};
const Talk = Coral.Talk = {};
// build the URL to load in the pym iframe
function buildStreamIframeUrl(talkBaseUrl, asset_url, comment, asset_id) {
let iframeArray = [
function buildStreamIframeUrl(talkBaseUrl, query) {
let url = [
talkBaseUrl,
(talkBaseUrl.match(/\/$/) ? '' : '/'), // make sure no double-'/' if opts.talk already ends with '/'
'embed/stream?asset_url=',
encodeURIComponent(asset_url)
];
'embed/stream?'
].join('');
if (comment) {
iframeArray.push('&comment_id=');
iframeArray.push(encodeURIComponent(comment));
}
url += stringify(query);
if (asset_id) {
iframeArray.push('&asset_id=');
iframeArray.push(encodeURIComponent(asset_id));
}
return iframeArray.join('');
return url;
}
// Set up postMessage listeners/handlers on the pymParent
// e.g. to resize the iframe, and navigate the host page
function configurePymParent(pymParent, asset_url) {
function configurePymParent(pymParent) {
let notificationOffset = 200;
let ready = false;
let cachedHeight;
@@ -117,8 +110,8 @@ function configurePymParent(pymParent, asset_url) {
if (ready) {
window.clearInterval(interval);
// @todo - It's weird to me that this is sent here in addition to the iframe URL. Could it just be in one place?
pymParent.sendMessage('DOMContentLoaded', asset_url);
// TODO: It's weird to me that this is sent here
pymParent.sendMessage('DOMContentLoaded');
}
}, 100);
});
@@ -166,42 +159,37 @@ Talk.render = function (el, opts) {
}
opts = opts || {};
// @todo infer this URL without explicit user input (if possible, may have to be added at build/render time of this script)
// TODO: infer this URL without explicit user input (if possible, may have to be added at build/render time of this script)
if (!opts.talk) {
throw new Error('Coral.Talk.render() expects opts.talk as the Talk Base URL');
}
// ensure el has an id, as pym can't directly accept the HTMLElement
// Ensure el has an id, as pym can't directly accept the HTMLElement.
if (!el.id) {
el.id = `_${Math.random()}`;
}
let asset_url = opts.asset_url;
if (!asset_url) {
// Compose the query to send down to the Talk API so it knows what to load.
let query = {};
query.comment_id = window.location.hash.slice(1);
query.asset_id = opts.asset_id;
query.asset_url = opts.asset_url;
if (!query.asset_url) {
try {
asset_url = document.querySelector('link[rel="canonical"]').href;
query.asset_url = document.querySelector('link[rel="canonical"]').href;
} catch (e) {
console.warn('This page does not include a canonical link tag. Talk has inferred this asset_url from the window object. Query params have been stripped, which may cause a single thread to be present across multiple pages.');
asset_url = window.location.origin + window.location.pathname;
query.asset_url = window.location.origin + window.location.pathname;
}
}
let comment = window.location.hash.slice(1);
let query = {
configurePymParent(new pym.Parent(el.id, buildStreamIframeUrl(opts.talk, query), {
title: opts.title,
asset_url: asset_url,
id: `${el.id}_iframe`,
name: `${el.id}_iframe`
};
if (opts.asset_id && opts.asset_id.length > 0) {
query.asset_id = opts.asset_id;
}
let pymParent = new pym.Parent(el.id, buildStreamIframeUrl(opts.talk, asset_url, comment), query);
configurePymParent(pymParent, asset_url);
}));
};
export default Coral;
@@ -1,6 +1,6 @@
#import "../fragments/commentView.graphql"
query AssetQuery($asset_id: ID, $asset_url: String!, $comment_id: ID!, $has_comment: Boolean!) {
query AssetQuery($asset_id: ID, $asset_url: String, $comment_id: ID!, $has_comment: Boolean!) {
# the comment here is for loading one comment and it's children, probably after following a permalink
# $has_comment is derived from the comment_id query param in the iframe url,
# which is in turn pulled from the host page url
-5
View File
@@ -8,11 +8,6 @@ const RootQuery = {
},
asset(_, query, {loaders: {Assets}}) {
if (query.id) {
// TODO: we may not always have a comment stream here, therefore, when we
// load it, we may also need to create with the url. This may also have to
// move the logic over to the mutators function as an upsert operation
// possibly.
return Assets.getByID.load(query.id);
}
+1
View File
@@ -76,6 +76,7 @@
"graphql-tools": "^0.10.1",
"helmet": "^3.5.0",
"inquirer": "^3.0.6",
"joi": "^10.4.1",
"jsonwebtoken": "^7.3.0",
"kue": "^0.11.5",
"linkify-it": "^2.0.3",
+39 -2
View File
@@ -2,6 +2,7 @@ const fs = require('fs');
const path = require('path');
const resolve = require('resolve');
const debug = require('debug')('talk:plugins');
const Joi = require('joi');
// Add support for require rewriting.
require('app-module-path').addPath(__dirname);
@@ -12,15 +13,42 @@ let plugins = {};
// file isn't loaded, but continuing. Else, like a parsing error, throw it and
// crash the program.
try {
plugins = JSON.parse(fs.readFileSync(path.join(__dirname, 'plugins.json'), 'utf8'));
let defaultPlugins = path.join(__dirname, 'plugins.default.json');
let customPlugins = path.join(__dirname, 'plugins.json');
let envPluginJSON = process.env.TALK_PLUGINS_JSON;
if (envPluginJSON && envPluginJSON.length > 0) {
debug('Now using TALK_PLUGINS_JSON environment variable for plugins');
plugins = JSON.parse(envPluginJSON);
} else if (fs.existsSync(customPlugins)) {
debug(`Now using ${customPlugins} for plugins`);
plugins = JSON.parse(fs.readFileSync(customPlugins, 'utf8'));
} else {
debug(`Now using ${defaultPlugins} for plugins`);
plugins = JSON.parse(fs.readFileSync(defaultPlugins, 'utf8'));
}
} catch (err) {
if (err.code === 'ENOENT') {
console.error('plugins.json not found, plugins will not be active');
console.error('plugins.json and plugins.default.json not found, plugins will not be active');
} else {
throw err;
}
}
const hookSchemas = {
passport: Joi.func().arity(1),
router: Joi.func().arity(1),
context: Joi.object().pattern(/\w/, Joi.func().maxArity(1)),
hooks: Joi.object({
pre: Joi.func(),
post: Joi.func()
}),
loaders: Joi.object().pattern(/\w/, Joi.object().pattern(/\w/, Joi.func())),
mutators: Joi.object().pattern(/\w/, Joi.object().pattern(/\w/, Joi.func())),
resolvers: Joi.object().pattern(/\w/, Joi.object().pattern(/\w/, Joi.func())),
typeDefs: Joi.string()
};
/**
* isInternal checks to see if a given plugin is internal, and returns true
* if it is.
@@ -122,6 +150,15 @@ class PluginSection {
hook(hook) {
return this.plugins
.filter(({module}) => hook in module)
.filter((plugin) => {
// Validate the hook.
if (hook in hookSchemas) {
Joi.assert(plugin.module[hook], hookSchemas[hook], `Plugin '${plugin.name}' failed validation for the '${hook}' hook`);
}
return true;
})
.map((plugin) => ({
plugin,
[hook]: plugin.module[hook]
+2
View File
@@ -14,6 +14,7 @@ router.get('/id/:asset_id', (req, res, next) => {
}
res.render('article', {
title: asset.title,
asset_id: asset.id,
asset_url: asset.url,
body: '',
basePath: '/client/embed/stream'
@@ -26,6 +27,7 @@ router.get('/title/:asset_title', (req, res) => {
return res.render('article', {
title: req.params.asset_title.split('-').join(' '),
asset_url: '',
asset_id: null,
body: body,
basePath: '/client/embed/stream'
});
+1
View File
@@ -18,6 +18,7 @@ if (process.env.NODE_ENV !== 'production') {
return res.render('article', {
title: 'Coral Talk',
asset_url: '',
asset_id: '',
body: '',
basePath: '/client/embed/stream'
});
+2 -1
View File
@@ -27,7 +27,8 @@
<script src="/embed.js" async onload="
Coral.Talk.render(document.getElementById('coralStreamEmbed'), {
talk: '/',
asset_url: '<%= asset_url ? asset_url : '' %>'
asset_url: '<%= asset_url ? asset_url : '' %>',
asset_id: '<%= asset_id ? asset_id : '' %>'
})
"></script>
</main>
+27
View File
@@ -3626,6 +3626,10 @@ hoek@2.x.x:
version "2.16.3"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
hoek@4.x.x:
version "4.1.1"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.1.1.tgz#9cc573ffba2b7b408fb5e9c2a13796be94cddce9"
hoist-non-react-statics@^1.0.3, hoist-non-react-statics@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
@@ -4156,6 +4160,10 @@ isemail@1.x.x:
version "1.2.0"
resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a"
isemail@2.x.x:
version "2.2.1"
resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6"
isexe@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0"
@@ -4254,6 +4262,10 @@ istanbul@^1.1.0-alpha.1:
which "^1.1.1"
wordwrap "^1.0.0"
items@2.x.x:
version "2.1.1"
resolved "https://registry.yarnpkg.com/items/-/items-2.1.1.tgz#8bd16d9c83b19529de5aea321acaada78364a198"
iterall@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.0.2.tgz#41a2e96ce9eda5e61c767ee5dc312373bb046e91"
@@ -4275,6 +4287,15 @@ jodid25519@^1.0.0:
dependencies:
jsbn "~0.1.0"
joi@^10.4.1:
version "10.4.1"
resolved "https://registry.yarnpkg.com/joi/-/joi-10.4.1.tgz#a2fca1f0d603d1b843f2c1e086b52461f6be1f36"
dependencies:
hoek "4.x.x"
isemail "2.x.x"
items "2.x.x"
topo "2.x.x"
joi@^6.10.1:
version "6.10.1"
resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06"
@@ -7731,6 +7752,12 @@ topo@1.x.x:
dependencies:
hoek "2.x.x"
topo@2.x.x:
version "2.0.2"
resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182"
dependencies:
hoek "4.x.x"
touch@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/touch/-/touch-1.0.0.tgz#449cbe2dbae5a8c8038e30d71fa0ff464947c4de"