diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 000000000..9e1ee91e0
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,292 @@
+# job_defaults applies all the defaults for each job.
+job_defaults: &job_defaults
+ working_directory: ~/coralproject/talk
+ docker:
+ - image: circleci/node:8
+
+# integration_environment is the environment that configures the tests.
+integration_environment: &integration_environment
+ NODE_ENV: test
+ CIRCLE_TEST_REPORTS: /tmp/circleci-test-results
+ E2E_MAX_RETRIES: 3
+
+# integration_job runs the integration tests and saves the test results.
+integration_job: &integration_job
+ <<: *job_defaults
+ environment:
+ <<: *integration_environment
+ docker:
+ - image: circleci/node:8-browsers
+ - image: circleci/mongo:3
+ - image: circleci/redis:4-alpine
+ steps:
+ - checkout
+ - attach_workspace:
+ at: ~/coralproject/talk
+ - run:
+ name: Setup the database with defaults
+ command: ./bin/cli setup --defaults
+ - run:
+ name: Run the integration tests
+ command: bash .circleci/e2e.sh
+ - store_test_results:
+ when: always
+ path: /tmp/circleci-test-results
+
+version: 2
+jobs:
+ # npm_dependencies will install the dependencies used by all other steps.
+ npm_dependencies:
+ <<: *job_defaults
+ steps:
+ - checkout
+ - attach_workspace:
+ at: ~/coralproject/talk
+ - restore_cache:
+ key: dependency-cache-{{ checksum "yarn.lock" }}
+ - run:
+ name: Install dependencies
+ command: |
+ yarn global add node-gyp &&
+ yarn install --frozen-lockfile
+ - save_cache:
+ key: dependency-cache-{{ checksum "yarn.lock" }}
+ paths:
+ - ./node_modules
+ - persist_to_workspace:
+ root: .
+ paths: node_modules
+
+ # lint will perform file linting.
+ lint:
+ <<: *job_defaults
+ steps:
+ - checkout
+ - attach_workspace:
+ at: ~/coralproject/talk
+ - run:
+ name: Perform linting
+ command: yarn lint
+
+ # build_assets will build the static assets.
+ build_assets:
+ <<: *job_defaults
+ steps:
+ - checkout
+ - attach_workspace:
+ at: ~/coralproject/talk
+ - restore_cache:
+ keys:
+ - build-cache-{{ .Branch }}-{{ .Revision }}
+ - build-cache-{{ .Branch }}-
+ - build-cache-
+ - run:
+ name: Build static assets
+ command: yarn build
+ - save_cache:
+ key: build-cache-{{ .Branch }}-{{ .Revision }}
+ paths:
+ - ./node_modules/.cache/hard-source
+ - persist_to_workspace:
+ root: .
+ paths: dist
+
+ # test_unit will run the unit tests.
+ test_unit:
+ <<: *job_defaults
+ docker:
+ - image: circleci/node:8
+ - image: circleci/mongo:3
+ - image: circleci/redis:4-alpine
+ steps:
+ - checkout
+ - attach_workspace:
+ at: ~/coralproject/talk
+ - run:
+ name: Setup the test results directory
+ command: mkdir -p /tmp/circleci-test-results
+ - run:
+ name: Run the client unit tests
+ command: yarn test:client --ci
+ environment:
+ JEST_JUNIT_OUTPUT: /tmp/circleci-test-results/jest/test-results.xml
+ JEST_REPORTER: jest-junit
+ - run:
+ name: Run the server unit tests
+ command: yarn test:server
+ environment:
+ MOCHA_FILE: /tmp/circleci-test-results/mocha/test-results.xml
+ MOCHA_REPORTER: mocha-junit-reporter
+ - store_test_results:
+ when: always
+ path: /tmp/circleci-test-results
+
+ # test_integration_chrome_local will run the integration tests locally with
+ # chrome headless.
+ test_integration_chrome_local:
+ <<: *integration_job
+ environment:
+ <<: *integration_environment
+ E2E_BROWSERS: chrome
+
+ # test_integration_firefox_local will run the integration tests locally with
+ # firefox headless.
+ test_integration_firefox_local:
+ <<: *integration_job
+ environment:
+ <<: *integration_environment
+ E2E_BROWSERS: firefox
+
+ # test_integration_chrome will run the integration tests with chrome in
+ # browserstack.
+ test_integration_chrome:
+ <<: *integration_job
+ environment:
+ <<: *integration_environment
+ BROWSERSTACK: true
+ E2E_BROWSERS: chrome
+
+ # test_integration_firefox will run the integration tests with firefox in
+ # browserstack.
+ test_integration_firefox:
+ <<: *integration_job
+ environment:
+ <<: *integration_environment
+ BROWSERSTACK: true
+ E2E_BROWSERS: firefox
+
+ # test_integration_edge will run the integration tests with edge in
+ # browserstack.
+ test_integration_edge:
+ <<: *integration_job
+ environment:
+ <<: *integration_environment
+ BROWSERSTACK: true
+ E2E_BROWSERS: edge
+
+ # test_integration_ie will run the integration tests with ie in
+ # browserstack.
+ test_integration_ie:
+ <<: *integration_job
+ environment:
+ <<: *integration_environment
+ BROWSERSTACK: true
+ E2E_BROWSERS: ie
+ # TODO: remove when more reliable
+ E2E_MAX_RETRIES: 1
+
+ # test_integration_safari will run the integration tests with safari in
+ # browserstack.
+ test_integration_safari:
+ <<: *integration_job
+ environment:
+ <<: *integration_environment
+ BROWSERSTACK: true
+ E2E_BROWSERS: safari
+ # TODO: remove when more reliable
+ E2E_MAX_RETRIES: 1
+
+ # deploy will deploy the application as a docker image.
+ deploy:
+ <<: *job_defaults
+ steps:
+ - checkout
+ - setup_remote_docker
+ - run:
+ name: Deploy the code
+ command: bash ./scripts/docker.sh deploy
+
+# filter_deploy will add the filters for a deploy job in a workflow to make it
+# only execute on a deploy related job.
+filter_deploy: &filter_deploy
+ filters:
+ branches:
+ only:
+ - master
+ - next
+ tags:
+ only: /v[0-9]+(\.[0-9]+)*/
+
+# filter_develop will add the filters for a development related commit.
+filter_develop: &filter_develop
+ filters:
+ branches:
+ ignore:
+ - master
+ - next
+
+workflows:
+ version: 2
+
+ # All PR's will hit this workflow.
+ build-and-test:
+ jobs:
+ - npm_dependencies:
+ <<: *filter_develop
+ - lint:
+ <<: *filter_develop
+ requires:
+ - npm_dependencies
+ - test_unit:
+ <<: *filter_develop
+ requires:
+ - npm_dependencies
+ - build_assets:
+ <<: *filter_develop
+ requires:
+ - npm_dependencies
+ - test_integration_chrome_local:
+ <<: *filter_develop
+ requires:
+ - build_assets
+ - test_integration_firefox_local:
+ <<: *filter_develop
+ requires:
+ - build_assets
+ deploy-tagged:
+ jobs:
+ - npm_dependencies:
+ <<: *filter_deploy
+ - lint:
+ <<: *filter_deploy
+ requires:
+ - npm_dependencies
+ - test_unit:
+ <<: *filter_deploy
+ requires:
+ - npm_dependencies
+ - build_assets:
+ <<: *filter_deploy
+ requires:
+ - npm_dependencies
+ - test_integration_chrome:
+ <<: *filter_deploy
+ requires:
+ - build_assets
+ - test_integration_firefox:
+ <<: *filter_deploy
+ requires:
+ - build_assets
+ - test_integration_edge:
+ <<: *filter_deploy
+ requires:
+ - build_assets
+ - test_integration_ie:
+ <<: *filter_deploy
+ requires:
+ - build_assets
+ - test_integration_safari:
+ <<: *filter_deploy
+ requires:
+ - build_assets
+ - deploy:
+ <<: *filter_deploy
+ requires:
+ - lint
+ - test_unit
+ - test_integration_chrome
+ - test_integration_firefox
+ - test_integration_edge
+ # TODO: uncomment when more reliable
+ # - test_integration_ie
+ # - test_integration_safari
\ No newline at end of file
diff --git a/scripts/e2e-ci.sh b/.circleci/e2e.sh
similarity index 65%
rename from scripts/e2e-ci.sh
rename to .circleci/e2e.sh
index a509ccee7..2d9fc25af 100755
--- a/scripts/e2e-ci.sh
+++ b/.circleci/e2e.sh
@@ -19,11 +19,11 @@ if [[ "${E2E_DISABLE}" == "true" ]]; then
exit
fi
-if [[ "${CIRCLE_BRANCH}" == "master" && -n "$BROWSERSTACK_KEY" ]]; then
+if [[ "$BROWSERSTACK" == "true" && -n "$BROWSERSTACK_KEY" ]]; then
echo Testing on browserstack
- yarn e2e --reports-folder "$REPORTS_FOLDER" --bs-key "$BROWSERSTACK_KEY" --retries "$E2E_MAX_RETRIES" --timeout "$E2E_WAIT_FOR_TIMEOUT" --browsers "$E2E_BROWSERS"
+ node scripts/e2e.js --reports-folder "$REPORTS_FOLDER" --retries "$E2E_MAX_RETRIES" --timeout "$E2E_WAIT_FOR_TIMEOUT" --browsers "$E2E_BROWSERS" --browserstack
else
# When browserstack is not available test locally using chrome headless.
echo Testing locally
- yarn e2e --reports-folder "$REPORTS_FOLDER" --retries "$E2E_MAX_RETRIES" --headless
+ node scripts/e2e.js --reports-folder "$REPORTS_FOLDER" --retries "$E2E_MAX_RETRIES" --timeout "$E2E_WAIT_FOR_TIMEOUT" --browsers "$E2E_BROWSERS" --headless
fi
diff --git a/.eslintignore b/.eslintignore
index ad58230e0..a9543d933 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -2,4 +2,5 @@
dist
docs
node_modules
-public
\ No newline at end of file
+public
+
diff --git a/.gitignore b/.gitignore
index 48ce6e5d5..b7a79b2bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,39 +23,49 @@ browserstack.err
plugins.json
plugins/*
+
!plugins/talk-plugin-akismet
-!plugins/talk-plugin-facebook-auth
-!plugins/talk-plugin-google-auth
!plugins/talk-plugin-auth
-!plugins/talk-plugin-respect
-!plugins/talk-plugin-offtopic
-!plugins/talk-plugin-like
-!plugins/talk-plugin-mod
-!plugins/talk-plugin-love
-!plugins/talk-plugin-viewing-options
+!plugins/talk-plugin-author-menu
!plugins/talk-plugin-comment-content
-!plugins/talk-plugin-permalink
+!plugins/talk-plugin-deep-reply-count
+!plugins/talk-plugin-facebook-auth
!plugins/talk-plugin-featured-comments
-!plugins/talk-plugin-toxic-comments
-!plugins/talk-plugin-sort-newest
-!plugins/talk-plugin-sort-oldest
-!plugins/talk-plugin-sort-most-replied
+!plugins/talk-plugin-flag-details
+!plugins/talk-plugin-google-auth
+!plugins/talk-plugin-ignore-user
+!plugins/talk-plugin-like
+!plugins/talk-plugin-love
+!plugins/talk-plugin-member-since
+!plugins/talk-plugin-mod
+!plugins/talk-plugin-moderation-actions
+!plugins/talk-plugin-notifications
+!plugins/talk-plugin-notifications-category-featured
+!plugins/talk-plugin-notifications-category-reply
+!plugins/talk-plugin-notifications-category-staff
+!plugins/talk-plugin-offtopic
+!plugins/talk-plugin-permalink
+!plugins/talk-plugin-profile-settings
+!plugins/talk-plugin-remember-sort
+!plugins/talk-plugin-respect
+!plugins/talk-plugin-slack-notifications
!plugins/talk-plugin-sort-most-liked
!plugins/talk-plugin-sort-most-loved
+!plugins/talk-plugin-sort-most-replied
!plugins/talk-plugin-sort-most-respected
-!plugins/talk-plugin-author-menu
-!plugins/talk-plugin-member-since
-!plugins/talk-plugin-ignore-user
-!plugins/talk-plugin-moderation-actions
-!plugins/talk-plugin-toxic-comments
-!plugins/talk-plugin-remember-sort
-!plugins/talk-plugin-deep-reply-count
+!plugins/talk-plugin-sort-newest
+!plugins/talk-plugin-sort-oldest
!plugins/talk-plugin-subscriber
+<<<<<<< HEAD
!plugins/talk-plugin-flag-details
!plugins/talk-plugin-slack-notifications
!plugins/talk-plugin-rich-text
!plugins/talk-plugin-rich-text-pell
!plugins/talk-plugin-profile-settings
+=======
+!plugins/talk-plugin-toxic-comments
+!plugins/talk-plugin-viewing-options
+>>>>>>> 8d147dbf47104cdcfb8c3bc2ebb02b6c73c7b8ab
**/node_modules/*
yarn-error.log
diff --git a/README.md b/README.md
index 89474bf6d..db9f65c3d 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Talk · [](https://circleci.com/gh/coralproject/talk) · [](https://nodesecurity.io/orgs/coralproject/projects/07ce2e4c-99fb-48f8-b50b-69d2d2c081b8) · [](CONTRIBUTING.md#pull-requests)
+# Talk · [](https://circleci.com/gh/coralproject/talk) · [](https://nodesecurity.io/orgs/coralproject/projects/7bd7d26c-47ed-4a5f-8c4a-b919bf1c2946) · [](CONTRIBUTING.md#pull-requests)
Online comments are broken. Our open-source commenting platform, Talk, rethinks how moderation, comment display, and conversation function, creating the opportunity for safer, smarter discussions around your work. [Read more about Talk here](https://coralproject.net/products/talk.html).
diff --git a/app.js b/app.js
index e962e41ff..c2bf9648d 100644
--- a/app.js
+++ b/app.js
@@ -1,7 +1,6 @@
const express = require('express');
const morgan = require('morgan');
const path = require('path');
-const uuid = require('uuid');
const merge = require('lodash/merge');
const helmet = require('helmet');
const plugins = require('./services/plugins');
@@ -13,13 +12,6 @@ const { ENABLE_TRACING, APOLLO_ENGINE_KEY, PORT } = require('./config');
const app = express();
-// Request Identity Middleware
-app.use((req, res, next) => {
- req.id = uuid.v4();
-
- next();
-});
-
//==============================================================================
// PLUGIN PRE APPLICATION MIDDLEWARE
//==============================================================================
diff --git a/bin/cli-jobs b/bin/cli-jobs
index 672a5a70d..a9599213d 100755
--- a/bin/cli-jobs
+++ b/bin/cli-jobs
@@ -6,8 +6,7 @@
const util = require('./util');
const program = require('commander');
-const scraper = require('../services/scraper');
-const mailer = require('../services/mailer');
+const jobs = require('../jobs');
const mongoose = require('../services/mongoose');
const kue = require('../services/kue');
@@ -21,11 +20,8 @@ function processJobs() {
// started.
util.onshutdown([() => kue.Task.shutdown()]);
- // Start the scraper processor.
- scraper.process();
-
- // Start the mail processor.
- mailer.process();
+ // Start the jobs processor.
+ jobs.process();
}
/**
diff --git a/bin/cli-plugins b/bin/cli-plugins
index ee331bff2..f2e76de21 100755
--- a/bin/cli-plugins
+++ b/bin/cli-plugins
@@ -143,7 +143,7 @@ async function reconcileRemotePlugins({ dryRun, upgradeRemote }) {
if (fetchable.length > 0) {
console.log(
- `$ yarn add --ignore-scripts ${fetchable.map(
+ `$ yarn add --ignore-scripts --ignore-workspace-root-check ${fetchable.map(
({ name, version }) => `${name}@${version}`.cyan
)}`
);
@@ -152,6 +152,7 @@ async function reconcileRemotePlugins({ dryRun, upgradeRemote }) {
let args = [
'add',
'--ignore-scripts',
+ '--ignore-workspace-root-check',
...fetchable.map(({ name, version }) => `${name}@${version}`),
];
diff --git a/circle.yml b/circle.yml
deleted file mode 100644
index bd5c2c520..000000000
--- a/circle.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-machine:
- node:
- version: 8
- services:
- - docker
- - redis
- environment:
- PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
- NODE_ENV: "test"
- MOCHA_FILE: "${CIRCLE_TEST_REPORTS}/junit/test-results.xml"
- MOCHA_REPORTER: "mocha-junit-reporter"
- pre:
- # TODO: use the following to add in support for MongoDB 3.4.
- # # Upgrade the database version to 3.4.
- # - sudo apt-get purge mongodb-org*
- # - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6
- # - echo "deb [ arch=amd64 ] http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list
- # - sudo apt-get update
- # - sudo apt-get install -y mongodb-org
- # - sudo service mongod restart
-
- # Force sync the time.
- - date
- - sudo service ntp stop
- - sudo ntpdate -s time.nist.gov
- - sudo service ntp start
- - date
-
- # Install chromium for e2e and remove old google-chrome
- - sudo rm -rf /opt/google/chrome
- - sudo rm -f /usr/bin/google-chrome*
- - sudo apt-get update
- - sudo apt-get install chromium-browser
-
-dependencies:
- override:
-
- # Install node dependencies.
- - yarn --version
- - yarn global add node-gyp --force
- - yarn
-
- post:
- # Build the static assets.
- - yarn build
- # Lint the project here, before tests are ran.
- - yarn lint
-
-database:
- post:
- # Initialize the settings in the database, this will create indicies for the
- # database.
- - ./bin/cli setup --defaults
- - sleep 2
-
-test:
- override:
- # Run the tests using the junit reporter.
- - yarn test
- # Run the end to end tests
- - yarn e2e:ci
-
-deployment:
- release:
- tag: /v[0-9]+(\.[0-9]+)*/
- commands:
- - bash ./scripts/docker.sh deploy
-
- latest:
- branch:
- - master
- - next
- owner: coralproject
- commands:
- - bash ./scripts/docker.sh deploy
diff --git a/client/coral-auth-callback/src/index.js b/client/coral-auth-callback/src/index.js
index b7f3ed226..41a83dcd2 100644
--- a/client/coral-auth-callback/src/index.js
+++ b/client/coral-auth-callback/src/index.js
@@ -1,14 +1,34 @@
+import { HANDLE_SUCCESSFUL_LOGIN } from 'coral-framework/constants/auth';
+import { getStaticConfiguration } from 'coral-framework/services/staticConfiguration';
+import { createPostMessage } from 'coral-framework/services/postMessage';
+
document.addEventListener('DOMContentLoaded', () => {
- // Get the auth element and parse it as JSON by decoding it.
- const auth = document.getElementById('auth');
- const doc = document.implementation.createHTMLDocument('');
- doc.body.innerHTML = auth.innerText;
+ try {
+ const staticConfig = getStaticConfiguration();
+ const { STATIC_ORIGIN: origin } = staticConfig;
+ const postMessage = createPostMessage(origin);
- // Set the item in localStorage.
- localStorage.setItem('auth', doc.body.textContent);
+ // Get the auth element and parse it as JSON by decoding it.
+ const auth = document.getElementById('auth');
+ const doc = document.implementation.createHTMLDocument('');
+ doc.body.innerHTML = auth.innerText;
- // Close the window.
- setTimeout(() => {
- window.close();
- }, 50);
+ // Auth state is contained within the node.
+ const { err, data } = JSON.parse(doc.body.textContent);
+ if (err) {
+ // TODO: send back the error message.
+ console.error(err);
+ } else {
+ // The data will contain a user and a token.
+ const { user, token } = data;
+
+ // Send the state back.
+ postMessage.post(HANDLE_SUCCESSFUL_LOGIN, { user, token });
+ }
+ } finally {
+ // Always close the window.
+ setTimeout(() => {
+ window.close();
+ }, 50);
+ }
});
diff --git a/client/coral-embed-stream/src/actions/asset.js b/client/coral-embed-stream/src/actions/asset.js
deleted file mode 100644
index 158e65594..000000000
--- a/client/coral-embed-stream/src/actions/asset.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as actions from '../constants/asset';
-import { notify } from 'coral-framework/actions/notification';
-
-import t from 'coral-framework/services/i18n';
-
-export const fetchAssetRequest = () => ({ type: actions.FETCH_ASSET_REQUEST });
-export const fetchAssetSuccess = asset => ({
- type: actions.FETCH_ASSET_SUCCESS,
- asset,
-});
-export const fetchAssetFailure = error => ({
- type: actions.FETCH_ASSET_FAILURE,
- error,
-});
-
-const updateAssetSettingsRequest = () => ({
- type: actions.UPDATE_ASSET_SETTINGS_REQUEST,
-});
-const updateAssetSettingsSuccess = settings => ({
- type: actions.UPDATE_ASSET_SETTINGS_SUCCESS,
- settings,
-});
-const updateAssetSettingsFailure = error => ({
- type: actions.UPDATE_ASSET_SETTINGS_FAILURE,
- error,
-});
-
-export const updateConfiguration = newConfig => (
- dispatch,
- getState,
- { rest }
-) => {
- const assetId = getState().asset.id;
- dispatch(updateAssetSettingsRequest());
- rest(`/assets/${assetId}/settings`, { method: 'PUT', body: newConfig })
- .then(() => {
- dispatch(notify('success', t('framework.success_update_settings')));
- dispatch(updateAssetSettingsSuccess(newConfig));
- })
- .catch(error => {
- console.error(error);
- dispatch(updateAssetSettingsFailure(error));
- });
-};
-
-export const updateOpenStream = closedBody => (
- dispatch,
- getState,
- { rest }
-) => {
- const assetId = getState().asset.id;
- dispatch(fetchAssetRequest());
- rest(`/assets/${assetId}/status`, { method: 'PUT', body: closedBody })
- .then(() => {
- dispatch(notify('success', t('framework.success_update_settings')));
- dispatch(fetchAssetSuccess(closedBody));
- })
- .catch(error => {
- console.error(error);
- dispatch(fetchAssetFailure(error));
- });
-};
-
-const openStream = () => ({ type: actions.OPEN_COMMENTS });
-const closeStream = () => ({ type: actions.CLOSE_COMMENTS });
-
-export const updateOpenStatus = status => dispatch => {
- if (status === 'open') {
- dispatch(openStream());
- dispatch(updateOpenStream({ closedAt: null }));
- } else {
- dispatch(closeStream());
- dispatch(updateOpenStream({ closedAt: new Date().getTime() }));
- }
-};
diff --git a/client/coral-embed-stream/src/actions/config.js b/client/coral-embed-stream/src/actions/config.js
deleted file mode 100644
index e30dc24cf..000000000
--- a/client/coral-embed-stream/src/actions/config.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import { ADD_EXTERNAL_CONFIG } from '../constants/config';
-
-export const addExternalConfig = config => ({
- type: ADD_EXTERNAL_CONFIG,
- config,
-});
diff --git a/client/coral-embed-stream/src/actions/login.js b/client/coral-embed-stream/src/actions/login.js
index c602bf7b6..fe1f76149 100644
--- a/client/coral-embed-stream/src/actions/login.js
+++ b/client/coral-embed-stream/src/actions/login.js
@@ -1,14 +1,10 @@
import * as actions from '../constants/login';
-import { checkLogin } from 'coral-framework/actions/auth';
export const showSignInDialog = () => ({
type: actions.SHOW_SIGNIN_DIALOG,
});
-export const hideSignInDialog = () => dispatch => {
- dispatch(checkLogin());
- dispatch({ type: actions.HIDE_SIGNIN_DIALOG });
-};
+export const hideSignInDialog = () => ({ type: actions.HIDE_SIGNIN_DIALOG });
export const focusSignInDialog = () => ({
type: actions.FOCUS_SIGNIN_DIALOG,
diff --git a/client/coral-embed-stream/src/constants/asset.js b/client/coral-embed-stream/src/constants/asset.js
deleted file mode 100644
index 40f746706..000000000
--- a/client/coral-embed-stream/src/constants/asset.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export const FETCH_ASSET_REQUEST = 'FETCH_ASSET_REQUEST';
-export const FETCH_ASSET_FAILURE = 'FETCH_ASSET_FAILURE';
-export const FETCH_ASSET_SUCCESS = 'FETCH_ASSET_SUCCESS';
-
-export const UPDATE_ASSET_SETTINGS_REQUEST = 'UPDATE_ASSET_SETTINGS_REQUEST';
-export const UPDATE_ASSET_SETTINGS_SUCCESS = 'UPDATE_ASSET_SETTINGS_SUCCESS';
-export const UPDATE_ASSET_SETTINGS_FAILURE = 'UPDATE_ASSET_SETTINGS_FAILURE';
-
-export const OPEN_COMMENTS = 'OPEN_COMMENTS';
-export const CLOSE_COMMENTS = 'CLOSE_COMMENTS';
diff --git a/client/coral-embed-stream/src/constants/config.js b/client/coral-embed-stream/src/constants/config.js
deleted file mode 100644
index 5821316c5..000000000
--- a/client/coral-embed-stream/src/constants/config.js
+++ /dev/null
@@ -1 +0,0 @@
-export const ADD_EXTERNAL_CONFIG = 'ADD_EXTERNAL_CONFIG';
diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js
index f8486fd14..d365af50d 100644
--- a/client/coral-embed-stream/src/containers/Embed.js
+++ b/client/coral-embed-stream/src/containers/Embed.js
@@ -2,7 +2,6 @@ import React from 'react';
import { compose, gql } from 'react-apollo';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import isEqual from 'lodash/isEqual';
import get from 'lodash/get';
import branch from 'recompose/branch';
import renderComponent from 'recompose/renderComponent';
@@ -14,12 +13,11 @@ import {
hideSignInDialog,
} from '../actions/login';
import { updateStatus } from 'coral-framework/actions/auth';
-import { fetchAssetSuccess } from '../actions/asset';
import {
getDefinitionName,
getSlotFragmentSpreads,
} from 'coral-framework/utils';
-import { withQuery } from 'coral-framework/hocs';
+import { withQuery, withPopupAuthHandler } from 'coral-framework/hocs';
import Embed from '../components/Embed';
import Stream from '../tabs/stream/containers/Stream';
import AutomaticAssetClosure from './AutomaticAssetClosure';
@@ -107,12 +105,6 @@ class EmbedContainer extends React.Component {
this.props.data.refetch();
this.resubscribe(nextProps);
}
-
- const { fetchAssetSuccess } = this.props;
- if (!isEqual(nextProps.root.asset, this.props.root.asset)) {
- // TODO: remove asset data from redux store.
- fetchAssetSuccess(nextProps.root.asset);
- }
}
componentDidUpdate(prevProps) {
@@ -298,7 +290,6 @@ EmbedContainer.propTypes = {
activeTab: PropTypes.string,
parentUrl: PropTypes.string,
data: PropTypes.object,
- fetchAssetSuccess: PropTypes.func,
showSignInDialog: PropTypes.bool,
signInDialogFocus: PropTypes.bool,
};
@@ -322,7 +313,6 @@ const mapDispatchToProps = dispatch =>
bindActionCreators(
{
setActiveTab,
- fetchAssetSuccess,
notify,
focusSignInDialog,
blurSignInDialog,
@@ -333,6 +323,7 @@ const mapDispatchToProps = dispatch =>
);
export default compose(
+ withPopupAuthHandler,
connect(mapStateToProps, mapDispatchToProps),
branch(props => !props.checkedInitialLogin, renderComponent(Spinner)),
withEmbedQuery
diff --git a/client/coral-embed-stream/src/graphql/utils.js b/client/coral-embed-stream/src/graphql/utils.js
index 720cb7f2e..96cf49cad 100644
--- a/client/coral-embed-stream/src/graphql/utils.js
+++ b/client/coral-embed-stream/src/graphql/utils.js
@@ -141,7 +141,7 @@ export function findCommentInAsset(asset, callbackOrId) {
callback = node => node.id === callbackOrId;
}
if (asset.comment) {
- return findComment([getTopLevelParent(asset.comment)], callback);
+ return findComment([reverseCommentParentTree(asset.comment)], callback);
}
if (!asset.comments) {
return false;
@@ -187,11 +187,12 @@ export function insertFetchedCommentsIntoEmbedQuery(root, comments, parent_id) {
);
}
-/**
- * attachCommentToParent recurses through the comment tree starting at `topLevelComment`
- * to find the parent of `comment` and attach it to the replies.
- */
-export function attachCommentToParent(topLevelComment, comment) {
+function attachComment(topLevelComment, comment) {
+ if (!topLevelComment.replies) {
+ topLevelComment = update(topLevelComment, {
+ replies: { $set: { nodes: [] } },
+ });
+ }
if (topLevelComment.id === comment.parent.id) {
return update(topLevelComment, {
replies: {
@@ -204,16 +205,40 @@ export function attachCommentToParent(topLevelComment, comment) {
},
});
}
+ if (!topLevelComment.replies.nodes.length) {
+ return topLevelComment;
+ }
return update(topLevelComment, {
replies: {
nodes: {
- $apply: nodes =>
- nodes.map(node => attachCommentToParent(node, comment)),
+ $apply: nodes => nodes.map(node => attachComment(node, comment)),
},
},
});
}
+/**
+ * attachCommentToParent recurses through the comment tree starting at `topLevelComment`
+ * to find the ancestor of `comment` and attach it to the replies.
+ */
+export function attachCommentToParent(topLevelComment, comment) {
+ let result = topLevelComment;
+ if (comment.parent.parent) {
+ result = attachCommentToParent(result, comment.parent);
+ }
+ return attachComment(result, comment);
+}
+
+/**
+ * reverseCommentParentTree reverses a comment parent relationship tree
+ * like `comment -> parent -> parent` into `parent -> parent -> comment -> replies`.
+ */
+export function reverseCommentParentTree(comment) {
+ return comment.parent
+ ? attachCommentToParent(getTopLevelParent(comment), comment)
+ : comment;
+}
+
/**
* Nest a string in itself repeatly until `level` has been reached.
*
diff --git a/client/coral-embed-stream/src/reducers/asset.js b/client/coral-embed-stream/src/reducers/asset.js
deleted file mode 100644
index b683eae30..000000000
--- a/client/coral-embed-stream/src/reducers/asset.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import * as actions from '../constants/asset';
-
-const initialState = {
- closedAt: null,
- settings: null,
- title: null,
- url: null,
- features: {},
- status: 'open',
- moderation: null,
-};
-
-export default function asset(state = initialState, action) {
- switch (action.type) {
- case actions.FETCH_ASSET_SUCCESS:
- return {
- ...state,
- ...action.asset,
- };
- case actions.UPDATE_ASSET_SETTINGS_SUCCESS:
- return {
- ...state,
- settings: action.settings,
- };
- default:
- return state;
- }
-}
diff --git a/client/coral-embed-stream/src/reducers/config.js b/client/coral-embed-stream/src/reducers/config.js
deleted file mode 100644
index 09a89a743..000000000
--- a/client/coral-embed-stream/src/reducers/config.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ADD_EXTERNAL_CONFIG } from '../constants/config';
-
-const initialState = {};
-
-export default function config(state = initialState, action) {
- switch (action.type) {
- case ADD_EXTERNAL_CONFIG:
- return {
- ...state,
- ...action.config,
- };
- default:
- return state;
- }
-}
diff --git a/client/coral-embed-stream/src/reducers/index.js b/client/coral-embed-stream/src/reducers/index.js
index f8eed72d4..15056d414 100644
--- a/client/coral-embed-stream/src/reducers/index.js
+++ b/client/coral-embed-stream/src/reducers/index.js
@@ -1,5 +1,4 @@
import login from './login';
-import asset from './asset';
import embed from './embed';
import configure from './configure';
import stream from './stream';
@@ -7,7 +6,6 @@ import profile from './profile';
export default {
login,
- asset,
embed,
configure,
stream,
diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.css b/client/coral-embed-stream/src/tabs/stream/components/Comment.css
index 15d42bef0..58ff0e731 100644
--- a/client/coral-embed-stream/src/tabs/stream/components/Comment.css
+++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.css
@@ -22,6 +22,10 @@
.commentLevel0 {
padding-left: 0px;
+
+ &.highlightedComment {
+ margin-top: 8px;
+ }
}
.commentLevel1 {
@@ -41,8 +45,8 @@
}
.highlightedComment {
- padding-left: 15px;
- border-left: 3px solid rgb(35,118,216);
+ padding: 1px 15px 8px 15px;
+ background-color: #E3F2FD;
}
.bylineSecondary {
diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.js b/client/coral-embed-stream/src/tabs/stream/components/Comment.js
index f17e7adec..acd791ba4 100644
--- a/client/coral-embed-stream/src/tabs/stream/components/Comment.js
+++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.js
@@ -13,6 +13,7 @@ import styles from './Comment.css';
import { THREADING_LEVEL } from '../../../constants/stream';
import merge from 'lodash/merge';
import mapValues from 'lodash/mapValues';
+import get from 'lodash/get';
import LoadMore from './LoadMore';
import { getEditableUntilDate } from './util';
@@ -169,7 +170,7 @@ export default class Comment extends React.Component {
postFlag: PropTypes.func.isRequired,
deleteAction: PropTypes.func.isRequired,
parentId: PropTypes.string,
- highlighted: PropTypes.string,
+ highlighted: PropTypes.object,
notify: PropTypes.func.isRequired,
postComment: PropTypes.func.isRequired,
depth: PropTypes.number.isRequired,
@@ -186,28 +187,7 @@ export default class Comment extends React.Component {
postDontAgree: PropTypes.func,
animateEnter: PropTypes.bool,
commentClassNames: PropTypes.array,
- comment: PropTypes.shape({
- depth: PropTypes.number,
- action_summaries: PropTypes.array.isRequired,
- body: PropTypes.string.isRequired,
- id: PropTypes.string.isRequired,
- tags: PropTypes.arrayOf(
- PropTypes.shape({
- name: PropTypes.string,
- })
- ),
- replies: PropTypes.object,
- user: PropTypes.shape({
- id: PropTypes.string.isRequired,
- username: PropTypes.string.isRequired,
- }).isRequired,
- editing: PropTypes.shape({
- edited: PropTypes.bool,
-
- // ISO8601
- editableUntil: PropTypes.string,
- }),
- }).isRequired,
+ comment: PropTypes.object.isRequired,
setCommentStatus: PropTypes.func.isRequired,
// edit a comment, passed (id, asset_id, { body })
@@ -301,6 +281,14 @@ export default class Comment extends React.Component {
this.props.setActiveReplyBox('');
};
+ undoStatus = () =>
+ this.props.setCommentStatus({
+ commentId: this.props.comment.id,
+ status: this.props.comment.status_history[
+ this.props.comment.status_history.length - 2
+ ].type,
+ });
+
// getVisibileReplies returns a list containing comments
// which were authored by current user or comes before the `idCursor`.
getVisibileReplies() {
@@ -329,6 +317,27 @@ export default class Comment extends React.Component {
return view;
}
+ /**
+ * getConditionalClassNames
+ * conditionalClassNames adds classNames based on condition
+ * classnames is an array of objects with key as classnames and value as conditions
+ * i.e:
+ * {
+ * 'myClassName': { tags: [STAFF]}
+ * }
+ *
+ * This will add myClassName to comments tagged with STAFF TAG.
+ **/
+ getConditionalClassNames() {
+ const { commentClassNames = [] } = this.props;
+ return mapValues(merge({}, ...commentClassNames), condition => {
+ if (condition.tags) {
+ return condition.tags.some(tag => hasTag(this.props.comment.tags, tag));
+ }
+ return false;
+ });
+ }
+
componentDidMount() {
this._isMounted = true;
if (this.editWindowExpiryTimeout) {
@@ -343,25 +352,51 @@ export default class Comment extends React.Component {
}, Math.max(msLeftToEdit, 0));
}
}
+
componentWillUnmount() {
if (this.editWindowExpiryTimeout) {
this.editWindowExpiryTimeout = clearTimeout(this.editWindowExpiryTimeout);
}
this._isMounted = false;
}
- render() {
+
+ renderReplyBox() {
+ const {
+ asset,
+ depth,
+ comment,
+ parentId,
+ postComment,
+ currentUser,
+ setActiveReplyBox,
+ maxCharCount,
+ notify,
+ charCountEnable,
+ } = this.props;
+ return (
+
+ );
+ }
+
+ renderReplies(view) {
const {
asset,
- data,
- root,
depth,
comment,
postFlag,
- parentId,
highlighted,
postComment,
currentUser,
- postDontAgree,
setActiveReplyBox,
activeReplyBox,
loadMore,
@@ -372,39 +407,47 @@ export default class Comment extends React.Component {
charCountEnable,
showSignInDialog,
liveUpdates,
- animateEnter,
emit,
- commentClassNames = [],
} = this.props;
+ return (
+
+ {view.map(reply => {
+ return (
+
+ );
+ })}
+
+ );
+ }
- if (!highlighted && this.commentIsRejected(comment)) {
- return (
- {
- this.props.setCommentStatus({
- commentId: comment.id,
- status:
- comment.status_history[comment.status_history.length - 2].type,
- });
- }}
- />
- );
- }
-
- if (this.commentIsIgnored(comment)) {
- return ;
- }
-
- const view = this.getVisibileReplies();
-
- // Inactive comments can be viewed by moderators and admins (e.g. using permalinks).
- const isActive = isCommentActive(comment.status);
-
+ renderLoadMoreReplies(view) {
+ const { comment } = this.props;
const { loadingState } = this.state;
- const isPending = comment.id.indexOf('pending') >= 0;
- const isHighlighted = highlighted === comment.id;
-
const hasMoreComments =
comment.replies &&
(comment.replies.hasNextPage ||
@@ -412,6 +455,57 @@ export default class Comment extends React.Component {
const moreRepliesCount = this.hasIgnoredReplies()
? -1
: comment.replyCount - view.length;
+ return (
+
+
+
+ );
+ }
+
+ renderRepliesContainer() {
+ const { highlighted, comment } = this.props;
+
+ // Only render highlighted reply when we are the parent of it.
+ if (get(highlighted, 'parent.id') === comment.id) {
+ return this.renderReplies([highlighted]);
+ }
+
+ // Otherwise render replies in current view and a load more button if needed.
+ const view = this.getVisibileReplies();
+ return [this.renderReplies(view), this.renderLoadMoreReplies(view)];
+ }
+
+ renderComment() {
+ const {
+ asset,
+ data,
+ root,
+ depth,
+ comment,
+ postFlag,
+ parentId,
+ highlighted,
+ currentUser,
+ postDontAgree,
+ deleteAction,
+ disableReply,
+ maxCharCount,
+ notify,
+ charCountEnable,
+ showSignInDialog,
+ } = this.props;
+
+ // Inactive comments can be viewed by moderators and admins (e.g. using permalinks).
+ const isActive = isCommentActive(comment.status);
+
+ const isPending = comment.id.indexOf('pending') >= 0;
+ const isHighlighted = highlighted && highlighted.id === comment.id;
const flagSummary = getActionSummary('FlagActionSummary', comment);
const dontAgreeSummary = getActionSummary(
'DontAgreeActionSummary',
@@ -424,38 +518,6 @@ export default class Comment extends React.Component {
myFlag = dontAgreeSummary.find(s => s.current_user);
}
- /**
- * conditionClassNames
- * adds classNames based on condition
- * classnames is an array of objects with key as classnames and value as conditions
- * i.e:
- * {
- * 'myClassName': { tags: [STAFF]}
- * }
- *
- * This will add myClassName to comments tagged with STAFF TAG.
- * **/
- const conditionalClassNames = mapValues(
- merge({}, ...commentClassNames),
- condition => {
- if (condition.tags) {
- return condition.tags.some(tag => hasTag(comment.tags, tag));
- }
- return false;
- }
- );
-
- const rootClassName = cn(
- 'talk-stream-comment-wrapper',
- `talk-stream-comment-wrapper-level-${depth}`,
- styles.root,
- styles[`rootLevel${depth}`],
- {
- ...conditionalClassNames,
- [styles.enter]: animateEnter,
- }
- );
-
const commentClassName = cn(
'talk-stream-comment',
`talk-stream-comment-level-${depth}`,
@@ -482,183 +544,183 @@ export default class Comment extends React.Component {
};
return (
-