diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..751aa263d
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,77 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We expect everyone contributing to The Coral Project to follow this code of conduct. That means the team, contractors we employ, contributors, as well as anyone posting to our public or internal-facing channels.
+We created it not because we anticipate any unacceptable behavior, but because we believe that articulating our values and obligations to one another reinforces the already exceptional level of respect among the team, and because having a code provides us with clear avenues to correct our culture should it ever stray from that course.
+
+We commit to enforce and evolve this code over the duration of the project.
+
+## Expected behavior
+
+
+* Be supportive of each other.
+* Be collaborative. Involve others in brainstorms, sketching sessions, code reviews, planning documents, and the like. It’s not only okay to ask for help or feedback often, it’s unacceptable not to do so.
+* Be generous and kind in both giving and accepting critique. Critique is a natural and important part of our culture. Good critiques are kind, respectful, clear, and constructive, focused on goals and requirements rather than personal preferences. You are expected to give and receive criticism with grace.
+* Be humane. Be polite and friendly in all forms of communication, especially remote communication, where opportunities for misunderstanding are greater. Use sarcasm carefully. Tone is hard to decipher online; make judicious use of emoji to aid in communication.
+* Be considerate.
+* Be tolerant.
+* Respect people’s boundaries.
+* Do not make it personal.
+* Use welcoming and inclusive language.
+* Offer to help if you see someone struggling or otherwise in need of assistance (taking care not to be patronizing or disrespectful).
+* If someone approaches you looking for help, be generous with your time; if you’re under a deadline, direct them to someone else who may be of assistance.
+* Go out of your way to include people in jokes or memes, recognizing that we want to build an environment free of cliques.
+* Show empathy towards other community members
+
+
+## Unacceptable behavior
+
+We are committed to providing a welcoming and safe environment for people of all races, gender identities, gender expressions, sexual orientations, physical abilities, physical appearances, socioeconomic backgrounds, nationalities, ages, religions, and beliefs.
+We expect that you will refrain from demeaning, discriminatory, or harassing behavior and speech.
+
+Harassment includes, but is not limited to: deliberate intimidation; stalking; unwanted photography or recording; sustained or willful disruption of talks or other events; inappropriate physical contact; use of sexual or discriminatory imagery, comments, or jokes; and unwelcome sexual attention.
+Furthermore, any behavior or language which is unwelcoming—whether or not it rises to the level of harassment—is also strongly discouraged. Much exclusionary behavior takes the form of microaggressions—subtle put-downs which may be unconsciously delivered. Regardless of intent, microaggressions can have a significant negative impact on victims and have no place on our team.
+
+Other inappropriate behavior:
+* Threats
+* Slurs
+* Pornography
+* Spam
+* Bullying
+* Copyright infringement
+* Impersonation of someone else
+* Violating someone’s privacy
+
+If you feel that someone has harassed you or otherwise treated you or someone else inappropriately, please alert the project lead at andrewl@mozillafoundation.org.
+
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+These guidelines are ambitious, and we’re not always going to succeed in meeting them. When something goes wrong—whether it’s a microaggression or an instance of harassment — there are a number of things you can do to address the situation. Depending on your comfort level and the severity of the situation, here are some suggestions:
+
+* Address it directly. If you’re comfortable bringing up the incident with the person who instigated it, pull them aside to discuss how it affected you. Be sure to approach these conversations in a forgiving spirit: an angry or tense conversation will not do either of you any good. If you’re unsure how to go about that, try discussing with your manager or with the people and culture team first—they might have some advice about how to make this conversation happen.
+If you’re too frustrated to have a direct conversation, there are a number of alternate routes you can take.
+
+* Talk to a peer or mentor. Your colleagues are likely to have personal and professional experience on which to draw that could be of use to you. If you have someone you’re comfortable approaching, reach out and discuss the situation with them. They may be able to advise on how they would handle it, or direct you to someone who can. The flip side of this, of course, is that you should also be available when your colleagues reach out to you.
+
+* Contact the project lead, Andrew Losowsky, andrewl@mozillafoundation.org, or the technical lead. We will work with you to help you figure out how to ensure that any conflict doesn’t interfere with your work, in confidence if you would prefer.
+
+* Talk to Chris Lawrence. Chris oversees the project. He can be contacted at clawrence@mozillafoundation.org.
+
+If you feel you have been unfairly accused of violating this code of conduct, you should contact Chris with a concise description of your grievance.
+
+## Conclusion
+
+We welcome your feedback on this and every other aspect of what we do as The Coral Project, and we thank you for working with us to make it a safe, enjoyable, and friendly experience for everyone involved in the project and what we do.
+Above text is licensed CC BY-SA 4.0, adapted from the SRCCON code of conduct, FreeBSD’s code of conduct, Vox Media’s product team code of conduct, Medium’s code of conduct, as well as adapted from the Contributor Covenant.
diff --git a/Dockerfile b/Dockerfile
index 5c7b3cabb..1f67da95b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:7.8
+FROM node:7.10.1
# Create app directory
RUN mkdir -p /usr/src/app
diff --git a/README.md b/README.md
index 68547e7a8..86e8000d0 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,10 @@ See our [Talk Documentation & Guides](https://coralproject.github.io/talk/index.
See our guide to using and building [Talk Plugins](https://github.com/coralproject/talk/blob/master/PLUGINS.md).
+### Recipes
+
+Recipes are plugin templates provided by the Coral Core team. Developers can use these recipes to build their own plugins. You can find all the Talk recipes here: https://github.com/coralproject/talk-recipes/
+
## Usage
### Installation
@@ -46,11 +50,12 @@ sign and verify tokens via a `HS256` algorithm.
- `TALK_SMTP_PASSWORD` (*required for email*) - password for the SMTP provider you are using.
- `TALK_SMTP_HOST` (*required for email*) - SMTP host url with format `smtp.domain.com`.
- `TALK_SMTP_PORT` (*required for email*) - SMTP port.
-- `TALK_INSTALL_LOCK` (_optional for dynamic setup_) - Defaults to `FALSE`. When `TRUE`, disables the dynamic setup endpoint.
+- `TALK_INSTALL_LOCK` (_optional for dynamic setup_) - When `TRUE`, disables the dynamic setup endpoint. (Default `FALSE`)
- `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
- `TALK_KEEP_ALIVE` (_optional_) - The keepalive timeout that should be used to send keep alive messages through the websocket to keep the socket alive. (Default `30s`)
+- `TALK_DISABLE_AUTOFLAG_SUSPECT_WORDS` (_optional_) When `TRUE`, disables flagging of comments that match the suspect word filter. (Default `FALSE`)
Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for
alternative methods of loading configuration during development.
diff --git a/circle.yml b/circle.yml
index b924028f3..69f95c9b4 100644
--- a/circle.yml
+++ b/circle.yml
@@ -1,6 +1,6 @@
machine:
node:
- version: 7.9
+ version: 7.10.1
services:
- docker
- redis
diff --git a/client/coral-admin/src/routes/Moderation/containers/Comment.js b/client/coral-admin/src/routes/Moderation/containers/Comment.js
index 8843188b8..d724682ab 100644
--- a/client/coral-admin/src/routes/Moderation/containers/Comment.js
+++ b/client/coral-admin/src/routes/Moderation/containers/Comment.js
@@ -1,22 +1,21 @@
import {gql} from 'react-apollo';
import Comment from '../components/Comment';
-import {getSlotsFragments} from 'coral-framework/helpers/plugins';
import withFragments from 'coral-framework/hocs/withFragments';
+import {getSlotFragmentSpreads} from 'coral-framework/utils';
-const pluginFragments = getSlotsFragments([
+const slots = [
'adminCommentInfoBar',
'adminCommentContent',
'adminSideActions',
'adminCommentDetailArea',
-]);
+];
export default withFragments({
root: gql`
fragment CoralAdmin_ModerationComment_root on RootQuery {
__typename
- ${pluginFragments.spreads('root')}
+ ${getSlotFragmentSpreads(slots, 'root')}
}
- ${pluginFragments.definitions('root')}
`,
comment: gql`
fragment CoralAdmin_ModerationComment_comment on Comment {
@@ -52,8 +51,7 @@ export default withFragments({
editing {
edited
}
- ${pluginFragments.spreads('comment')}
+ ${getSlotFragmentSpreads(slots, 'comment')}
}
- ${pluginFragments.definitions('comment')}
`
})(Comment);
diff --git a/client/coral-admin/src/routes/Moderation/containers/UserDetail.js b/client/coral-admin/src/routes/Moderation/containers/UserDetail.js
index 0e36ed51a..6b476b861 100644
--- a/client/coral-admin/src/routes/Moderation/containers/UserDetail.js
+++ b/client/coral-admin/src/routes/Moderation/containers/UserDetail.js
@@ -4,8 +4,7 @@ import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import UserDetail from '../components/UserDetail';
import withQuery from 'coral-framework/hocs/withQuery';
-import {getSlotsFragments} from 'coral-framework/helpers/plugins';
-import {getDefinitionName} from 'coral-framework/utils';
+import {getDefinitionName, getSlotFragmentSpreads} from 'coral-framework/utils';
import {
changeUserDetailStatuses,
clearUserDetailSelections,
@@ -26,9 +25,9 @@ const commentConnectionFragment = gql`
${Comment.fragments.comment}
`;
-const pluginFragments = getSlotsFragments([
+const slots = [
'userProfile',
-]);
+];
class UserDetailContainer extends React.Component {
static propTypes = {
@@ -80,7 +79,7 @@ export const withUserDetailQuery = withQuery(gql`
id
provider
}
- ${pluginFragments.spreads('user')}
+ ${getSlotFragmentSpreads(slots, 'user')}
}
totalComments: commentCount(query: {author_id: $author_id})
rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]})
@@ -90,11 +89,8 @@ export const withUserDetailQuery = withQuery(gql`
}) {
...CoralAdmin_Moderation_CommentConnection
}
- ${pluginFragments.spreads('root')}
+ ${getSlotFragmentSpreads(slots, 'root')}
}
- ${Comment.fragments.comment}
- ${pluginFragments.definitions('user')}
- ${pluginFragments.definitions('root')}
${commentConnectionFragment}
`, {
options: ({id, moderation: {userDetailStatuses: statuses}}) => {
diff --git a/client/coral-embed-stream/src/components/Comment.css b/client/coral-embed-stream/src/components/Comment.css
index d397185dc..9a176534d 100644
--- a/client/coral-embed-stream/src/components/Comment.css
+++ b/client/coral-embed-stream/src/components/Comment.css
@@ -2,24 +2,23 @@
margin-left: 20px;
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 {
margin-left: 0px;
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.comment {
- padding-left: 20px;
+ padding-left: 15px;
+ display: flex;
+ flex-flow: row;
}
.commentLevel0 {
@@ -43,7 +42,7 @@
}
.highlightedComment {
- padding-left: 20px;
+ padding-left: 15px;
border-left: 3px solid rgb(35,118,216);
}
@@ -157,3 +156,20 @@
.enter {
animation: enter 1000ms;
}
+
+.commentContainer {
+ flex: auto;
+}
+
+.commentAvatar {
+ max-width: 60px;
+}
+
+.commentAvatar:empty {
+ display: none;
+}
+
+.timerIcon {
+ vertical-align: middle;
+ font-size: 14px;
+}
diff --git a/client/coral-embed-stream/src/components/Comment.js b/client/coral-embed-stream/src/components/Comment.js
index 0cd37a92a..9e6a70efe 100644
--- a/client/coral-embed-stream/src/components/Comment.js
+++ b/client/coral-embed-stream/src/components/Comment.js
@@ -430,26 +430,10 @@ export default class Comment extends React.Component {
id={`c_${comment.id}`}
>
-
+ {
{t('reply')}
- reply
);
diff --git a/client/coral-ui/components/Icon.css b/client/coral-ui/components/Icon.css
new file mode 100644
index 000000000..16fe6d235
--- /dev/null
+++ b/client/coral-ui/components/Icon.css
@@ -0,0 +1,3 @@
+
+.root {
+}
diff --git a/client/coral-ui/components/Icon.js b/client/coral-ui/components/Icon.js
index b7c49a799..025b6df9e 100644
--- a/client/coral-ui/components/Icon.js
+++ b/client/coral-ui/components/Icon.js
@@ -1,8 +1,10 @@
import React, {PropTypes} from 'react';
import {Icon as IconMDL} from 'react-mdl';
+import cn from 'classnames';
+import styles from './Icon.css';
const Icon = ({className = '', name}) => (
-
+
);
Icon.propTypes = {
diff --git a/config.js b/config.js
index ce8931235..e3dd4578c 100644
--- a/config.js
+++ b/config.js
@@ -77,7 +77,15 @@ const CONFIG = {
SMTP_HOST: process.env.TALK_SMTP_HOST,
SMTP_PASSWORD: process.env.TALK_SMTP_PASSWORD,
SMTP_PORT: process.env.TALK_SMTP_PORT,
- SMTP_USERNAME: process.env.TALK_SMTP_USERNAME
+ SMTP_USERNAME: process.env.TALK_SMTP_USERNAME,
+
+ //------------------------------------------------------------------------------
+ // Flagging Config
+ //------------------------------------------------------------------------------
+
+ // DISABLE_AUTOFLAG_SUSPECT_WORDS is true when the suspect words that are
+ // matched should not be flagged.
+ DISABLE_AUTOFLAG_SUSPECT_WORDS: process.env.TALK_DISABLE_AUTOFLAG_SUSPECT_WORDS === 'TRUE'
};
//==============================================================================
diff --git a/docs/_data/sidebars/talk_sidebar.yml b/docs/_data/sidebars/talk_sidebar.yml
index 62660783c..b483b47d1 100644
--- a/docs/_data/sidebars/talk_sidebar.yml
+++ b/docs/_data/sidebars/talk_sidebar.yml
@@ -36,6 +36,12 @@ entries:
- title: Setup
url: /install-setup.html
output: web
+ - title: Microservice Deployments
+ url: /install-microservices.html
+ output: web
+ - title: Troubleshooting
+ url: /install-troubleshooting.html
+ output: web
- title: Architecture
output: web
@@ -46,11 +52,13 @@ entries:
- title: Tags
url: /architecture-tags.html
output: web
+ - title: Metadata API
+ url: /architecture-metadata.html
+ output: web
- title: cli
url: /architecture-cli.html
output: web
-
- title: Plugins
output: web
folderitems:
diff --git a/docs/architecture-metadata.md b/docs/architecture-metadata.md
new file mode 100644
index 000000000..089127343
--- /dev/null
+++ b/docs/architecture-metadata.md
@@ -0,0 +1,92 @@
+---
+title: Metadata
+keywords: architecture
+sidebar: talk_sidebar
+permalink: architecture-metadata.html
+summary:
+---
+
+_Metadata_ allows you to add fields to models that are not represented in the core schema.
+
+## Goals
+
+The metadata api is designed to satisfy two product goals:
+
+* Give developers flexibility in extending datatypes.
+* Protect core fields that are essential to Talk's operation.
+
+## Design
+
+Metadata is represented by an [subdocument in our Schemas](https://github.com/coralproject/talk/blob/c59c09e1f42c51eed3b0d57b7c2882fc7b5edc13/models/comment.js#L74). This takes advantage of Mongo's flexibility allowing for any data to be stored therein.
+
+### Setting Metadata
+
+Talk provides [a service layer](https://github.com/coralproject/talk/blob/c59c09e1f42c51eed3b0d57b7c2882fc7b5edc13/services/metadata.js) allowing developers to `set` and `unset` metadata on objects in a way similar to key-value stores.
+
+Let's say that I want to add a custom field called `potency` to a comment.
+
+```
+const MetadataService = require('services/metadata');
+const CommentModel = require('models/comment');
+
+// Sets the property `potency` on the comment with `id=1`.
+MetadataService.set(CommentModel, '1', 'potency', 42);
+```
+
+Note that the model passed here is the Model itself and not an individual comment object. This allows us to update the value on that document [in an atomic manner](https://github.com/coralproject/talk/blob/c59c09e1f42c51eed3b0d57b7c2882fc7b5edc13/services/metadata.js#L60) for efficiency and to prevent race conditions.
+
+### Accessing Metadata
+
+The metadata api does not contain a `get` method. The metadata object is retrieved via database queries along with the rest of the data.
+
+## Metadata and the Graph
+
+One of the first principles of GraphQL is that the shape of the graph does not need to be the same as the shape of the data in the database. In fact, it probably shouldn't be.
+
+This enables us to treat metadata fields in any way that makes sense as we design our Graph. The fact that a value is stored in the metadata object is an implementation detail invisible to the front end.
+
+Take for example, the `reason` field in the `FlagAction` type. This stores the user provided reason why they flagged a comment. As far as the front end knows, it's [just another field](https://github.com/coralproject/talk/blob/c59c09e1f42c51eed3b0d57b7c2882fc7b5edc13/graph/typeDefs.graphql#L453) alongside the core fields:
+
+```
+# graph/typeDefs.graphql
+type FlagAction implements Action {
+
+ ...
+
+ # The reason for which the Flag Action was created.
+ reason: String
+
+ ...
+}
+```
+
+If, however, we [look at the resolver](https://github.com/coralproject/talk/blob/a47e2378e96f34f25447782f3e7ce59fa48ec791/graph/resolvers/dont_agree_action.js) for that field, we see that `reason` is [destructured](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) from the metadata object and returned.
+
+```
+// graph/resolvers/dont_agree_action.js
+const DontAgreeAction = {
+
+ // Stored in the metadata, extract and return.
+ reason({metadata: {reason}}) {
+ return reason;
+ }
+};
+
+module.exports = DontAgreeAction;
+```
+
+This is an extremely powerful pattern as it allows us absolute freedom in designing our graph and complete isolation of the added fields in the database.
+
+## Some things to keep in mind
+
+### Namespace your metadata fields
+
+Since metadata can be added by the core and multiple plugins, collisions may occur. As you create your plugins, please be careful to pick unique names for metadata fields. We recommend namespacing all your fields in a subdocument named after your plugin.
+
+```
+[model].metadata.[your_plugin_name].[the_field]
+```
+
+### Querying by metadata fields
+
+We currently do not have a clean way to index metadata fields. As a result queries that match against metadata fields will not scale. If you have a need to match, sort, etc... by a metadata field, [please let us know](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md#writing-code).
diff --git a/docs/architecture.md b/docs/architecture.md
index 180e172ac..d914bcf03 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -17,7 +17,7 @@ Talk consists of four distinct layers of code:
### 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.
+Talk plugins deliver the features and functionality that can be changed or removed. Much of the default functionality is delivered by core plugins allowing developers to have control over any non-essential functionality.
### Plugin API
diff --git a/docs/configuration.md b/docs/configuration.md
index 8323ca724..c19c5a38e 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -17,7 +17,7 @@ sign and verify tokens via a `HS256` algorithm.
- `TALK_JWT_EXPIRY` (_optional_) - the expiry duration (`exp`) for the tokens issued for logged in sessions (Default `1 day`)
- `TALK_JWT_ISSUER` (_optional_) - the issuer (`iss`) claim for login JWT tokens (Default `process.env.TALK_ROOT_URL`)
- `TALK_JWT_AUDIENCE` (_optional_) - the audience (`aud`) claim for login JWT tokens (Default `talk`)
-- `TALK_SMTP_EMAIL` (*required for email*) - the address to send emails from using the
+- `TALK_SMTP_FROM_ADDRESS` (*required for email*) - the address to send emails from using the
SMTP provider.
- `TALK_SMTP_USERNAME` (*required for email*) - username of the SMTP provider you are using.
- `TALK_SMTP_PASSWORD` (*required for email*) - password for the SMTP provider you are using.
diff --git a/docs/install-microservices.md b/docs/install-microservices.md
new file mode 100644
index 000000000..061640dd9
--- /dev/null
+++ b/docs/install-microservices.md
@@ -0,0 +1,98 @@
+---
+title: Microservice Deployments
+keywords: install
+sidebar: talk_sidebar
+permalink: install-microservices.html
+summary:
+---
+
+In Talk, we seek to deliver the simplicity of a monolith with the advantages of
+a microservice based infrastructure for those who want them.
+
+To accomplish this, Talk has the ability to run with subsets of its overall
+functionality and contains architecture that allows them to operate logically as
+microservices when running in a single environment.
+
+## Talk Server Functionalities
+
+The Talk server serves several logically/architecturally distinct functions:
+
+* A web server that
+ * serves "public" assets (aka, the comment embed)
+ * the GraphQL endpoints
+* A web socket server that handles subscriptions.
+* A jobs processor that handles queued operations.
+
+In the documentation so far, we've discussed how to deploy all of these
+functionalities bundled into a single monolith application. This is convenient
+as there is minimal configuration and horizontal scaling is as easy as upping
+the number of servers behind a single load balancer.
+
+## Separating Talk into Microservices
+
+Talk can be run in two or more separate clusters of servers by
+enabling/disabling different bits of functionality: webserver, socket server and
+jobs server.
+
+Each microservice would deploy with the same codebase and configuration.
+
+Note that the `cli serve` command, which is responsible for starting the server,
+contains flags that control whether `jobs` and `websockets` are enabled.
+
+```
+talk :) ]$ bin/cli serve --help
+
+ Options:
+
+ ...
+ -j, --jobs enable job processing on this thread
+ -w, --websockets enable the websocket (subscriptions) handler on this thread
+ ...
+```
+
+Each Talk Microservice cluster can be deployed in an identical manner described
+in the other docs in this section with the omission of the `-j` and/or `-w`
+flags.
+
+With routing logic in front of the webserver cluster, separation between public
+and protected assets can be achieved.
+
+## When should I consider separating?
+
+Consider a microservice deployment if:
+
+* you are running plugins that require intensive job processing
+* you do not want to simplicity of single cluster horizontal scaling and want to
+ tune the economy and performance of your install.
+* You run into scaling issues serving websockets
+
+At scale, combining separate concerns in a single process makes it very
+difficult to understand what is taking up resources. With microservices, each
+server could be configured to sit behind it's own load balance and scale
+independently. Each variety of process can always have just enough resources.
+
+An install that heavily utilizes the jobs queue could see delays in http service
+because of heavy jobs processes and/or delays in the execution of jobs processes
+due to increased server load as a result of Node's single thread infrustructure.
+
+## Deployment Methodologies
+
+Note that there is no flag to separate the http routes on the webserver.
+Separating the http server functionalities can be accomplished by the routing of
+various routes to the correct http server via an external upstream proxy like
+Google Cloud's Load Balancer, AWS's ELB, or a websever like NGINX/Apache. This
+can ensure that sensitive areas, such as the `/admin/` route are not available
+outside the firewall.
+
+Talk's job processors are synchronized by Redis, so as long as all Talk
+instances access the same Redis cluster no additional configuration is needed
+when launching an independent jobs cluster.
+
+If there are any features of Talk that you believe should be disable-able via
+server flags, please let us know and consider contributing it to the project!
+
+## Deployment Flows/Scripts
+
+We do not currently support any microservice based deployment flows. If you
+develop one yourself that is completely based on open source tooling, please
+consider contributing it to the project!
diff --git a/docs/install-troubleshooting.md b/docs/install-troubleshooting.md
new file mode 100644
index 000000000..6ff42af05
--- /dev/null
+++ b/docs/install-troubleshooting.md
@@ -0,0 +1,22 @@
+---
+title: Installation Troubleshooting
+keywords: install
+sidebar: talk_sidebar
+permalink: install-troubleshooting.html
+summary:
+---
+
+This page tracks common issues that arise when installing Talk and provides resolutions.
+
+## The Talk server seems to be working but the stream isn't showing up on my page.
+
+Talk employs a _domain whitelist_ that controls which sites can contain comment threads. This prevents malicious folks from using your server to embed streams on unwanted pages.
+
+If your comment thread isn't showing:
+
+1. Log into your admin panel
+1. Go to the Configure tab
+1. Select the Tech Settings submenu
+1. Ensure that your Domain is the Permitted Domains list
+
+Note: if your site has multiple subdomains, listing the domain itself (ie: `mydomain.com`) will enable Talk on all subdomains. If you would like to restrict Talk to certain subdomains, you must list all of them here (ie: `thisone.mydomain.com thatone.mydomain.com`).
diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js
index f32172a4d..539bd674e 100644
--- a/graph/mutators/comment.js
+++ b/graph/mutators/comment.js
@@ -15,6 +15,10 @@ const {
EDIT_COMMENT
} = require('../../perms/constants');
+const {
+ DISABLE_AUTOFLAG_SUSPECT_WORDS
+} = require('../../config');
+
const debug = require('debug')('talk:graph:mutators:tags');
const plugins = require('../../services/plugins');
@@ -297,7 +301,10 @@ const createPublicComment = async (context, commentInput) => {
// Otherwise just return the new comment.
// TODO: Check why the wordlist is undefined
- if (wordlist != null && wordlist.suspect != null) {
+
+ // If the wordlist has matched the suspect word filter and we haven't disabled
+ // auto-flagging suspect words, then we should flag the comment!
+ if (wordlist != null && wordlist.suspect != null && !DISABLE_AUTOFLAG_SUSPECT_WORDS) {
// TODO: this is kind of fragile, we should refactor this to resolve
// all these const's that we're using like 'COMMENTS', 'FLAG' to be
diff --git a/package.json b/package.json
index 34e069c0f..3e3254fd4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "talk",
- "version": "2.4.0",
+ "version": "2.5.0",
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
"main": "app.js",
"scripts": {
@@ -77,6 +77,7 @@
"env-rewrite": "^1.0.2",
"express": "^4.15.2",
"express-session": "^1.15.1",
+ "file-loader": "^0.11.2",
"form-data": "^2.1.2",
"fs-extra": "^3.0.1",
"gql-merge": "^0.0.4",
@@ -209,9 +210,10 @@
"style-loader": "^0.16.0",
"supertest": "^2.0.1",
"timeago.js": "^2.0.3",
+ "url-loader": "^0.5.9",
"webpack": "^2.3.1"
},
"engines": {
- "node": "^7.8.0"
+ "node": "^7.10.1"
}
}
diff --git a/plugin-api/beta/client/hocs/index.js b/plugin-api/beta/client/hocs/index.js
index 7be682670..78408f6c2 100644
--- a/plugin-api/beta/client/hocs/index.js
+++ b/plugin-api/beta/client/hocs/index.js
@@ -1 +1,4 @@
export {default as withReaction} from './withReaction';
+export {default as withFragments} from 'coral-framework/hocs/withFragments';
+export {default as excludeIf} from 'coral-framework/hocs/excludeIf';
+export {default as connect} from 'coral-framework/hocs/connect';
diff --git a/plugin-api/beta/client/hocs/withReaction.js b/plugin-api/beta/client/hocs/withReaction.js
index 72de7f5c4..f7be6e701 100644
--- a/plugin-api/beta/client/hocs/withReaction.js
+++ b/plugin-api/beta/client/hocs/withReaction.js
@@ -1,7 +1,7 @@
import React from 'react';
import get from 'lodash/get';
import uuid from 'uuid/v4';
-import {connect} from 'react-redux';
+import {connect} from 'plugin-api/beta/client/hocs';
import {bindActionCreators} from 'redux';
import {getDisplayName} from 'coral-framework/helpers/hoc';
import {compose, gql} from 'react-apollo';
diff --git a/plugin-api/beta/client/selectors/index.js b/plugin-api/beta/client/selectors/index.js
new file mode 100644
index 000000000..09e25d453
--- /dev/null
+++ b/plugin-api/beta/client/selectors/index.js
@@ -0,0 +1 @@
+export const pluginConfigSelector = (state) => state.config.pluginConfig;
diff --git a/plugin-api/beta/client/services/index.js b/plugin-api/beta/client/services/index.js
index ddaa346ab..4e94a782a 100644
--- a/plugin-api/beta/client/services/index.js
+++ b/plugin-api/beta/client/services/index.js
@@ -1,3 +1,9 @@
export {t} from 'coral-framework/services/i18n';
export {can} from 'coral-framework/services/perms';
-export {isSlotEmpty} from 'coral-framework/helpers/plugins';
+import {isSlotEmpty as ise} from 'coral-framework/helpers/plugins';
+
+// @TODO: Deprecated.
+export function isSlotEmpty(...args) {
+ console.warn('A plugin is using `isSlotEmpty` which has been deprecated, please port to the new API using the `IfSlotIsEmpty` and `IfSlotIsNotEmpty` components.');
+ return ise(...args);
+}
diff --git a/plugin-api/beta/client/utils/index.js b/plugin-api/beta/client/utils/index.js
new file mode 100644
index 000000000..44c3af6c0
--- /dev/null
+++ b/plugin-api/beta/client/utils/index.js
@@ -0,0 +1 @@
+export {getSlotFragmentSpreads} from 'coral-framework/utils';
diff --git a/plugins.default.json b/plugins.default.json
index 3bc2e41bf..425dccf93 100644
--- a/plugins.default.json
+++ b/plugins.default.json
@@ -1,14 +1,12 @@
{
"server": [
"coral-plugin-auth",
- "coral-plugin-like",
"coral-plugin-respect",
"coral-plugin-offtopic",
"coral-plugin-facebook-auth"
],
"client": [
"coral-plugin-respect",
- "coral-plugin-like",
"coral-plugin-auth",
"coral-plugin-offtopic",
"coral-plugin-viewing-options",
diff --git a/plugins/coral-plugin-like/client/styles.css b/plugins/coral-plugin-like/client/styles.css
index 9ce0d5271..859581bd4 100644
--- a/plugins/coral-plugin-like/client/styles.css
+++ b/plugins/coral-plugin-like/client/styles.css
@@ -25,7 +25,9 @@
}
.icon {
- padding: 0 2px;
+ font-size: 12px;
+ padding: 0 2px 0 5px;
+ vertical-align: middle;
}
@media (max-width: 425px) {
diff --git a/plugins/coral-plugin-love/client/styles.css b/plugins/coral-plugin-love/client/styles.css
index d7753a0e9..9d1bec487 100644
--- a/plugins/coral-plugin-love/client/styles.css
+++ b/plugins/coral-plugin-love/client/styles.css
@@ -25,7 +25,9 @@
}
.icon {
- padding: 0 2px;
+ font-size: 12px;
+ padding: 0 2px 0 5px;
+ vertical-align: middle;
}
@media (max-width: 425px) {
diff --git a/plugins/coral-plugin-offtopic/client/containers/OffTopicCheckbox.js b/plugins/coral-plugin-offtopic/client/containers/OffTopicCheckbox.js
index 286fa8d9d..f8471344b 100644
--- a/plugins/coral-plugin-offtopic/client/containers/OffTopicCheckbox.js
+++ b/plugins/coral-plugin-offtopic/client/containers/OffTopicCheckbox.js
@@ -1,7 +1,7 @@
-import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {addTag, removeTag} from 'plugin-api/alpha/client/actions';
import {commentBoxTagsSelector} from 'plugin-api/alpha/client/selectors';
+import {connect} from 'plugin-api/beta/client/hocs';
import OffTopicCheckbox from '../components/OffTopicCheckbox';
const mapStateToProps = (state) => ({
diff --git a/plugins/coral-plugin-offtopic/client/containers/OffTopicFilter.js b/plugins/coral-plugin-offtopic/client/containers/OffTopicFilter.js
index 98f5f7aea..6bca55638 100644
--- a/plugins/coral-plugin-offtopic/client/containers/OffTopicFilter.js
+++ b/plugins/coral-plugin-offtopic/client/containers/OffTopicFilter.js
@@ -1,4 +1,4 @@
-import {connect} from 'react-redux';
+import {connect} from 'plugin-api/beta/client/hocs';
import {bindActionCreators} from 'redux';
import {toggleCheckbox} from '../actions';
import {commentClassNamesSelector} from 'plugin-api/alpha/client/selectors';
diff --git a/plugins/coral-plugin-respect/client/styles.css b/plugins/coral-plugin-respect/client/styles.css
index 3ab0fbd29..362e02b8f 100644
--- a/plugins/coral-plugin-respect/client/styles.css
+++ b/plugins/coral-plugin-respect/client/styles.css
@@ -26,7 +26,8 @@
}
.icon {
- padding: 0 2px;
+ font-size: 12px;
+ padding: 0 3px;
}
@media (max-width: 425px) {
diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css
index 9c302d07e..81d373b95 100644
--- a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css
+++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.css
@@ -24,3 +24,8 @@
list-style: none;
white-space: nowrap;
}
+
+.icon {
+ font-size: 14px;
+ vertical-align: middle;
+}
diff --git a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js
index b64123fdb..87680b370 100644
--- a/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js
+++ b/plugins/coral-plugin-viewing-options/client/components/ViewingOptions.js
@@ -24,7 +24,7 @@ const ViewingOptions = (props) => {