diff --git a/.dockerignore b/.dockerignore
index 3c3629e64..f05b1f265 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1 +1,2 @@
node_modules
+test
diff --git a/.eslintrc.json b/.eslintrc.json
index 8b737cbd2..34b46b83b 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -17,7 +17,7 @@
"no-template-curly-in-string": [1],
"no-unsafe-negation": [1],
"array-callback-return": [1],
- "eqeqeq": [2],
+ "eqeqeq": [2, "smart"],
"no-eval": [2],
"no-global-assign": [2],
"no-implied-eval": [2],
diff --git a/.gitignore b/.gitignore
index 2ec55accd..777dcc7f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,13 +3,13 @@ npm-debug.log*
dist
!dist/coral-admin
dist/coral-admin/bundle.js
-tests/e2e/reports
+test/e2e/reports
.DS_Store
*.iml
*.swp
dump.rdb
.env
-gaba.cfg
+*.cfg
.idea/
coverage/
yarn.lock
diff --git a/.nodemon.json b/.nodemon.json
index 834e6c054..4c48c707e 100644
--- a/.nodemon.json
+++ b/.nodemon.json
@@ -1,4 +1,5 @@
{
"verbose": true,
- "ignore": ["tests/*", "client/*", "dist/*"]
+ "ignore": ["test/*", "client/*", "dist/*"],
+ "ext": "js,json,graphql"
}
diff --git a/.travis-yml b/.travis-yml
deleted file mode 100644
index 85242354a..000000000
--- a/.travis-yml
+++ /dev/null
@@ -1,3 +0,0 @@
-language: node_js
-node_js:
- - 4
diff --git a/app.js b/app.js
index 9cdad4ea3..c04e0cb4a 100644
--- a/app.js
+++ b/app.js
@@ -10,6 +10,8 @@ const RedisStore = require('connect-redis')(session);
const redis = require('./services/redis');
const csrf = require('csurf');
const errors = require('./errors');
+const graph = require('./graph');
+const apollo = require('graphql-server-express');
const app = express();
@@ -49,7 +51,7 @@ const session_opts = {
name: 'talk.sid',
cookie: {
secure: false,
- maxAge: 18000000, // 30 minutes for expiry.
+ maxAge: 36000000, // 1 hour for expiry.
},
store: new RedisStore({
ttl: 1800,
@@ -77,6 +79,23 @@ app.use(session(session_opts));
app.use(passport.initialize());
app.use(passport.session());
+//==============================================================================
+// GraphQL Router
+//==============================================================================
+
+// GraphQL endpoint.
+app.use('/api/v1/graph/ql', apollo.graphqlExpress(graph.createGraphOptions));
+
+// Only include the graphiql tool if we aren't in production mode.
+if (app.get('env') !== 'production') {
+
+ // Interactive graphiql interface.
+ app.use('/api/v1/graph/iql', apollo.graphiqlExpress({
+ endpointURL: '/api/v1/graph/ql'
+ }));
+
+}
+
//==============================================================================
// CSRF MIDDLEWARE
//==============================================================================
diff --git a/bin/cli-assets b/bin/cli-assets
index 008460f9e..f7d6a0bbb 100755
--- a/bin/cli-assets
+++ b/bin/cli-assets
@@ -8,7 +8,7 @@ const program = require('commander');
const pkg = require('../package.json');
const parseDuration = require('parse-duration');
const Table = require('cli-table');
-const Asset = require('../models/asset');
+const AssetModel = require('../models/asset');
const mongoose = require('../services/mongoose');
const scraper = require('../services/scraper');
const util = require('../util');
@@ -22,7 +22,7 @@ util.onshutdown([
* Lists all the assets registered in the database.
*/
function listAssets() {
- Asset
+ AssetModel
.find({})
.sort({'created_at': 1})
.then((asset) => {
@@ -56,7 +56,7 @@ function refreshAssets(ageString) {
const ageMs = parseDuration(ageString);
const age = new Date(now - ageMs);
- Asset.find({
+ AssetModel.find({
$or: [
{
scraped: {
diff --git a/bin/cli-settings b/bin/cli-settings
index 5fdfbc38f..7854942c0 100755
--- a/bin/cli-settings
+++ b/bin/cli-settings
@@ -6,7 +6,7 @@
const program = require('commander');
const mongoose = require('../services/mongoose');
-const Setting = require('../models/setting');
+const SettingsService = require('../services/settings');
const util = require('../util');
// Register the shutdown criteria.
@@ -22,10 +22,16 @@ program
.command('init')
.description('initilizes the talk settings')
.action(() => {
- const defaults = {id: '1', moderation: 'pre'};
+ const defaults = {
+ moderation: 'PRE',
+ wordlist: {
+ banned: [],
+ suspect: []
+ }
+ };
- Setting
- .update({id: '1'}, {$setOnInsert: defaults}, {upsert: true})
+ SettingsService
+ .init(defaults)
.then(() => {
console.log('Created settings object.');
util.shutdown();
diff --git a/bin/cli-users b/bin/cli-users
index fd9e30c7f..a7e9cdbda 100755
--- a/bin/cli-users
+++ b/bin/cli-users
@@ -7,7 +7,8 @@
const program = require('commander');
const pkg = require('../package.json');
const prompt = require('prompt');
-const User = require('../models/user');
+const UsersService = require('../services/users');
+const UserModel = require('../models/user');
const mongoose = require('../services/mongoose');
const util = require('../util');
const Table = require('cli-table');
@@ -76,16 +77,22 @@ function createUser(options) {
});
})
.then((result) => {
- return User.createLocalUser(result.email.trim(), result.password.trim(), result.displayName.trim())
+ return UsersService.createLocalUser(result.email.trim(), result.password.trim(), result.displayName.trim())
.then((user) => {
console.log(`Created user ${user.id}.`);
- if (result.role && result.role.length > 0) {
- return User
- .addRoleToUser(user.id, result.role.trim())
+ let role = result.role ? result.role.trim() : null;
+
+ if (role && role.length > 0) {
+ return UsersService
+ .addRoleToUser(user.id, role)
.then(() => {
console.log(`Added the admin ${result.role.trim()} to User ${user.id}.`);
util.shutdown();
+ })
+ .catch((err) => {
+ console.error(err);
+ util.shutdown();
});
} else {
util.shutdown();
@@ -102,7 +109,7 @@ function createUser(options) {
* Deletes a user.
*/
function deleteUser(userID) {
- User
+ UserModel
.findOneAndRemove({
id: userID
})
@@ -148,7 +155,7 @@ function passwd(userID) {
return;
}
- User
+ UsersService
.changePassword(userID, result.password)
.then(() => {
console.log('Password changed.');
@@ -168,7 +175,7 @@ function updateUser(userID, options) {
const updates = [];
if (options.email && typeof options.email === 'string' && options.email.length > 0) {
- let q = User.update({
+ let q = UserModel.update({
'id': userID,
'profiles.provider': 'local'
}, {
@@ -181,7 +188,7 @@ function updateUser(userID, options) {
}
if (options.name && typeof options.name === 'string' && options.name.length > 0) {
- let q = User.update({
+ let q = UserModel.update({
'id': userID
}, {
$set: {
@@ -208,7 +215,7 @@ function updateUser(userID, options) {
* Lists all the users registered in the database.
*/
function listUsers() {
- User
+ UsersService
.all()
.then((users) => {
let table = new Table({
@@ -248,7 +255,7 @@ function listUsers() {
* @param {String} srcUserID id of the user to which is the source of the merge
*/
function mergeUsers(dstUserID, srcUserID) {
- User
+ UsersService
.mergeUsers(dstUserID, srcUserID)
.then(() => {
console.log(`User ${srcUserID} was merged into user ${dstUserID}.`);
@@ -266,7 +273,7 @@ function mergeUsers(dstUserID, srcUserID) {
* @param {String} role the role to add
*/
function addRole(userID, role) {
- User
+ UsersService
.addRoleToUser(userID, role)
.then(() => {
console.log(`Added the ${role} role to User ${userID}.`);
@@ -284,7 +291,7 @@ function addRole(userID, role) {
* @param {String} role the role to remove
*/
function removeRole(userID, role) {
- User
+ UsersService
.removeRoleFromUser(userID, role)
.then(() => {
console.log(`Removed the ${role} role from User ${userID}.`);
@@ -301,8 +308,8 @@ function removeRole(userID, role) {
* @param {String} userID id of the user to ban
*/
function ban(userID) {
- User
- .setStatus(userID, 'banned', '')
+ UsersService
+ .setStatus(userID, 'BANNED')
.then(() => {
console.log(`Banned the User ${userID}.`);
util.shutdown();
@@ -318,8 +325,8 @@ function ban(userID) {
* @param {String} userUD id of the user to remove the role from
*/
function unban(userID) {
- User
- .setStatus(userID, 'active', '')
+ UsersService
+ .setStatus(userID, 'ACTIVE')
.then(() => {
console.log(`Unban the User ${userID}.`);
util.shutdown();
@@ -335,7 +342,7 @@ function unban(userID) {
* @param {String} userID the ID of a user to disable
*/
function disableUser(userID) {
- User
+ UsersService
.disableUser(userID)
.then(() => {
console.log(`User ${userID} was disabled.`);
@@ -352,7 +359,7 @@ function disableUser(userID) {
* @param {String} userID the ID of a user to enable
*/
function enableUser(userID) {
- User
+ UsersService
.enableUser(userID)
.then(() => {
console.log(`User ${userID} was enabled.`);
diff --git a/circle.yml b/circle.yml
index b9cea6fed..160bc7bb9 100644
--- a/circle.yml
+++ b/circle.yml
@@ -3,12 +3,24 @@ machine:
version: 7
services:
- docker
+ - redis
+
+dependencies:
+ post:
+ # Lint the project here, before tests are ran.
+ - npm run lint
+
+database:
+ post:
+ # Initialize the settings in the database, this will create indicies for the
+ # database.
+ - ./bin/cli settings init
+ - sleep 2
test:
override:
- - MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml ./node_modules/.bin/mocha tests --reporter mocha-junit-reporter
- - npm run lint
- - npm run build
+ # Run the tests using the junit reporter.
+ - MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml NPM_PACKAGE_CONFIG_MOCHA_REPORTER=mocha-junit-reporter npm run test
deployment:
release:
diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js
index fb469588b..c3f9b1be1 100644
--- a/client/coral-admin/src/actions/auth.js
+++ b/client/coral-admin/src/actions/auth.js
@@ -11,7 +11,7 @@ export const checkLogin = () => dispatch => {
dispatch(checkLoginRequest());
coralApi('/auth')
.then(result => {
- const isAdmin = !!result.user.roles.filter(i => i === 'admin').length;
+ const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length;
dispatch(checkLoginSuccess(result.user, isAdmin));
})
.catch(error => dispatch(checkLoginFailure(error)));
diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js
index b5eb40231..178d3cc02 100644
--- a/client/coral-admin/src/components/CommentList.js
+++ b/client/coral-admin/src/components/CommentList.js
@@ -6,10 +6,10 @@ import Comment from 'components/Comment';
// Each action has different meaning and configuration
const modActions = {
- 'reject': {status: 'rejected', icon: 'close', key: 'r'},
- 'approve': {status: 'accepted', icon: 'done', key: 't'},
- 'flag': {status: 'flagged', icon: 'flag', filter: 'Untouched'},
- 'ban': {status: 'banned', icon: 'not interested'}
+ 'reject': {status: 'REJECTED', icon: 'close', key: 'r'},
+ 'approve': {status: 'ACCEPTED', icon: 'done', key: 't'},
+ 'flag': {status: 'FLAGGED', icon: 'flag', filter: 'Untouched'},
+ 'ban': {status: 'BANNED', icon: 'not interested'}
};
// Renders a comment list and allow performing actions
@@ -21,7 +21,7 @@ export default class CommentList extends React.Component {
comments: PropTypes.object.isRequired,
users: PropTypes.object.isRequired,
onClickAction: PropTypes.func,
-
+
// list of actions (flags, etc) associated with the comments
modActions: PropTypes.arrayOf(PropTypes.string).isRequired,
loading: PropTypes.bool,
diff --git a/client/coral-admin/src/containers/Community/Table.js b/client/coral-admin/src/containers/Community/Table.js
index c146e564a..254550e74 100644
--- a/client/coral-admin/src/containers/Community/Table.js
+++ b/client/coral-admin/src/containers/Community/Table.js
@@ -55,8 +55,8 @@ class Table extends Component {
className={styles.selectField}
label={lang.t('community.status')}
onChange={status => this.onCommenterStatusChange(row.id, status)}>
-
-
+
+
@@ -65,8 +65,8 @@ class Table extends Component {
label={lang.t('community.role')}
onChange={role => this.onRoleChange(row.id, role)}>
-
-
+
+
|
diff --git a/client/coral-admin/src/containers/Configure/CommentSettings.js b/client/coral-admin/src/containers/Configure/CommentSettings.js
index 9fb3d75da..0ccb5f820 100644
--- a/client/coral-admin/src/containers/Configure/CommentSettings.js
+++ b/client/coral-admin/src/containers/Configure/CommentSettings.js
@@ -28,7 +28,7 @@ const updateCharCount = (updateSettings, settingsError) => (event) => {
};
const updateModeration = (updateSettings, mod) => () => {
- const moderation = mod === 'pre' ? 'post' : 'pre';
+ const moderation = mod === 'PRE' ? 'POST' : 'PRE';
updateSettings({moderation});
};
@@ -70,15 +70,15 @@ const CommentSettings = ({fetchingSettings, title, updateSettings, settingsError
return (
{title}
-
+
+ checked={settings.moderation === 'PRE'} />
{lang.t('configure.enable-pre-moderation')}
-
+
{lang.t('configure.enable-pre-moderation-text')}
diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
index 41988e216..1b23de89a 100644
--- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
+++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
@@ -75,12 +75,12 @@ class ModerationContainer extends React.Component {
render () {
const {comments} = this.props;
- const premodIds = comments.ids.filter(id => comments.byId[id].status === 'premod');
- const rejectedIds = comments.ids.filter(id => comments.byId[id].status === 'rejected');
+ const premodIds = comments.ids.filter(id => comments.byId[id].status === 'PREMOD');
+ const rejectedIds = comments.ids.filter(id => comments.byId[id].status === 'REJECTED');
const flaggedIds = comments.ids.filter(id =>
comments.byId[id].flagged === true &&
- comments.byId[id].status !== 'rejected' &&
- comments.byId[id].status !== 'accepted'
+ comments.byId[id].status !== 'REJECTED' &&
+ comments.byId[id].status !== 'ACCEPTED'
);
return (
@@ -112,7 +112,7 @@ const mapDispatchToProps = dispatch => {
fetchFlaggedQueue: () => dispatch(fetchFlaggedQueue()),
showBanUserDialog: (userId, userName, commentId) => dispatch(showBanUserDialog(userId, userName, commentId)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
- banUser: (userId, commentId) => dispatch(userStatusUpdate('banned', userId, commentId)).then(() => {
+ banUser: (userId, commentId) => dispatch(userStatusUpdate('BANNED', userId, commentId)).then(() => {
dispatch(fetchModerationQueueComments());
}),
updateStatus: (action, comment) => dispatch(updateStatus(action, comment))
diff --git a/client/coral-configure/containers/ConfigureStreamContainer.js b/client/coral-configure/containers/ConfigureStreamContainer.js
index 71da184ab..90835ab3f 100644
--- a/client/coral-configure/containers/ConfigureStreamContainer.js
+++ b/client/coral-configure/containers/ConfigureStreamContainer.js
@@ -2,7 +2,7 @@ import React, {Component} from 'react';
import {connect} from 'react-redux';
import {I18n} from '../../coral-framework';
-import {updateOpenStatus, updateConfiguration} from '../../coral-framework/actions/config';
+import {updateOpenStatus, updateConfiguration} from '../../coral-framework/actions/asset';
import CloseCommentsInfo from '../components/CloseCommentsInfo';
import ConfigureCommentStream from '../components/ConfigureCommentStream';
@@ -13,8 +13,10 @@ class ConfigureStreamContainer extends Component {
constructor (props) {
super(props);
+ console.log('moderation', props.asset.settings.moderation);
+
this.state = {
- premod: props.config.moderation === 'pre',
+ premod: props.asset.settings.moderation === 'PRE',
premodLinks: false
};
@@ -26,7 +28,7 @@ class ConfigureStreamContainer extends Component {
handleApply () {
const {premod, changed} = this.state;
const newConfig = {
- moderation: premod ? 'pre' : 'post'
+ moderation: premod ? 'PRE' : 'POST'
};
if (changed) {
this.props.updateConfiguration(newConfig);
@@ -47,18 +49,17 @@ class ConfigureStreamContainer extends Component {
}
toggleStatus () {
- this.props.updateStatus(this.props.config.status === 'open' ? 'closed' : 'open');
+ this.props.updateStatus(this.props.asset.closedAt === null ? 'closed' : 'open');
}
getClosedIn () {
- const {closedTimeout} = this.props.config;
+ const {closedTimeout} = this.props.asset.settings;
const {created_at} = this.props.asset;
return lang.timeago(new Date(created_at).getTime() + (1000 * closedTimeout));
}
render () {
- const {status} = this.props;
-
+ const status = this.props.asset.closedAt === null ? 'open' : 'closed';
return (
({
- config: state.config.toJS(),
- asset: state.items
- .get('assets')
- .first()
- .toJS()
+ asset: state.asset.toJS()
});
const mapDispatchToProps = dispatch => ({
diff --git a/client/coral-embed-stream/src/Comment.css b/client/coral-embed-stream/src/Comment.css
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/client/coral-embed-stream/src/Comment.css
@@ -0,0 +1 @@
+
diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js
new file mode 100644
index 000000000..f734bce0b
--- /dev/null
+++ b/client/coral-embed-stream/src/Comment.js
@@ -0,0 +1,171 @@
+// this component will
+// render its children
+// render a like button
+// render a permalink button
+// render a reply button
+// render a flag button
+// translate things?
+
+import React, {PropTypes} from 'react';
+import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton';
+
+import AuthorName from 'coral-plugin-author-name/AuthorName';
+import Content from 'coral-plugin-commentcontent/CommentContent';
+import PubDate from 'coral-plugin-pubdate/PubDate';
+import {ReplyBox, ReplyButton} from 'coral-plugin-replies';
+import FlagComment from 'coral-plugin-flags/FlagComment';
+import LikeButton from 'coral-plugin-likes/LikeButton';
+
+const getAction = (type, comment) => comment.actions.filter((a) => a.type === type)[0];
+
+class Comment extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {replyBoxVisible: false};
+ }
+
+ static propTypes = {
+ refetch: PropTypes.func.isRequired,
+ showSignInDialog: PropTypes.func.isRequired,
+ postAction: PropTypes.func.isRequired,
+ deleteAction: PropTypes.func.isRequired,
+ parentId: PropTypes.string,
+ addNotification: PropTypes.func.isRequired,
+ postItem: PropTypes.func.isRequired,
+ depth: PropTypes.number.isRequired,
+ asset: PropTypes.shape({
+ id: PropTypes.string,
+ title: PropTypes.string,
+ url: PropTypes.string
+ }).isRequired,
+ currentUser: PropTypes.shape({
+ id: PropTypes.string.isRequired
+ }),
+ comment: PropTypes.shape({
+ depth: PropTypes.number,
+ actions: PropTypes.array.isRequired,
+ body: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
+ replies: PropTypes.arrayOf(
+ PropTypes.shape({
+ body: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired
+ })
+ ),
+ user: PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired
+ }).isRequired
+ }).isRequired
+ }
+
+ onReplyBoxClick = () => {
+ if (this.props.currentUser) {
+ this.setState({replyBoxVisible: !this.state.replyBoxVisible});
+ } else {
+ const offset = document.getElementById(`c_${this.props.comment.id}`).getBoundingClientRect().top - 75;
+ this.props.showSignInDialog(offset);
+ }
+ }
+
+ render () {
+ const {
+ comment,
+ parentId,
+ currentUser,
+ asset,
+ depth,
+ postItem,
+ refetch,
+ addNotification,
+ showSignInDialog,
+ postAction,
+ deleteAction
+ } = this.props;
+
+ const like = getAction('LIKE', comment);
+ const flag = getAction('FLAG', comment);
+
+ return (
+
+ );
+ }
+}
+
+export default Comment;
diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js
deleted file mode 100644
index bd60f8c32..000000000
--- a/client/coral-embed-stream/src/CommentStream.js
+++ /dev/null
@@ -1,331 +0,0 @@
-import React, {Component, PropTypes} from 'react';
-import Pym from 'pym.js';
-import {connect} from 'react-redux';
-
-import {
- itemActions,
- Notification,
- notificationActions,
- authActions
-} from '../../coral-framework';
-
-import CommentBox from '../../coral-plugin-commentbox/CommentBox';
-import InfoBox from '../../coral-plugin-infobox/InfoBox';
-import Content from '../../coral-plugin-commentcontent/CommentContent';
-import PubDate from '../../coral-plugin-pubdate/PubDate';
-import Count from '../../coral-plugin-comment-count/CommentCount';
-import AuthorName from '../../coral-plugin-author-name/AuthorName';
-import {ReplyBox, ReplyButton} from '../../coral-plugin-replies';
-import FlagComment from '../../coral-plugin-flags/FlagComment';
-import LikeButton from '../../coral-plugin-likes/LikeButton';
-import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton';
-import SignInContainer from '../../coral-sign-in/containers/SignInContainer';
-import UserBox from '../../coral-sign-in/components/UserBox';
-
-import {TabBar, Tab, TabContent, Spinner} from '../../coral-ui';
-import SettingsContainer from '../../coral-settings/containers/SettingsContainer';
-import RestrictedContent from '../../coral-framework/components/RestrictedContent';
-import SuspendedAccount from '../../coral-framework/components/SuspendedAccount';
-
-import ConfigureStreamContainer from '../../coral-configure/containers/ConfigureStreamContainer';
-
-const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions;
-const {addNotification, clearNotification} = notificationActions;
-const {logout, showSignInDialog} = authActions;
-
-class CommentStream extends Component {
-
- constructor (props) {
- super(props);
-
- this.state = {
- activeTab: 0
- };
-
- this.changeTab = this.changeTab.bind(this);
- }
-
- changeTab (tab) {
- this.setState({
- activeTab: tab
- });
- }
-
- static propTypes = {
- items: PropTypes.object.isRequired,
- addItem: PropTypes.func.isRequired,
- updateItem: PropTypes.func.isRequired
- }
-
- componentDidMount () {
-
- // Set up messaging between embedded Iframe an parent component
- this.pym = new Pym.Child({polling: 100});
-
- let path = this.pym.parentUrl.split('#')[0];
-
- if (!path) {
- path = window.location.href.split('#')[0];
- }
-
- this.props.getStream(path || window.location);
- this.path = path;
-
- this.pym.sendMessage('childReady');
-
- this.pym.onMessage('DOMContentLoaded', hash => {
- const commentId = hash.replace('#', 'c_');
- let count = 0;
- const interval = setInterval(() => {
- if (document.getElementById(commentId)) {
- window.clearInterval(interval);
- this.pym.scrollParentToChildEl(commentId);
- }
-
- if (++count > 100) { // ~10 seconds
- // give up waiting for the comments to load.
- // it would be weird for the page to jump after that long.
- window.clearInterval(interval);
- }
- }, 100);
- });
- }
-
- render () {
- const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0];
- const rootItem = this.props.items.assets && this.props.items.assets[rootItemId];
- const {actions, users, comments} = this.props.items;
- const {status, moderation, closedMessage, charCount, charCountEnable} = this.props.config;
- const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth;
- const {activeTab} = this.state;
- const banned = (this.props.userData.status === 'banned');
-
- const expandForLogin = showSignInDialog ? {
- minHeight: document.body.scrollHeight + 150
- } : {};
- return
- {
- rootItem
- ?
-
-
- Settings
- Configure Stream
-
- {loggedIn &&
}
-
- {
- status === 'open'
- ?
- : {closedMessage}
- }
- {!loggedIn && }
- {
- rootItem.comments && rootItem.comments.map((commentId) => {
- const comment = comments[commentId];
- return ;
- })
- }
-
-
-
-
-
-
-
-
-
-
-
-
- :
-
- }
-
;
- }
-}
-
-const mapStateToProps = state => ({
- config: state.config.toJS(),
- items: state.items.toJS(),
- notification: state.notification.toJS(),
- auth: state.auth.toJS(),
- userData: state.user.toJS()
-});
-
-const mapDispatchToProps = (dispatch) => ({
- addItem: (item, item_id) => dispatch(addItem(item, item_id)),
- updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)),
- postItem: (data, type, id) => dispatch(postItem(data, type, id)),
- getStream: (rootId) => dispatch(getStream(rootId)),
- addNotification: (type, text) => dispatch(addNotification(type, text)),
- clearNotification: () => dispatch(clearNotification()),
- postAction: (item, itemType, action) => dispatch(postAction(item, itemType, action)),
- showSignInDialog: (offset) => dispatch(showSignInDialog(offset)),
- deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)),
- appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)),
- handleSignInDialog: () => dispatch(authActions.showSignInDialog()),
- logout: () => dispatch(logout())
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(CommentStream);
diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js
new file mode 100644
index 000000000..5922266fa
--- /dev/null
+++ b/client/coral-embed-stream/src/Embed.js
@@ -0,0 +1,205 @@
+import React, {Component} from 'react';
+import {compose} from 'react-apollo';
+import {connect} from 'react-redux';
+import {isEqual} from 'lodash';
+
+import {TabBar, Tab, TabContent, Spinner} from '../../coral-ui';
+
+const {logout, showSignInDialog} = authActions;
+const {addNotification, clearNotification} = notificationActions;
+const {fetchAssetSuccess} = assetActions;
+
+import {queryStream} from './graphql/queries';
+import {postComment, postAction, deleteAction} from './graphql/mutations';
+import {Notification, notificationActions, authActions, assetActions, pym} from 'coral-framework';
+
+import Stream from './Stream';
+import InfoBox from 'coral-plugin-infobox/InfoBox';
+import Count from 'coral-plugin-comment-count/CommentCount';
+import CommentBox from 'coral-plugin-commentbox/CommentBox';
+import UserBox from '../../coral-sign-in/components/UserBox';
+import SignInContainer from '../../coral-sign-in/containers/SignInContainer';
+import SuspendedAccount from '../../coral-framework/components/SuspendedAccount';
+import SettingsContainer from '../../coral-settings/containers/SettingsContainer';
+import RestrictedContent from '../../coral-framework/components/RestrictedContent';
+import ConfigureStreamContainer from '../../coral-configure/containers/ConfigureStreamContainer';
+
+class Embed extends Component {
+
+ constructor (props) {
+ super(props);
+
+ this.state = {
+ activeTab: 0,
+ showSignInDialog: false
+ };
+
+ this.changeTab = this.changeTab.bind(this);
+ }
+
+ changeTab (tab) {
+ this.setState({
+ activeTab: tab
+ });
+ }
+
+ static propTypes = {
+ data: React.PropTypes.shape({
+ loading: React.PropTypes.bool,
+ error: React.PropTypes.object
+ }).isRequired
+ }
+
+ componentDidMount () {
+
+ // stream id, logged in user, settings
+
+ // Set up messaging between embedded Iframe an parent component
+
+ // this.props.getStream(path || window.location);
+ // this.path = window.location.href.split('#')[0];
+ //
+
+ pym.sendMessage('childReady');
+
+ pym.onMessage('DOMContentLoaded', hash => {
+ const commentId = hash.replace('#', 'c_');
+ let count = 0;
+ const interval = setInterval(() => {
+ if (document.getElementById(commentId)) {
+ window.clearInterval(interval);
+ pym.scrollParentToChildEl(commentId);
+ }
+
+ if (++count > 100) { // ~10 seconds
+ // give up waiting for the comments to load.
+ // it would be weird for the page to jump after that long.
+ window.clearInterval(interval);
+ }
+ }, 100);
+ });
+
+ }
+
+ componentWillReceiveProps (nextProps) {
+ const {loadAsset} = this.props;
+ if(!isEqual(nextProps.data.asset, this.props.data.asset)) {
+ loadAsset(nextProps.data.asset);
+ }
+ }
+
+ render () {
+ const {activeTab} = this.state;
+ const {loading, asset, refetch} = this.props.data;
+ const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth;
+
+ const expandForLogin = showSignInDialog ? {
+ minHeight: document.body.scrollHeight + 200
+ } : {};
+
+ return
+ {
+ loading ?
+ :
+
+
+ Settings
+ Configure Stream
+
+ {loggedIn &&
}
+
+ {
+ asset.closedAt === null
+ ?
+ : {asset.settings.closedMessage}
+ }
+ {!loggedIn && }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+
;
+ }
+}
+
+const mapStateToProps = state => ({
+ items: state.items.toJS(),
+ notification: state.notification.toJS(),
+ auth: state.auth.toJS(),
+ userData: state.user.toJS()
+});
+
+const mapDispatchToProps = dispatch => ({
+ loadAsset: (asset) => dispatch(fetchAssetSuccess(asset)),
+ addNotification: (type, text) => dispatch(addNotification(type, text)),
+ clearNotification: () => dispatch(clearNotification()),
+ showSignInDialog: (offset) => dispatch(showSignInDialog(offset)),
+ logout: () => dispatch(logout()),
+ dispatch: d => dispatch(d)
+});
+
+export default compose(
+ connect(mapStateToProps, mapDispatchToProps),
+ postComment,
+ postAction,
+ deleteAction,
+ queryStream
+)(Embed);
diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js
new file mode 100644
index 000000000..73842a947
--- /dev/null
+++ b/client/coral-embed-stream/src/Stream.js
@@ -0,0 +1,49 @@
+import React, {PropTypes} from 'react';
+import Comment from './Comment';
+
+const Stream = ({
+ comments,
+ currentUser,
+ asset,
+ postItem,
+ addNotification,
+ postAction,
+ deleteAction,
+ showSignInDialog,
+ refetch
+}) => {
+ return (
+
+ {
+ comments.map(comment => {
+ return ;
+ })
+ }
+
+ );
+};
+
+Stream.propTypes = {
+ refetch: PropTypes.func.isRequired,
+ addNotification: PropTypes.func.isRequired,
+ postItem: PropTypes.func.isRequired,
+ asset: PropTypes.object.isRequired,
+ comments: PropTypes.array.isRequired,
+ currentUser: PropTypes.shape({
+ displayName: PropTypes.string,
+ id: PropTypes.string
+ })
+};
+
+export default Stream;
diff --git a/client/coral-embed-stream/src/graphql/mutations/deleteAction.graphql b/client/coral-embed-stream/src/graphql/mutations/deleteAction.graphql
new file mode 100644
index 000000000..bfce8cf6a
--- /dev/null
+++ b/client/coral-embed-stream/src/graphql/mutations/deleteAction.graphql
@@ -0,0 +1,3 @@
+mutation deleteAction ($id: ID!) {
+ deleteAction(id:$id)
+}
diff --git a/client/coral-embed-stream/src/graphql/mutations/index.js b/client/coral-embed-stream/src/graphql/mutations/index.js
new file mode 100644
index 000000000..383862455
--- /dev/null
+++ b/client/coral-embed-stream/src/graphql/mutations/index.js
@@ -0,0 +1,39 @@
+import {graphql} from 'react-apollo';
+import POST_COMMENT from './postComment.graphql';
+import POST_ACTION from './postAction.graphql';
+import DELETE_ACTION from './deleteAction.graphql';
+
+export const postComment = graphql(POST_COMMENT, {
+ props: ({mutate}) => ({
+ postItem: ({asset_id, body, parent_id} /* , type */ ) => {
+ return mutate({
+ variables: {
+ asset_id,
+ body,
+ parent_id
+ }
+ });
+ }}),
+});
+
+export const postAction = graphql(POST_ACTION, {
+ props: ({mutate}) => ({
+ postAction: (action) => {
+ return mutate({
+ variables: {
+ action
+ }
+ });
+ }}),
+});
+
+export const deleteAction = graphql(DELETE_ACTION, {
+ props: ({mutate}) => ({
+ deleteAction: (id) => {
+ return mutate({
+ variables: {
+ id
+ }
+ });
+ }}),
+});
diff --git a/client/coral-embed-stream/src/graphql/mutations/postAction.graphql b/client/coral-embed-stream/src/graphql/mutations/postAction.graphql
new file mode 100644
index 000000000..fff737fa8
--- /dev/null
+++ b/client/coral-embed-stream/src/graphql/mutations/postAction.graphql
@@ -0,0 +1,5 @@
+mutation CreateAction ($action: CreateActionInput!) {
+ createAction(action:$action) {
+ id
+ }
+}
diff --git a/client/coral-embed-stream/src/graphql/mutations/postComment.graphql b/client/coral-embed-stream/src/graphql/mutations/postComment.graphql
new file mode 100644
index 000000000..a9496f1fa
--- /dev/null
+++ b/client/coral-embed-stream/src/graphql/mutations/postComment.graphql
@@ -0,0 +1,23 @@
+
+ fragment commentView on Comment {
+ id
+ body
+ status
+ user {
+ name: displayName
+ }
+ actions {
+ type: action_type
+ count
+ current: current_user {
+ id
+ created_at
+ }
+ }
+ }
+
+mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) {
+ createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) {
+ ...commentView
+ }
+}
diff --git a/client/coral-embed-stream/src/graphql/queries/index.js b/client/coral-embed-stream/src/graphql/queries/index.js
new file mode 100644
index 000000000..ae01ea9f4
--- /dev/null
+++ b/client/coral-embed-stream/src/graphql/queries/index.js
@@ -0,0 +1,9 @@
+import {graphql} from 'react-apollo';
+import STREAM_QUERY from './streamQuery.graphql';
+import pym from 'coral-framework/PymConnection';
+
+let url = pym.parentUrl.split('#')[0] || 'http://localhost:3000/';
+
+export const queryStream = graphql(STREAM_QUERY, {
+ options: {variables: {asset_url: url}}
+});
diff --git a/client/coral-embed-stream/src/graphql/queries/streamQuery.graphql b/client/coral-embed-stream/src/graphql/queries/streamQuery.graphql
new file mode 100644
index 000000000..64bb46b47
--- /dev/null
+++ b/client/coral-embed-stream/src/graphql/queries/streamQuery.graphql
@@ -0,0 +1,47 @@
+
+fragment commentView on Comment {
+ id
+ body
+ created_at
+ user {
+ id
+ name: displayName
+ settings {
+ bio
+ }
+ }
+ actions {
+ type: action_type
+ count
+ current: current_user {
+ id
+ created_at
+ }
+ }
+}
+
+query AssetQuery($asset_url: String!) {
+ asset(url: $asset_url) {
+ id
+ title
+ url
+ closedAt
+ created_at
+ settings {
+ moderation
+ infoBoxEnable
+ infoBoxContent
+ closeTimeout
+ closedMessage
+ charCountEnable
+ charCount
+ requireEmailConfirmation
+ }
+ comments {
+ ...commentView
+ replies {
+ ...commentView
+ }
+ }
+ }
+}
diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js
index de14edbc7..f75146f0a 100644
--- a/client/coral-embed-stream/src/index.js
+++ b/client/coral-embed-stream/src/index.js
@@ -1,11 +1,15 @@
import React from 'react';
import {render} from 'react-dom';
-import CommentStream from './CommentStream';
-import {Provider} from 'react-redux';
-import {store} from '../../coral-framework';
+import {ApolloProvider} from 'react-apollo';
+
+import {client} from 'coral-framework/client';
+import store from 'coral-framework/store';
+
+import Embed from './Embed';
render(
-
-
-
- , document.querySelector('#coralStream'));
+
+
+
+ , document.querySelector('#coralStream')
+);
diff --git a/client/coral-framework/PymConnection.js b/client/coral-framework/PymConnection.js
new file mode 100644
index 000000000..ca592b824
--- /dev/null
+++ b/client/coral-framework/PymConnection.js
@@ -0,0 +1,9 @@
+import Pym from '../../node_modules/pym.js';
+
+const pym = new Pym.Child({polling: 100});
+export default pym;
+
+export const link = (url) => (e) => {
+ e.preventDefault();
+ pym.sendMessage('navigate', url);
+};
diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/asset.js
similarity index 80%
rename from client/coral-framework/actions/config.js
rename to client/coral-framework/actions/asset.js
index 77e7f5af5..b8474857c 100644
--- a/client/coral-framework/actions/config.js
+++ b/client/coral-framework/actions/asset.js
@@ -1,20 +1,21 @@
+import * as actions from '../constants/asset';
import coralApi from '../helpers/response';
-import * as actions from '../constants/config';
import {addNotification} from '../actions/notification';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from './../translations';
const lang = new I18n(translations);
+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 updateConfigRequest = () => ({type: actions.UPDATE_CONFIG_REQUEST});
const updateConfigSuccess = config => ({type: actions.UPDATE_CONFIG_SUCCESS, config});
const updateConfigFailure = () => ({type: actions.UPDATE_CONFIG_FAILURE});
export const updateConfiguration = newConfig => (dispatch, getState) => {
- const assetId = getState().items.get('assets')
- .keySeq()
- .toArray()[0];
-
+ const assetId = getState().asset.toJS().id;
dispatch(updateConfigRequest());
coralApi(`/assets/${assetId}/settings`, {method: 'PUT', body: newConfig})
.then(() => {
@@ -25,12 +26,8 @@ export const updateConfiguration = newConfig => (dispatch, getState) => {
};
export const updateOpenStream = closedBody => (dispatch, getState) => {
- const assetId = getState().items.get('assets')
- .keySeq()
- .toArray()[0];
-
+ const assetId = getState().asset.toJS().id;
dispatch(updateConfigRequest());
-
coralApi(`/assets/${assetId}/status`, {method: 'PUT', body: closedBody})
.then(() => {
dispatch(addNotification('success', lang.t('successUpdateSettings')));
diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js
index 0f65e14c2..e815fd5d0 100644
--- a/client/coral-framework/actions/auth.js
+++ b/client/coral-framework/actions/auth.js
@@ -27,7 +27,7 @@ export const fetchSignIn = (formData) => (dispatch) => {
dispatch(signInRequest());
coralApi('/auth/local', {method: 'POST', body: formData})
.then(({user}) => {
- const isAdmin = !!user.roles.filter(i => i === 'admin').length;
+ const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length;
dispatch(signInSuccess(user, isAdmin));
dispatch(hideSignInDialog());
dispatch(addItem(user, 'users'));
@@ -132,7 +132,7 @@ export const checkLogin = () => dispatch => {
throw new Error('Not logged in');
}
- const isAdmin = !!result.user.roles.filter(i => i === 'admin').length;
+ const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length;
dispatch(checkLoginSuccess(result.user, isAdmin));
})
.catch(error => dispatch(checkLoginFailure(error)));
diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js
index 48418b2a2..e289e9f60 100644
--- a/client/coral-framework/actions/items.js
+++ b/client/coral-framework/actions/items.js
@@ -189,17 +189,34 @@ export function getItemsArray (ids) {
* The newly put item to the item store
*/
-export function postItem (item, type, id) {
- return (dispatch) => {
- if (id) {
- item.id = id;
+export function postItem (item, type, id, mutate) {
+ console.log(
+ item,
+ type,
+ id,
+ mutate
+ );
+ mutate({
+ variables: {
+ asset_id: id,
+ body: item,
+ parent_id: null
}
- return coralApi(`/${type}`, {method: 'POST', body: item})
- .then((json) => {
- dispatch(addItem({...item, id:json.id}, type));
- return json;
- });
- };
+ }).then(({data}) => {
+ console.log('it workt');
+ console.log(data);
+ });
+
+ // return (dispatch) => {
+ // if (id) {
+ // item.id = id;
+ // }
+ // return coralApi(`/${type}`, {method: 'POST', body: item})
+ // .then((json) => {
+ // dispatch(addItem({...item, id:json.id}, type));
+ // return json;
+ // });
+ // };
}
/*
diff --git a/client/coral-framework/client.js b/client/coral-framework/client.js
new file mode 100644
index 000000000..40a539634
--- /dev/null
+++ b/client/coral-framework/client.js
@@ -0,0 +1,13 @@
+import ApolloClient, {addTypename} from 'apollo-client';
+import getNetworkInterface from './transport';
+
+export const client = new ApolloClient({
+ queryTransformer: addTypename,
+ dataIdFromObject: (result) => {
+ if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle
+ return result.__typename + result.id; // eslint-disable-line no-underscore-dangle
+ }
+ return null;
+ },
+ networkInterface: getNetworkInterface()
+});
diff --git a/client/coral-framework/constants/asset.js b/client/coral-framework/constants/asset.js
new file mode 100644
index 000000000..bfeb98441
--- /dev/null
+++ b/client/coral-framework/constants/asset.js
@@ -0,0 +1,13 @@
+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_CONFIG_REQUEST = 'UPDATE_CONFIG_REQUEST';
+export const UPDATE_CONFIG_SUCCESS = 'UPDATE_CONFIG_SUCCESS';
+export const UPDATE_CONFIG_FAILURE = 'UPDATE_CONFIG_FAILURE';
+
+export const UPDATE_CONFIG = 'UPDATE_CONFIG';
+
+export const OPEN_COMMENTS = 'OPEN_COMMENTS';
+export const CLOSE_COMMENTS = 'CLOSE_COMMENTS';
+export const ADD_ITEM = 'ADD_ITEM';
diff --git a/client/coral-framework/helpers/validate.js b/client/coral-framework/helpers/validate.js
index 6e5db3662..b267fb7c0 100644
--- a/client/coral-framework/helpers/validate.js
+++ b/client/coral-framework/helpers/validate.js
@@ -1,5 +1,5 @@
export default {
- email: email => (/^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(email)),
+ email: email => (/^([A-Za-z0-9_\-\.\+])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(email)),
password: pass => (/^(?=.{8,}).*$/.test(pass)),
confirmPassword: () => true,
displayName: displayName => (/^[a-zA-Z0-9_]+$/.test(displayName))
diff --git a/client/coral-framework/index.js b/client/coral-framework/index.js
index 9a679d969..c21136d27 100644
--- a/client/coral-framework/index.js
+++ b/client/coral-framework/index.js
@@ -4,7 +4,8 @@ import * as itemActions from './actions/items';
import I18n from './modules/i18n/i18n';
import * as notificationActions from './actions/notification';
import * as authActions from './actions/auth';
-import * as configActions from './actions/config';
+import * as assetActions from './actions/asset';
+import pym from './PymConnection';
export {
Notification,
@@ -13,5 +14,6 @@ export {
I18n,
notificationActions,
authActions,
- configActions
+ assetActions,
+ pym
};
diff --git a/client/coral-framework/reducers/asset.js b/client/coral-framework/reducers/asset.js
new file mode 100644
index 000000000..495307aeb
--- /dev/null
+++ b/client/coral-framework/reducers/asset.js
@@ -0,0 +1,36 @@
+import {Map} from 'immutable';
+import * as actions from '../constants/asset';
+
+const initialState = Map({
+ closedAt: null,
+ settings: null,
+ title: null,
+ url: null,
+ features: Map({}),
+ status: 'open',
+ moderation: null
+});
+
+export default function asset (state = initialState, action) {
+ switch (action.type) {
+ case actions.FETCH_ASSET_SUCCESS :
+ return state
+ .merge(action.asset);
+ case actions.UPDATE_CONFIG:
+ return state
+ .merge(action.config);
+ case actions.UPDATE_CONFIG_SUCCESS:
+ return state
+ .merge(action.config);
+ case actions.OPEN_COMMENTS:
+ return state
+ .set('status', 'open')
+ .set('closedAt', null);
+ case actions.CLOSE_COMMENTS:
+ return state
+ .set('status', 'closed')
+ .set('closedAt', Date.now());
+ default:
+ return state;
+ }
+}
diff --git a/client/coral-framework/reducers/config.js b/client/coral-framework/reducers/config.js
deleted file mode 100644
index 362608d6d..000000000
--- a/client/coral-framework/reducers/config.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import {Map} from 'immutable';
-import * as actions from '../constants/config';
-
-const initialState = Map({
- features: Map({}),
- status: 'open',
- moderation: null
-});
-
-export default (state = initialState, action) => {
- switch(action.type) {
- case actions.UPDATE_CONFIG:
- return state
- .merge(Map(action.config));
- case actions.UPDATE_CONFIG_SUCCESS:
- return state
- .merge(Map(action.config));
- case actions.OPEN_COMMENTS:
- return state
- .set('status', 'open');
- case actions.CLOSE_COMMENTS:
- return state
- .set('status', 'closed');
- case actions.ADD_ITEM:
- return action.item_type === 'assets' ? state.set('status', (action.item && action.item.closedAt && new Date(action.item.closedAt).getTime() <= new Date().getTime()) ? 'closed' : 'open') : state;
- default:
- return state;
- }
-};
diff --git a/client/coral-framework/reducers/index.js b/client/coral-framework/reducers/index.js
index a2f439028..8c1457bfe 100644
--- a/client/coral-framework/reducers/index.js
+++ b/client/coral-framework/reducers/index.js
@@ -1,20 +1,13 @@
-/* @flow */
-
-import {combineReducers} from 'redux';
-import config from './config';
-import items from './items';
-import notification from './notification';
import auth from './auth';
import user from './user';
+import asset from './asset';
+import items from './items';
+import notification from './notification';
-/**
- * Expose the combined main reducer
- */
-
-export default combineReducers({
- config,
- items,
- notification,
+export default {
auth,
- user
-});
+ user,
+ asset,
+ items,
+ notification
+};
diff --git a/client/coral-framework/store.js b/client/coral-framework/store.js
index 8c3f9edc1..439569ee1 100644
--- a/client/coral-framework/store.js
+++ b/client/coral-framework/store.js
@@ -1,9 +1,24 @@
-import {createStore, applyMiddleware} from 'redux';
+import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import mainReducer from './reducers';
+import {client} from './client';
+
+const middlewares = [
+ applyMiddleware(client.middleware()),
+ applyMiddleware(thunk)
+];
+
+if (window.devToolsExtension) {
+
+ // we can't have the last argument of compose() be undefined
+ middlewares.push(window.devToolsExtension());
+}
export default createStore(
- mainReducer,
- window.devToolsExtension && window.devToolsExtension(),
- applyMiddleware(thunk)
+ combineReducers({
+ ...mainReducer,
+ apollo: client.reducer()
+ }),
+ {},
+ compose(...middlewares)
);
diff --git a/client/coral-framework/subscriptions.js b/client/coral-framework/subscriptions.js
new file mode 100644
index 000000000..f514ea18b
--- /dev/null
+++ b/client/coral-framework/subscriptions.js
@@ -0,0 +1,14 @@
+import {print} from 'graphql-tag/printer';
+
+// quick way to add the subscribe and unsubscribe functions to the network interface
+const addGraphQLSubscriptions = (networkInterface, wsClient) => Object.assign(networkInterface, {
+ subscribe: (request, handler) => wsClient.subscribe({
+ query: print(request.query),
+ variables: request.variables,
+ }, handler),
+ unsubscribe: (id) => {
+ wsClient.unsubscribe(id);
+ },
+});
+
+export default addGraphQLSubscriptions;
diff --git a/client/coral-framework/transport.js b/client/coral-framework/transport.js
new file mode 100644
index 000000000..2bd6ac636
--- /dev/null
+++ b/client/coral-framework/transport.js
@@ -0,0 +1,11 @@
+import {createNetworkInterface} from 'apollo-client';
+
+export default function getNetworkInterface(apiUrl = '/api/v1/graph/ql', headers = {}) {
+ return new createNetworkInterface({
+ uri: apiUrl,
+ opts: {
+ credentials: 'same-origin',
+ headers,
+ },
+ });
+}
diff --git a/client/coral-plugin-author-name/AuthorName.js b/client/coral-plugin-author-name/AuthorName.js
index bf970b3c4..fcf464403 100644
--- a/client/coral-plugin-author-name/AuthorName.js
+++ b/client/coral-plugin-author-name/AuthorName.js
@@ -36,8 +36,8 @@ export default class AuthorName extends Component {
onMouseOver={this.handleMouseOver}
onMouseLeave={this.handleMouseLeave}
>
- {author && author.displayName}
- { showTooltip &&
+ {author && author.name}
+ { showTooltip && author.settings.bio &&
{author.settings.bio}
diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js
index 87f656c22..764984634 100644
--- a/client/coral-plugin-comment-count/CommentCount.js
+++ b/client/coral-plugin-comment-count/CommentCount.js
@@ -1,29 +1,18 @@
-import React from 'react';
+import React, {PropTypes} from 'react';
import {I18n} from '../coral-framework';
import translations from './translations.json';
-import has from 'lodash/has';
-import reduce from 'lodash/reduce';
const name = 'coral-plugin-comment-count';
-const CommentCount = ({items, id}) => {
- let count = 0;
- if (has(items, `assets.${id}.comments`)) {
- count += items.assets[id].comments.length;
- }
-
- // lodash reduce works on {}
- count += reduce(items.comments, (accum, comment) => {
- if (comment.children) {
- accum += comment.children.length;
- }
- return accum;
- }, 0);
-
+const CommentCount = ({count}) => {
return
{`${count} ${count === 1 ? lang.t('comment') : lang.t('comment-plural')}`}
;
};
+CommentCount.propTypes = {
+ count: PropTypes.number.isRequired
+};
+
export default CommentCount;
const lang = new I18n(translations);
diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js
index fe67b22e0..f368953a7 100644
--- a/client/coral-plugin-commentbox/CommentBox.js
+++ b/client/coral-plugin-commentbox/CommentBox.js
@@ -8,11 +8,15 @@ const name = 'coral-plugin-commentbox';
class CommentBox extends Component {
static propTypes = {
- postItem: PropTypes.func,
- updateItem: PropTypes.func,
- id: PropTypes.string,
- comments: PropTypes.array,
- reply: PropTypes.bool,
+
+ // updateItem: PropTypes.func,
+ // comments: PropTypes.array,
+ commentPostedHandler: PropTypes.func,
+ postItem: PropTypes.func.isRequired,
+ assetId: PropTypes.string.isRequired,
+ parentId: PropTypes.string,
+ authorId: PropTypes.string.isRequired,
+ isReply: PropTypes.bool.isRequired,
canPost: PropTypes.bool,
currentUser: PropTypes.object
}
@@ -24,73 +28,83 @@ class CommentBox extends Component {
postComment = () => {
const {
+
+ // child_id,
+ // updateItem,
+ // appendItemArray,
+ commentPostedHandler,
postItem,
- updateItem,
- id,
- parent_id,
- child_id,
+ assetId,
+ parentId,
addNotification,
- appendItemArray,
- premod,
- author
+ authorId
} = this.props;
let comment = {
body: this.state.body,
- asset_id: id,
- author_id: author.id
+ asset_id: assetId,
+ author_id: authorId,
+ parent_id: parentId
};
- let related;
- let parent_type;
- if (parent_id) {
- comment.parent_id = parent_id;
- related = 'children';
- parent_type = 'comments';
- } else {
- related = 'comments';
- parent_type = 'assets';
- }
- if (child_id || parent_id) {
- updateItem(child_id || parent_id, 'showReply', false, 'comments');
- }
+
+ // let related;
+ // let parent_type;
+ // if (parent_id) {
+ // comment.parent_id = parent_id;
+ // related = 'children';
+ // parent_type = 'comments';
+ // } else {
+ // related = 'comments';
+ // parent_type = 'assets';
+ // }
+ // if (child_id || parent_id) {
+ // updateItem(child_id || parent_id, 'showReply', false, 'comments');
+ // }
if (this.props.charCount && this.state.body.length > this.props.charCount) {
return;
}
postItem(comment, 'comments')
- .then((postedComment) => {
- const commentId = postedComment.id;
- if (postedComment.status === 'rejected') {
+ .then(({data}) => {
+ const postedComment = data.createComment;
+
+ // const commentId = postedComment.id;
+ if (postedComment.status === 'REJECTED') {
addNotification('error', lang.t('comment-post-banned-word'));
- } else if (premod === 'pre') {
+ } else if (postedComment.status === 'PREMOD') {
addNotification('success', lang.t('comment-post-notif-premod'));
} else {
- appendItemArray(parent_id || id, related, commentId, !parent_id, parent_type);
+
+ // appendItemArray(parent_id || id, related, commentId, !parent_id, parent_type);
addNotification('success', 'Your comment has been posted.');
}
+
+ if (commentPostedHandler) {
+ commentPostedHandler();
+ }
})
.catch((err) => console.error(err));
this.setState({body: ''});
}
render () {
- const {styles, reply, author, charCount} = this.props;
+ const {styles, isReply, authorId, charCount} = this.props;
const length = this.state.body.length;
return
@@ -101,7 +115,7 @@ class CommentBox extends Component {
}
- { author && (
+ { authorId && (