diff --git a/client/coral-admin/src/components/App.css b/client/coral-admin/src/components/App.css
new file mode 100644
index 000000000..39c7cf942
--- /dev/null
+++ b/client/coral-admin/src/components/App.css
@@ -0,0 +1,11 @@
+:global {
+ html, body, #root, #root > div {
+ min-height: 100%;
+ }
+
+ body {
+ margin: 0;
+ background-color: #FAFAFA;
+ font-family: 'Roboto', sans-serif;
+ }
+}
diff --git a/client/coral-admin/src/components/App.js b/client/coral-admin/src/components/App.js
index 52535f91a..a7ab1e336 100644
--- a/client/coral-admin/src/components/App.js
+++ b/client/coral-admin/src/components/App.js
@@ -1,5 +1,6 @@
import React from 'react';
import ToastContainer from './ToastContainer';
+import './App.css';
import 'material-design-lite';
import AppRouter from '../AppRouter';
diff --git a/client/coral-embed-stream/src/tabs/profile/components/Comment.css b/client/coral-embed-stream/src/tabs/profile/components/Comment.css
index 9004a1e6c..c2bf841d5 100644
--- a/client/coral-embed-stream/src/tabs/profile/components/Comment.css
+++ b/client/coral-embed-stream/src/tabs/profile/components/Comment.css
@@ -31,6 +31,7 @@
font-weight: bold;
font-size: 12px;
color: #757575;
+ cursor: pointer;
}
.commentSummary {
diff --git a/client/coral-embed-stream/src/tabs/profile/components/Comment.js b/client/coral-embed-stream/src/tabs/profile/components/Comment.js
index 171172dab..badeb4262 100644
--- a/client/coral-embed-stream/src/tabs/profile/components/Comment.js
+++ b/client/coral-embed-stream/src/tabs/profile/components/Comment.js
@@ -11,16 +11,6 @@ import { getTotalReactionsCount } from 'coral-framework/utils';
import t from 'coral-framework/services/i18n';
class Comment extends React.Component {
- goToStory = () => {
- this.props.navigate(this.props.comment.asset.url);
- };
-
- goToConversation = () => {
- this.props.navigate(
- `${this.props.comment.asset.url}?commentId=${this.props.comment.id}`
- );
- };
-
render() {
const { comment, root } = this.props;
const reactionCount = getTotalReactionsCount(comment.action_summaries);
@@ -76,8 +66,8 @@ class Comment extends React.Component {
{t('common.story')}:{' '}
{comment.asset.title ? comment.asset.title : comment.asset.url}
@@ -87,7 +77,13 @@ class Comment extends React.Component {
-
-
+
{t('view_conversation')}
diff --git a/client/coral-framework/components/Popup.js b/client/coral-framework/components/Popup.js
index 53d8f7cd8..339ddb806 100644
--- a/client/coral-framework/components/Popup.js
+++ b/client/coral-framework/components/Popup.js
@@ -37,7 +37,8 @@ export default class Popup extends Component {
this.onBlur();
};
- // Use `onunload` instead of `onbeforeunload` which is not supported in IOS Safari.
+ // Use `onunload` instead of `onbeforeunload` which is not supported in iOS
+ // Safari.
this.ref.onunload = () => {
this.onUnload();
@@ -46,10 +47,15 @@ export default class Popup extends Component {
}
this.resetCallbackInterval = setInterval(() => {
- if (this.ref && this.ref.onload === null) {
- clearInterval(this.resetCallbackInterval);
- this.resetCallbackInterval = null;
- this.setCallbacks();
+ try {
+ if (this.ref && this.ref.onload === null) {
+ clearInterval(this.resetCallbackInterval);
+ this.resetCallbackInterval = null;
+ this.setCallbacks();
+ }
+ } catch (err) {
+ // We could be getting a security exception here if the login page
+ // gets redirected to another domain to authenticate.
}
}, 50);
diff --git a/locales/en.yml b/locales/en.yml
index 9a26fbdb6..36cf956b4 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -162,7 +162,7 @@ en:
suspect_word_title: "Suspect words list"
suspect_word_text: "Comments which contain these words or phrases (not case-sensitive) will be highlighted in the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list."
tech_settings: "Tech Settings"
- organization_information: "Organization information"
+ organization_information: "Organization Information"
organization_info_copy: "We use this information in email notifications generated by Talk. This connects the messages to your organization, and provides a way for users to contact you if they have an issue with their account."
organization_info_copy_2: "We recommend creating a generic email account (eg. community@yournewsroom.com) for this purpose. This means it can remain consistent over time, and doesn't expose a name that users could target if their account were blocked."
organization_details: "Organization Details"
diff --git a/middleware/staticTemplate.js b/middleware/staticTemplate.js
index f16caaea9..d8137693e 100644
--- a/middleware/staticTemplate.js
+++ b/middleware/staticTemplate.js
@@ -94,9 +94,13 @@ const createResolveFactory = (() => {
module.exports = async (req, res, next) => {
try {
- // Attach the custom css url.
- const { customCssUrl } = await SettingsService.select('customCssUrl');
+ // Attach the custom css url and organization name.
+ const { customCssUrl, organizationName } = await SettingsService.select(
+ 'customCssUrl',
+ 'organizationName'
+ );
res.locals.customCssUrl = customCssUrl;
+ res.locals.organizationName = organizationName;
} catch (err) {
console.warn(err);
}
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index d6105c594..000000000
--- a/package-lock.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "talk",
- "version": "4.3.0",
- "lockfileVersion": 1,
- "requires": true,
- "dependencies": {
- "exenv": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
- "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
- },
- "react-side-effect": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-1.1.5.tgz",
- "integrity": "sha512-Z2ZJE4p/jIfvUpiUMRydEVpQRf2f8GMHczT6qLcARmX7QRb28JDBTpnM2g/i5y/p7ZDEXYGHWg0RbhikE+hJRw==",
- "requires": {
- "exenv": "1.2.2",
- "shallowequal": "1.0.2"
- }
- },
- "shallowequal": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.0.2.tgz",
- "integrity": "sha512-zlVXeVUKvo+HEv1e2KQF/csyeMKx2oHvatQ9l6XjCUj3agvC8XGf6R9HvIPDSmp8FNPvx7b5kaEJTRi7CqxtEw=="
- }
- }
-}
diff --git a/package.json b/package.json
index f6ff46a59..01b671dde 100644
--- a/package.json
+++ b/package.json
@@ -219,6 +219,7 @@
"babel-plugin-dynamic-import-node": "^1.1.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"browserstack-local": "^1.3.0",
+ "casual": "^1.5.19",
"chai": "^3.5.0",
"chai-as-promised": "^6.0.0",
"chai-datetime": "^1.5.0",
diff --git a/plugins/talk-plugin-local-auth/client/actions.js b/plugins/talk-plugin-local-auth/client/actions.js
new file mode 100644
index 000000000..972e1f6fd
--- /dev/null
+++ b/plugins/talk-plugin-local-auth/client/actions.js
@@ -0,0 +1,9 @@
+import * as actions from './constants';
+
+export const startAttach = () => ({
+ type: actions.START_ATTACH,
+});
+
+export const finishAttach = () => ({
+ type: actions.FINISH_ATTACH,
+});
diff --git a/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js b/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js
index dc7e0d974..a5bfd7096 100644
--- a/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js
+++ b/plugins/talk-plugin-local-auth/client/components/AddEmailAddressDialog.js
@@ -45,6 +45,10 @@ class AddEmailAddressDialog extends React.Component {
),
};
+ componentDidMount() {
+ this.props.startAttach();
+ }
+
onChange = e => {
const { name, value } = e.target;
this.setState(
@@ -99,7 +103,13 @@ class AddEmailAddressDialog extends React.Component {
});
};
- confirmChanges = async () => {
+ finish = () => {
+ this.props.finishAttach();
+ };
+
+ confirmChanges = async e => {
+ e.preventDefault();
+
if (!this.validate()) {
this.showErrors();
return;
@@ -113,7 +123,11 @@ class AddEmailAddressDialog extends React.Component {
email: emailAddress,
password: confirmPassword,
});
- this.props.notify('success', 'Email Added!');
+
+ this.props.notify(
+ 'success',
+ t('talk-plugin-local-auth.add_email.added.alert')
+ );
this.goToNextStep();
} catch (err) {
this.props.notify('error', getErrorMessages(err));
@@ -143,13 +157,13 @@ class AddEmailAddressDialog extends React.Component {
)}
{step === 1 &&
!settings.requireEmailConfirmation && (
- {}} />
+
)}
{step === 1 &&
settings.requireEmailConfirmation && (
{}}
+ done={this.finish}
/>
)}
@@ -161,6 +175,8 @@ AddEmailAddressDialog.propTypes = {
attachLocalAuth: PropTypes.func.isRequired,
notify: PropTypes.func.isRequired,
root: PropTypes.object.isRequired,
+ startAttach: PropTypes.func.isRequired,
+ finishAttach: PropTypes.func.isRequired,
};
export default AddEmailAddressDialog;
diff --git a/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js b/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js
index 8d3fc308f..de296c812 100644
--- a/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js
+++ b/plugins/talk-plugin-local-auth/client/components/AddEmailContent.js
@@ -41,7 +41,7 @@ const AddEmailContent = ({
-
diff --git a/plugins/talk-plugin-local-auth/client/constants.js b/plugins/talk-plugin-local-auth/client/constants.js
new file mode 100644
index 000000000..d410a10fa
--- /dev/null
+++ b/plugins/talk-plugin-local-auth/client/constants.js
@@ -0,0 +1,4 @@
+const prefix = 'TALK_LOCAL_AUTH';
+
+export const START_ATTACH = `${prefix}_START_ATTACH`;
+export const FINISH_ATTACH = `${prefix}_FINISH_ATTACH`;
diff --git a/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js b/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js
index 841dc8420..f6de3b39c 100644
--- a/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js
+++ b/plugins/talk-plugin-local-auth/client/containers/AddEmailAddressDialog.js
@@ -3,10 +3,16 @@ import { bindActionCreators } from 'redux';
import { connect, withFragments, excludeIf } from 'plugin-api/beta/client/hocs';
import AddEmailAddressDialog from '../components/AddEmailAddressDialog';
import { notify } from 'coral-framework/actions/notification';
-
import { withAttachLocalAuth } from '../hocs';
+import { startAttach, finishAttach } from '../actions';
+import get from 'lodash/get';
-const mapDispatchToProps = dispatch => bindActionCreators({ notify }, dispatch);
+const mapStateToProps = ({ talkPluginLocalAuth: state }) => ({
+ inProgress: state.inProgress,
+});
+
+const mapDispatchToProps = dispatch =>
+ bindActionCreators({ notify, startAttach, finishAttach }, dispatch);
const withData = withFragments({
root: gql`
@@ -14,6 +20,13 @@ const withData = withFragments({
me {
id
email
+ state {
+ status {
+ username {
+ status
+ }
+ }
+ }
}
settings {
requireEmailConfirmation
@@ -23,8 +36,13 @@ const withData = withFragments({
});
export default compose(
- connect(null, mapDispatchToProps),
+ connect(mapStateToProps, mapDispatchToProps),
withAttachLocalAuth,
withData,
- excludeIf(({ root: { me } }) => !me || me.email)
+ excludeIf(
+ ({ root: { me }, inProgress }) =>
+ !me ||
+ get(me, 'state.status.username.status') === 'UNSET' ||
+ (me.email && !inProgress)
+ )
)(AddEmailAddressDialog);
diff --git a/plugins/talk-plugin-local-auth/client/index.js b/plugins/talk-plugin-local-auth/client/index.js
index 9c89e0490..a4dc01ac2 100644
--- a/plugins/talk-plugin-local-auth/client/index.js
+++ b/plugins/talk-plugin-local-auth/client/index.js
@@ -1,10 +1,12 @@
import ChangePassword from './containers/ChangePassword';
import AddEmailAddressDialog from './containers/AddEmailAddressDialog';
import Profile from './containers/Profile';
-import translations from './translations.yml';
+import translations from '../translations.yml';
import graphql from './graphql';
+import reducer from './reducer';
export default {
+ reducer,
translations,
slots: {
profileHeader: [Profile],
diff --git a/plugins/talk-plugin-local-auth/client/reducer.js b/plugins/talk-plugin-local-auth/client/reducer.js
new file mode 100644
index 000000000..11666e505
--- /dev/null
+++ b/plugins/talk-plugin-local-auth/client/reducer.js
@@ -0,0 +1,20 @@
+import * as actions from './constants';
+
+const initialState = {
+ inProgress: false,
+};
+
+export default function reducer(state = initialState, action) {
+ switch (action.type) {
+ case actions.START_ATTACH:
+ return {
+ inProgress: true,
+ };
+ case actions.FINISH_ATTACH:
+ return {
+ inProgress: false,
+ };
+ default:
+ return state;
+ }
+}
diff --git a/plugins/talk-plugin-local-auth/index.js b/plugins/talk-plugin-local-auth/index.js
index 7e4ee6a3c..fabe1dd2b 100644
--- a/plugins/talk-plugin-local-auth/index.js
+++ b/plugins/talk-plugin-local-auth/index.js
@@ -4,7 +4,7 @@ const mutators = require('./server/mutators');
const path = require('path');
module.exports = {
- translations: path.join(__dirname, 'server', 'translations.yml'),
+ translations: path.join(__dirname, 'translations.yml'),
typeDefs,
mutators,
resolvers,
diff --git a/plugins/talk-plugin-local-auth/server/translations.yml b/plugins/talk-plugin-local-auth/server/translations.yml
deleted file mode 100644
index d81de6c6d..000000000
--- a/plugins/talk-plugin-local-auth/server/translations.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-en:
- email:
- email_change_original:
- subject: Email change
- body: Your email address has been changed from {0} to {1}. If you did not initiate this change, please contact {2}. # TODO: update translation
- error:
- NO_LOCAL_PROFILE: No existing email address is associated with this account.
- LOCAL_PROFILE: An email address is already associated with this account.
- INCORRECT_PASSWORD: Provided password was incorrect.
diff --git a/plugins/talk-plugin-local-auth/client/translations.yml b/plugins/talk-plugin-local-auth/translations.yml
similarity index 91%
rename from plugins/talk-plugin-local-auth/client/translations.yml
rename to plugins/talk-plugin-local-auth/translations.yml
index fcb80782a..7f67e33bd 100644
--- a/plugins/talk-plugin-local-auth/client/translations.yml
+++ b/plugins/talk-plugin-local-auth/translations.yml
@@ -1,4 +1,12 @@
en:
+ email:
+ email_change_original:
+ subject: Email change
+ body: Your email address has been changed from {0} to {1}. If you did not request this change, please contact {2}.
+ error:
+ NO_LOCAL_PROFILE: No existing email address is associated with this account.
+ LOCAL_PROFILE: An email address is already associated with this account.
+ INCORRECT_PASSWORD: Provided password was incorrect.
talk-plugin-local-auth:
change_password:
change_password: "Change Password"
@@ -43,7 +51,7 @@ en:
email_does_not_match: "Email Address does not match"
insert_password: "Insert Password:"
required_field: "This field is required"
- done: "done"
+ done: "Done"
content:
title: "Add an Email Address"
description: "For your added security, we require users to add an email address to their accounts. Your email address will be used to:"
@@ -59,6 +67,7 @@ en:
subtitle: "Need to change your email address?"
description_2: "You can change your account settings by visiting"
path: "My Profile > Settings"
+ alert: "Email Added!"
es:
talk-plugin-local-auth:
change_password:
diff --git a/plugins/talk-plugin-profile-data/translations.yml b/plugins/talk-plugin-profile-data/translations.yml
index 39b810cca..51555d305 100644
--- a/plugins/talk-plugin-profile-data/translations.yml
+++ b/plugins/talk-plugin-profile-data/translations.yml
@@ -7,7 +7,7 @@ en:
date: "When you wrote the comment"
url: "The permalink URL for the comment"
body: "The comment text"
- asset_url: "The URL on the article or story where the comment appears"
+ asset_url: "The URL of the article or story where the comment appears"
confirm: "Download My Comment History"
email:
download:
@@ -17,7 +17,7 @@ en:
delete:
subject: "Your account for {0} is scheduled to be deleted"
body: |
- A request to delete your account was received. Your account is scheduled for deletion on {1}.
+ A request to delete your account was received. Your account is scheduled for deletion on {1}.
After that time all of your comments will be removed from the site, all of your comments will be removed from our database, and your username and email address will be removed from our system.
diff --git a/plugins/talk-plugin-rich-text/client/components/Editor.css b/plugins/talk-plugin-rich-text/client/components/Editor.css
index 2259d01a3..be2646121 100644
--- a/plugins/talk-plugin-rich-text/client/components/Editor.css
+++ b/plugins/talk-plugin-rich-text/client/components/Editor.css
@@ -1,5 +1,12 @@
+@custom-media --narrow-viewport (max-width: 420px);
+
.commentContent {
composes: content from "./CommentContent.css";
+
+ /* Prevent zoom on narrow viewports */
+ @media (--narrow-viewport) {
+ font-size: 16px;
+ }
}
.placeholder {
diff --git a/plugins/talk-plugin-rich-text/client/components/Editor.js b/plugins/talk-plugin-rich-text/client/components/Editor.js
index e09931d38..99d2be617 100644
--- a/plugins/talk-plugin-rich-text/client/components/Editor.js
+++ b/plugins/talk-plugin-rich-text/client/components/Editor.js
@@ -8,6 +8,7 @@ import RTE from './rte/RTE';
import { Icon } from 'plugin-api/beta/client/components/ui';
import { Bold, Italic, Blockquote } from './rte/features';
import { t } from 'plugin-api/beta/client/services';
+import bowser from 'bowser';
class Editor extends React.Component {
ref = null;
@@ -40,7 +41,9 @@ class Editor extends React.Component {
}
});
}
- if (this.props.isReply) {
+
+ // Skip IOS due to a bug, see https://www.pivotaltracker.com/story/show/157434928
+ if (this.props.isReply && !bowser.ios) {
this.ref.focus();
}
}
diff --git a/public/css/dev.css b/public/css/dev.css
new file mode 100644
index 000000000..2fd6c1982
--- /dev/null
+++ b/public/css/dev.css
@@ -0,0 +1,9 @@
+.container {
+ width: auto;
+ max-width: 680px;
+ padding: 0 15px;
+}
+
+.graphiql em {
+ font-family: georgia;
+}
diff --git a/routes/dev/assets.js b/routes/dev/assets.js
index cb1f27e43..aea0628e3 100644
--- a/routes/dev/assets.js
+++ b/routes/dev/assets.js
@@ -1,49 +1,58 @@
const express = require('express');
const router = express.Router();
-
-const errors = require('../../errors');
-const Assets = require('../../services/assets');
-
-const body =
- 'Lorem ipsum dolor sponge amet, consectetur adipiscing clam. Ut lobortis sollicitudin pillar a ornare. Curabitur dignissim vestibulum cay non rhoncus. Cras laoreet ante vel nunc hendrerit, shelf imperdiet neque egestas. Suspendisse aliquet iaculis fermentum. Talk volutpat, tellus posuere laoreet consequat, mi lacus laoreet massa, sed vehicula mauris velit non lectus. Integer non trust nec neque congue faucibus porttitor sit amet elkhorn.';
+const casual = require('casual');
+const { ErrNotFound } = require('../../errors');
+const Asset = require('../../models/asset');
router.get('/id/:asset_id', async (req, res, next) => {
try {
- const asset = await Assets.findById(req.params.asset_id);
+ const asset = await Asset.findOne({ id: req.params.asset_id });
if (asset === null) {
- return next(errors.ErrNotFound);
+ throw new ErrNotFound();
}
res.render('dev/article', {
title: asset.title,
asset_id: asset.id,
asset_url: asset.url,
- body: '',
- basePath: '/client/embed/stream',
});
} catch (err) {
return next(err);
}
});
+router.get('/random', (req, res) => {
+ const title = casual.title;
+
+ res.redirect(`./title/${title.replace(/ /g, '-')}`);
+});
+
router.get('/title/:asset_title', (req, res) => {
- return res.render('dev/article', {
+ res.render('dev/article', {
title: req.params.asset_title.split('-').join(' '),
asset_url: '',
asset_id: null,
- body: body,
- basePath: '/client/embed/stream',
});
});
router.get('/', async (req, res, next) => {
- let skip = req.query.skip ? parseInt(req.query.skip) : 0;
- let limit = req.query.limit ? parseInt(req.query.limit) : 25;
-
try {
- const assets = await Assets.all(skip, limit);
+ const skip = req.query.skip ? parseInt(req.query.skip) : 0;
+ const limit = req.query.limit ? parseInt(req.query.limit) : 6;
+
+ const [assets, count] = await Promise.all([
+ Asset.find({})
+ .sort({ created_at: 1 })
+ .limit(limit)
+ .skip(skip),
+ Asset.count(),
+ ]);
+
res.render('dev/articles', {
- assets: assets,
+ skip,
+ limit,
+ count,
+ assets,
});
} catch (err) {
return next(err);
diff --git a/routes/dev/index.js b/routes/dev/index.js
index ac72db254..58daca583 100644
--- a/routes/dev/index.js
+++ b/routes/dev/index.js
@@ -16,8 +16,6 @@ router.get('/', staticTemplate, async (req, res) => {
title: 'Coral Talk',
asset_url: '',
asset_id: '',
- body: '',
- basePath: '/static/embed/stream',
});
}
});
diff --git a/services/assets.js b/services/assets.js
index 794145eb3..5f1fa989d 100644
--- a/services/assets.js
+++ b/services/assets.js
@@ -232,11 +232,5 @@ module.exports = class AssetsService {
await AssetModel.remove({
id: srcAssetID,
});
-
- // That's it!
- }
-
- static all(limit = undefined) {
- return AssetModel.find({}).limit(limit);
}
};
diff --git a/views/admin.ejs b/views/admin.ejs
index b8f775d79..89cc150f7 100644
--- a/views/admin.ejs
+++ b/views/admin.ejs
@@ -3,16 +3,11 @@
Talk - Coral Admin
-
+ <%- include partials/head %>
-
-
- <%- include partials/head %>
+ <%- include partials/custom-css %>
diff --git a/views/api/graphiql.ejs b/views/api/graphiql.ejs
index b6d609de6..8007569b3 100644
--- a/views/api/graphiql.ejs
+++ b/views/api/graphiql.ejs
@@ -5,13 +5,17 @@
GraphiQL
+ <%- include ../partials/dev %>
@@ -20,6 +24,8 @@
+ <%- include ../partials/dev-nav %>
+
-