diff --git a/bin/cli-setup b/bin/cli-setup
index 9ecdecc95..2e5c1e5b8 100755
--- a/bin/cli-setup
+++ b/bin/cli-setup
@@ -94,7 +94,7 @@ const performSetup = async () => {
name: 'requireEmailConfirmation',
default: settings.requireEmailConfirmation,
message: 'Should emails always be confirmed'
- }
+ },
]);
// Update the settings that were changed.
@@ -104,6 +104,32 @@ const performSetup = async () => {
}
});
+ answers = await inquirer.prompt([
+ {
+ type: 'confirm',
+ name: 'inputWhitelistedDomains',
+ default: true,
+ message: 'Would you like to specify a whitelisted domain'
+ },
+ {
+ type: 'input',
+ name: 'whitelistedDomain',
+ message: 'Whitelisted Domain',
+ when: ({inputWhitelistedDomains}) => inputWhitelistedDomains,
+ validate: (input) => {
+ if (input && input.length > 0) {
+ return true;
+ }
+
+ return 'Whitelisted Domain cannot be empty.';
+ }
+ }
+ ]);
+
+ if (answers.inputWhitelistedDomains) {
+ settings.domains.whitelist = [answers.whitelistedDomain];
+ }
+
console.log('\nWe\'ll ask you some questions about your first admin user.\n');
let user = await inquirer.prompt([
@@ -147,7 +173,11 @@ const performSetup = async () => {
name: 'confirmPassword',
message: 'Confirm Password',
type: 'password',
- filter: (confirmPassword) => {
+ filter: (confirmPassword, {password}) => {
+ if (password !== confirmPassword) {
+ return Promise.reject(new Error('Passwords do not match'));
+ }
+
return UsersService
.isValidPassword(confirmPassword)
.catch((err) => {
@@ -157,10 +187,6 @@ const performSetup = async () => {
},
]);
- if (user.password !== user.confirmPassword) {
- return Promise.reject(new Error('Passwords do not match'));
- }
-
let {user: newUser} = await SetupService.setup({
settings: settings.toObject(),
user: {
diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js
index 4b3ea0f0b..9772ebf17 100644
--- a/client/coral-admin/src/AppRouter.js
+++ b/client/coral-admin/src/AppRouter.js
@@ -37,27 +37,11 @@ const routes = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js
index 75bd2477d..b9ebe0ada 100644
--- a/client/coral-admin/src/actions/assets.js
+++ b/client/coral-admin/src/actions/assets.js
@@ -35,7 +35,7 @@ export const fetchAssets = (skip = '', limit = '', search = '', sort = '', filte
// Update an asset state
// Get comments to fill each of the three lists on the mod queue
export const updateAssetState = (id, closedAt) => (dispatch) => {
- dispatch({type: UPDATE_ASSET_STATE_REQUEST});
+ dispatch({type: UPDATE_ASSET_STATE_REQUEST, id, closedAt});
return coralApi(`/assets/${id}/status`, {method: 'PUT', body: {closedAt}})
.then(() => dispatch({type: UPDATE_ASSET_STATE_SUCCESS}))
.catch((error) => {
diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js
index 33cefab63..6393c58dd 100644
--- a/client/coral-admin/src/actions/install.js
+++ b/client/coral-admin/src/actions/install.js
@@ -93,21 +93,21 @@ const validation = (formData, dispatch, next) => {
};
export const submitSettings = () => (dispatch, getState) => {
- const settingsFormData = getState().install.toJS().data.settings;
+ const settingsFormData = getState().install.data.settings;
validation(settingsFormData, dispatch, function() {
dispatch(nextStep());
});
};
export const submitUser = () => (dispatch, getState) => {
- const userFormData = getState().install.toJS().data.user;
+ const userFormData = getState().install.data.user;
validation(userFormData, dispatch, function() {
dispatch(nextStep());
});
};
export const finishInstall = () => (dispatch, getState) => {
- const data = getState().install.toJS().data;
+ const data = getState().install.data;
dispatch(installRequest());
return coralApi('/setup', {method: 'POST', body: data})
.then(() => {
diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js
index ba8b917ea..ca9062b9e 100644
--- a/client/coral-admin/src/actions/settings.js
+++ b/client/coral-admin/src/actions/settings.js
@@ -42,7 +42,7 @@ export const updateDomainlist = (listName, list) => {
};
export const saveSettingsToServer = () => (dispatch, getState) => {
- let settings = getState().settings.toJS();
+ let settings = getState().settings;
if (settings.charCount) {
settings.charCount = parseInt(settings.charCount);
}
diff --git a/client/coral-admin/src/components/CommentType.css b/client/coral-admin/src/components/CommentType.css
index b5dbc229b..e4851275e 100644
--- a/client/coral-admin/src/components/CommentType.css
+++ b/client/coral-admin/src/components/CommentType.css
@@ -3,11 +3,13 @@
color: white;
background: grey;
box-sizing: border-box;
- padding: 2px 8px;
+ padding: 0px 5px;
border-radius: 2px;
font-size: 12px;
- height: 28px;
-
+ height: 24px;
+ letter-spacing: 0.4px;
+ margin-bottom: 1px;
+
> i {
font-size: 14px;
vertical-align: text-top;
diff --git a/client/coral-admin/src/components/FlagBox.css b/client/coral-admin/src/components/FlagBox.css
index 402a88045..232803203 100644
--- a/client/coral-admin/src/components/FlagBox.css
+++ b/client/coral-admin/src/components/FlagBox.css
@@ -1,6 +1,6 @@
.flagBox {
border-top: 1px solid rgba(66, 66, 66, 0.12);
-
+ margin-top: 10px;
.container {
padding: 0 14px;
}
diff --git a/client/coral-admin/src/components/ModerationList.css b/client/coral-admin/src/components/ModerationList.css
index 89e43e02a..4ac894290 100644
--- a/client/coral-admin/src/components/ModerationList.css
+++ b/client/coral-admin/src/components/ModerationList.css
@@ -186,7 +186,6 @@
.actionButton {
transform: scale(.8);
margin: 0;
- width: 140px;
}
.minimal {
diff --git a/client/coral-admin/src/components/ui/Logo.css b/client/coral-admin/src/components/ui/Logo.css
index af2758bcb..62a223683 100644
--- a/client/coral-admin/src/components/ui/Logo.css
+++ b/client/coral-admin/src/components/ui/Logo.css
@@ -10,16 +10,18 @@
.logo span {
display: inline-block;
margin-left: 10px;
- font-size: 18px;
+ font-size: 26px;
vertical-align: middle;
font-weight: 500;
+ color: white;
}
.logo {
- background: #E5E5E5;
+ background: #696969;
height: 100%;
width: 128px;
z-index: 10;
+ border-right: 1px #757575 solid;
}
.base {
diff --git a/client/coral-admin/src/containers/Layout.js b/client/coral-admin/src/containers/Layout.js
index 8b119fb72..ae022f2ef 100644
--- a/client/coral-admin/src/containers/Layout.js
+++ b/client/coral-admin/src/containers/Layout.js
@@ -74,10 +74,8 @@ class LayoutContainer extends Component {
}
const mapStateToProps = (state) => ({
- auth: state.auth.toJS(),
- TALK_RECAPTCHA_PUBLIC: state.config
- .get('data')
- .get('TALK_RECAPTCHA_PUBLIC', null)
+ auth: state.auth,
+ TALK_RECAPTCHA_PUBLIC: state.config.data.TALK_RECAPTCHA_PUBLIC,
});
const mapDispatchToProps = (dispatch) => ({
diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js
index e59629a85..2ae6e4c9c 100644
--- a/client/coral-admin/src/containers/UserDetail.js
+++ b/client/coral-admin/src/containers/UserDetail.js
@@ -169,8 +169,8 @@ const mapStateToProps = (state) => ({
selectedCommentIds: state.userDetail.selectedCommentIds,
statuses: state.userDetail.statuses,
activeTab: state.userDetail.activeTab,
- bannedWords: state.settings.toJS().wordlist.banned,
- suspectWords: state.settings.toJS().wordlist.suspect,
+ bannedWords: state.settings.wordlist.banned,
+ suspectWords: state.settings.wordlist.suspect,
});
const mapDispatchToProps = (dispatch) => ({
diff --git a/client/coral-admin/src/reducers/assets.js b/client/coral-admin/src/reducers/assets.js
index 03f0be9bb..bb8751973 100644
--- a/client/coral-admin/src/reducers/assets.js
+++ b/client/coral-admin/src/reducers/assets.js
@@ -1,35 +1,40 @@
-import {Map, List, fromJS} from 'immutable';
import * as actions from '../constants/assets';
+import update from 'immutability-helper';
-const initialState = Map({
- byId: Map(),
- ids: List(),
- assets: List()
-});
+const initialState = {
+ byId: {},
+ ids: [],
+ assets: []
+};
export default function assets (state = initialState, action) {
switch (action.type) {
- case actions.FETCH_ASSETS_SUCCESS:
- return replaceAssets(action, state);
+ case actions.FETCH_ASSETS_SUCCESS: {
+ const assets = action.assets.reduce((prev, curr) => {
+ prev[curr.id] = curr;
+ return prev;
+ }, {});
+
+ return update(state, {
+ byId: {$set: assets},
+ count: {$set: action.count},
+ ids: {$set: Object.keys(assets)},
+ });
+ }
case actions.UPDATE_ASSET_STATE_REQUEST:
- return state
- .setIn(['byId', action.id, 'closedAt'], action.closedAt);
+ return update(state, {
+ byId: {
+ [action.id]: {
+ closedAt: {$set: action.closedAt},
+ },
+ },
+ });
case actions.UPDATE_ASSETS:
- return state
- .set('assets', List(action.assets));
+ return update(state, {
+ assets: {$set: action.assets},
+ });
default:
return state;
}
}
-const replaceAssets = (action, state) => {
- const assets = fromJS(action.assets.reduce((prev, curr) => {
- prev[curr.id] = curr;
- return prev;
- }, {}));
-
- return state
- .set('byId', assets)
- .set('count', action.count)
- .set('ids', List(assets.keys()));
-};
diff --git a/client/coral-admin/src/reducers/auth.js b/client/coral-admin/src/reducers/auth.js
index a60d73cec..a9c7f520e 100644
--- a/client/coral-admin/src/reducers/auth.js
+++ b/client/coral-admin/src/reducers/auth.js
@@ -1,43 +1,63 @@
-import {Map} from 'immutable';
import * as actions from '../constants/auth';
-const initialState = Map({
+const initialState = {
loggedIn: false,
user: null,
loginError: null,
loginMaxExceeded: false,
passwordRequestSuccess: null
-});
+};
export default function auth (state = initialState, action) {
switch (action.type) {
case actions.CHECK_LOGIN_REQUEST:
- return state
- .set('loadingUser', true);
+ return {
+ ...state,
+ loadingUser: true,
+ };
case actions.CHECK_LOGIN_FAILURE:
- return state
- .set('loggedIn', false)
- .set('loadingUser', false)
- .set('user', null);
+ return {
+ ...state,
+ loggedIn: false,
+ loadingUser: false,
+ user: null,
+ };
case actions.CHECK_LOGIN_SUCCESS:
- return state
- .set('loggedIn', true)
- .set('loadingUser', false)
- .set('user', action.user);
+ return {
+ ...state,
+ loggedIn: true,
+ loadingUser: false,
+ user: action.user,
+ };
case actions.LOGOUT:
return initialState;
case actions.LOGIN_SUCCESS:
- return state.set('loginMaxExceeded', false).set('loginError', null);
+ return {
+ ...state,
+ loginMaxExceeded: false,
+ loginError: null,
+ };
case actions.LOGIN_FAILURE:
- return state.set('loginError', action.message);
+ return {
+ ...state,
+ loginError: action.message,
+ };
case actions.FETCH_FORGOT_PASSWORD_REQUEST:
- return state.set('passwordRequestSuccess', null);
+ return {
+ ...state,
+ passwordRequestSuccess: null,
+ };
case actions.FETCH_FORGOT_PASSWORD_SUCCESS:
- return state.set('passwordRequestSuccess', 'If you have a registered account, a password reset link was sent to that email.');
+ return {
+ ...state,
+ passwordRequestSuccess: 'If you have a registered account, a password reset link was sent to that email.',
+ };
case actions.LOGIN_MAXIMUM_EXCEEDED:
- return state
- .set('loginMaxExceeded', true)
- .set('loginError', action.message);
+ return {
+ ...state,
+ loginMaxExceeded: true,
+ loginError: action.message,
+ };
default :
return state;
}
diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js
index 3fba504f8..9f724c8d6 100644
--- a/client/coral-admin/src/reducers/community.js
+++ b/client/coral-admin/src/reducers/community.js
@@ -1,5 +1,3 @@
-import {Map} from 'immutable';
-
import {
FETCH_COMMENTERS_REQUEST,
FETCH_COMMENTERS_FAILURE,
@@ -13,8 +11,8 @@ import {
HIDE_REJECT_USERNAME_DIALOG
} from '../constants/community';
-const initialState = Map({
- community: Map(),
+const initialState = {
+ community: {},
isFetchingPeople: false,
errorPeople: '',
accounts: [],
@@ -22,72 +20,87 @@ const initialState = Map({
ascPeople: false,
totalPagesPeople: 0,
pagePeople: 0,
- user: Map({}),
+ user: {},
banDialog: false,
rejectUsernameDialog: false
-});
+};
export default function community (state = initialState, action) {
switch (action.type) {
case FETCH_COMMENTERS_REQUEST :
- return state
- .set('isFetchingPeople', true);
+ return {
+ ...state,
+ isFetchingPeople: true,
+ };
case FETCH_COMMENTERS_FAILURE :
- return state
- .set('isFetchingPeople', false)
- .set('errorPeople', action.error);
-
+ return {
+ ...state,
+ isFetchingPeople: false,
+ errorPeople: action.error,
+ };
case FETCH_COMMENTERS_SUCCESS : {
const {accounts, type, page, count, limit, totalPages, ...rest} = action; // eslint-disable-line
- return state
- .merge({
- isFetchingPeople: false,
- errorPeople: '',
- pagePeople: page,
- countPeople: count,
- limitPeople: limit,
- totalPagesPeople: totalPages,
- ...rest
- })
- .set('accounts', accounts); // Sets to normal array
+ return {
+ ...state,
+ isFetchingPeople: false,
+ errorPeople: '',
+ pagePeople: page,
+ countPeople: count,
+ limitPeople: limit,
+ totalPagesPeople: totalPages,
+ ...rest,
+ accounts, // Sets to normal array
+ };
}
case SET_ROLE : {
- const commenters = state.get('accounts');
+ const commenters = state.accounts;
const idx = commenters.findIndex((el) => el.id === action.id);
commenters[idx].roles[0] = action.role;
- return state.set('accounts', commenters.map((id) => id));
+ return {
+ ...state,
+ accounts: commenters.map((id) => id),
+ };
}
case SET_COMMENTER_STATUS: {
- const commenters = state.get('accounts');
+ const commenters = state.accounts;
const idx = commenters.findIndex((el) => el.id === action.id);
commenters[idx].status = action.status;
- return state.set('accounts', commenters.map((id) => id));
+ return {
+ ...state,
+ accounts: commenters.map((id) => id),
+ };
}
case SORT_UPDATE :
- return state
- .set('fieldPeople', action.sort.field)
- .set('ascPeople', !state.get('ascPeople'));
+ return {
+ ...state,
+ fieldPeople: action.sort.field,
+ ascPeople: !state.ascPeople,
+ };
case HIDE_BANUSER_DIALOG:
- return state
- .set('banDialog', false);
+ return {
+ ...state,
+ banDialog: false,
+ };
case SHOW_BANUSER_DIALOG:
- return state
- .merge({
- user: Map(action.user),
- banDialog: true
- });
+ return {
+ ...state,
+ user: action.user,
+ banDialog: true,
+ };
case HIDE_REJECT_USERNAME_DIALOG:
- return state
- .set('rejectUsernameDialog', false);
+ return {
+ ...state,
+ rejectUsernameDialog: false,
+ };
case SHOW_REJECT_USERNAME_DIALOG:
- return state
- .merge({
- user: Map(action.user),
- rejectUsernameDialog: true
- });
+ return {
+ ...state,
+ user: action.user,
+ rejectUsernameDialog: true
+ };
default :
return state;
}
diff --git a/client/coral-admin/src/reducers/config.js b/client/coral-admin/src/reducers/config.js
index a5d11d6eb..310a198ed 100644
--- a/client/coral-admin/src/reducers/config.js
+++ b/client/coral-admin/src/reducers/config.js
@@ -1,15 +1,16 @@
-import {Map} from 'immutable';
-
import * as actions from '../actions/config';
-const initialState = Map({
- data: Map({})
-});
+const initialState = {
+ data: {}
+};
export default function config (state = initialState, action) {
switch (action.type) {
case actions.CONFIG_UPDATED:
- return state.set('data', Map(action.data));
+ return {
+ ...state,
+ data: action.data,
+ };
default:
return state;
}
diff --git a/client/coral-admin/src/reducers/install.js b/client/coral-admin/src/reducers/install.js
index 1f0f079ff..c6e85c69f 100644
--- a/client/coral-admin/src/reducers/install.js
+++ b/client/coral-admin/src/reducers/install.js
@@ -1,30 +1,29 @@
-import {Map, List} from 'immutable';
-
import * as actions from '../constants/install';
+import update from 'immutability-helper';
-const initialState = Map({
+const initialState = {
isLoading: false,
- data: Map({
- settings: Map({
+ data: {
+ settings: {
organizationName: '',
- domains: Map({
- whitelist: List()
- })
- }),
- user: Map({
+ domains: {
+ whitelist: [],
+ }
+ },
+ user: {
username: '',
email: '',
password: '',
confirmPassword: ''
- })
- }),
- errors: Map({
+ }
+ },
+ errors: {
organizationName: '',
username: '',
email: '',
password: '',
confirmPassword: ''
- }),
+ },
showErrors: false,
hasError: false,
error: null,
@@ -44,57 +43,91 @@ const initialState = Map({
installRequest: null,
installRequestError: null,
alreadyInstalled: false
-});
+};
export default function install (state = initialState, action) {
switch (action.type) {
case actions.NEXT_STEP:
- return state
- .set('step', state.get('step') + 1);
+ return {
+ ...state,
+ step: state.step + 1,
+ };
case actions.PREVIOUS_STEP:
- return state
- .set('step', state.get('step') - 1);
+ return {
+ ...state,
+ step: state.step - 1,
+ };
case actions.GO_TO_STEP:
- return state
- .set('step', action.step);
+ return {
+ ...state,
+ step: action.step,
+ };
case actions.UPDATE_PERMITTED_DOMAINS_SETTINGS:
- return state
- .setIn(['data', 'settings', 'domains', 'whitelist'], action.value);
+ return update(state, {
+ data: {
+ settings: {
+ domains: {
+ whitelist: {$set: action.value},
+ },
+ },
+ },
+ });
case actions.UPDATE_FORMDATA_SETTINGS:
- return state
- .setIn(['data', 'settings', action.name], action.value);
+ return update(state, {
+ data: {
+ settings: {
+ [action.name]: {$set: action.value},
+ },
+ },
+ });
case actions.UPDATE_FORMDATA_USER:
- return state
- .setIn(['data', 'user', action.name], action.value);
+ return update(state, {
+ data: {
+ user: {
+ [action.name]: {$set: action.value},
+ },
+ },
+ });
case actions.HAS_ERROR:
- return state
- .merge({
- hasError: true,
- showErrors: true
- });
+ return {
+ ...state,
+ hasError: true,
+ showErrors: true,
+ };
case actions.ADD_ERROR:
- return state
- .setIn(['errors', action.name], action.error);
+ return update(state, {
+ errors: {
+ [action.name]: {$set: action.error},
+ },
+ });
case actions.CLEAR_ERRORS:
- return state
- .set('errors', Map());
+ return {
+ ...state,
+ errors: {},
+ };
case actions.INSTALL_REQUEST:
- return state
- .set('isLoading', true);
+ return {
+ ...state,
+ isLoading: true,
+ };
case actions.INSTALL_SUCCESS:
- return state
- .set('isLoading', false)
- .set('installRequest', 'SUCCESS');
+ return {
+ ...state,
+ isLoading: false,
+ installRequest: 'SUCCESS',
+ };
case actions.INSTALL_FAILURE:
- return state
- .merge({
- isLoading: false,
- installRequest: 'FAILURE',
- installRequestError: action.error
- });
+ return {
+ ...state,
+ isLoading: false,
+ installRequest: 'FAILURE',
+ installRequestError: action.error
+ };
case actions.CHECK_INSTALL_SUCCESS:
- return state
- .set('alreadyInstalled', action.installed);
+ return {
+ ...state,
+ alreadyInstalled: action.installed,
+ };
default :
return state;
}
diff --git a/client/coral-admin/src/reducers/moderation.js b/client/coral-admin/src/reducers/moderation.js
index a0cba1f5f..e0a608488 100644
--- a/client/coral-admin/src/reducers/moderation.js
+++ b/client/coral-admin/src/reducers/moderation.js
@@ -1,37 +1,54 @@
-import {fromJS} from 'immutable';
import * as actions from '../constants/moderation';
-const initialState = fromJS({
+const initialState = {
singleView: false,
modalOpen: false,
storySearchVisible: false,
storySearchString: '',
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
sortOrder: 'REVERSE_CHRONOLOGICAL',
-});
+};
export default function moderation (state = initialState, action) {
switch (action.type) {
case actions.MODERATION_CLEAR_STATE:
return initialState;
case actions.TOGGLE_MODAL:
- return state
- .set('modalOpen', action.open);
+ return {
+ ...state,
+ modalOpen: action.open,
+ };
case actions.SINGLE_VIEW:
- return state
- .set('singleView', !state.get('singleView'));
+ return {
+ ...state,
+ singleView: !state.singleView,
+ };
case actions.HIDE_SHORTCUTS_NOTE:
- return state
- .set('shortcutsNoteVisible', 'hide');
+ return {
+ ...state,
+ shortcutsNoteVisible: 'hide',
+ };
case actions.SHOW_STORY_SEARCH:
- return state.set('storySearchVisible', true);
+ return {
+ ...state,
+ storySearchVisible: true,
+ };
case actions.HIDE_STORY_SEARCH:
- return state.set('storySearchVisible', false);
+ return {
+ ...state,
+ storySearchVisible: false,
+ };
case actions.STORY_SEARCH_CHANGE_VALUE:
- return state.set('storySearchString', action.value);
+ return {
+ ...state,
+ storySearchString: action.value,
+ };
case actions.SET_SORT_ORDER:
- return state.set('sortOrder', action.order);
- default :
+ return {
+ ...state,
+ sortOrder: action.order,
+ };
+ default:
return state;
}
}
diff --git a/client/coral-admin/src/reducers/settings.js b/client/coral-admin/src/reducers/settings.js
index 67907b327..6e29ba719 100644
--- a/client/coral-admin/src/reducers/settings.js
+++ b/client/coral-admin/src/reducers/settings.js
@@ -1,5 +1,5 @@
-import {Map, List} from 'immutable';
import * as actions from '../actions/settings';
+import update from 'immutability-helper';
// this is initialized here because
// currently you have to reload the dashboard to get new stats
@@ -10,63 +10,80 @@ const DASHBOARD_WINDOW_MINUTES = 5;
let then = new Date();
then.setMinutes(then.getMinutes() - DASHBOARD_WINDOW_MINUTES);
-const initialState = Map({
- wordlist: Map({
- banned: List(),
- suspect: List()
- }),
+const initialState = {
+ wordlist: {
+ banned: [],
+ suspect: []
+ },
dashboardWindowStart: then.toISOString(),
dashboardWindowEnd: new Date().toISOString(),
- domains: Map({
- whitelist: List()
- }),
+ domains: {
+ whitelist: []
+ },
saveSettingsError: null,
fetchSettingsError: null,
fetchingSettings: false
-});
+};
export default function settings (state = initialState, action) {
switch (action.type) {
case actions.SETTINGS_LOADING:
- return state
- .set('fetchingSettings', true)
- .set('fetchSettingsError', null);
+ return {
+ ...state,
+ fetchingSettings: true,
+ fetchSettingsError: null,
+ };
case actions.SETTINGS_RECEIVED:
- return state.merge({
+ return {
+ ...state,
fetchingSettings: false,
fetchSettingsError: null,
...action.settings
- });
+ };
case actions.SETTINGS_FETCH_ERROR:
- return state
- .set('fetchingSettings', false)
- .set('fetchSettingsError', action.error);
+ return {
+ ...state,
+ fetchingSettings: false,
+ fetchSettingsError: action.error,
+ };
case actions.SETTINGS_UPDATED:
- return state.merge({
+ return {
+ ...state,
fetchingSettings: false,
fetchSettingsError: null,
...action.settings
- });
+ };
case actions.SAVE_SETTINGS_LOADING:
- return state
- .set('fetchingSettings', true)
- .set('saveSettingsError', null);
+ return {
+ ...state,
+ fetchingSettings: true,
+ saveSettingsError: null,
+ };
case actions.SAVE_SETTINGS_SUCCESS:
- return state.merge({
+ return {
+ ...state,
fetchingSettings: false,
fetchSettingsError: null,
...action.settings
- });
+ };
case actions.SAVE_SETTINGS_FAILED:
- return state
- .set('fetchingSettings', false)
- .set('fetchSettingsError', action.error);
+ return {
+ ...state,
+ fetchingSettings: false,
+ fetchSettingsError: action.error,
+ };
case actions.WORDLIST_UPDATED:
- return state
- .setIn(['wordlist', action.listName], action.list);
+ return update(state, {
+ wordList: {
+ [action.listName]: {$set: action.list},
+ }
+ });
case actions.DOMAINLIST_UPDATED:
- return state
- .setIn(['domains', action.listName], action.list);
+ return update(state, {
+ domains: {
+ [action.listName]: {$set: action.list},
+ }
+ });
default:
return state;
}
diff --git a/client/coral-admin/src/routes/Community/containers/Community.js b/client/coral-admin/src/routes/Community/containers/Community.js
index a9a164ac9..b04fc0281 100644
--- a/client/coral-admin/src/routes/Community/containers/Community.js
+++ b/client/coral-admin/src/routes/Community/containers/Community.js
@@ -87,8 +87,8 @@ export const withCommunityQuery = withQuery(gql`
});
const mapStateToProps = (state) => ({
- community: state.community.toJS(),
- currentUser: state.auth.toJS().user,
+ community: state.community,
+ currentUser: state.auth.user,
});
const mapDispatchToProps = (dispatch) =>
diff --git a/client/coral-admin/src/routes/Community/containers/Table.js b/client/coral-admin/src/routes/Community/containers/Table.js
index 8bd5395e4..5d4e4914f 100644
--- a/client/coral-admin/src/routes/Community/containers/Table.js
+++ b/client/coral-admin/src/routes/Community/containers/Table.js
@@ -23,7 +23,7 @@ class TableContainer extends Component {
}
const mapStateToProps = (state) => ({
- commenters: state.community.get('accounts'),
+ commenters: state.community.accounts,
});
const mapDispatchToProps = (dispatch) =>
diff --git a/client/coral-admin/src/routes/Configure/containers/Configure.js b/client/coral-admin/src/routes/Configure/containers/Configure.js
index c68b39e0c..aff1e57b2 100644
--- a/client/coral-admin/src/routes/Configure/containers/Configure.js
+++ b/client/coral-admin/src/routes/Configure/containers/Configure.js
@@ -23,8 +23,8 @@ class ConfigureContainer extends Component {
}
const mapStateToProps = (state) => ({
- auth: state.auth.toJS(),
- settings: state.settings.toJS()
+ auth: state.auth,
+ settings: state.settings
});
const mapDispatchToProps = (dispatch) =>
diff --git a/client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js b/client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js
index 5f974971c..d92b2bd14 100644
--- a/client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js
+++ b/client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js
@@ -17,11 +17,11 @@ const ActivityWidget = ({assets}) => {
? assets.map((asset) => {
return (
-
Moderate
+
Moderate
{asset.commentCount}
-
+
{asset.title}
-
+
{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}
);
diff --git a/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js b/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js
index ef0974fbd..ef4a6857e 100644
--- a/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js
+++ b/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js
@@ -24,11 +24,11 @@ const FlagWidget = ({assets}) => {
return (
-
Moderate
+
Moderate
{flagSummary ? flagSummary.actionCount : 0}
-
+
{asset.title}
-
+
{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}
);
diff --git a/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js b/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js
index 2188e38dd..a61f888bf 100644
--- a/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js
+++ b/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js
@@ -19,11 +19,11 @@ const LikeWidget = ({assets}) => {
const likeSummary = asset.action_summaries.find((s) => s.type === 'LikeAssetActionSummary');
return (
-
Moderate
+
Moderate
{likeSummary ? likeSummary.actionCount : 0}
-
+
{asset.title}
-
+
{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}
);
diff --git a/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js b/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js
index ea1fb2b6c..74faedcf7 100644
--- a/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js
+++ b/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js
@@ -54,8 +54,8 @@ export const witDashboardQuery = withQuery(gql`
const mapStateToProps = (state) => {
return {
- settings: state.settings.toJS(),
- moderation: state.moderation.toJS()
+ settings: state.settings,
+ moderation: state.moderation
};
};
diff --git a/client/coral-admin/src/routes/Install/containers/Install.js b/client/coral-admin/src/routes/Install/containers/Install.js
index 5dcb35705..442127e47 100644
--- a/client/coral-admin/src/routes/Install/containers/Install.js
+++ b/client/coral-admin/src/routes/Install/containers/Install.js
@@ -35,7 +35,7 @@ InstallContainer.contextTypes = {
};
const mapStateToProps = (state) => ({
- install: state.install.toJS()
+ install: state.install
});
const mapDispatchToProps = (dispatch) =>
diff --git a/client/coral-admin/src/routes/Moderation/components/CommentCount.css b/client/coral-admin/src/routes/Moderation/components/CommentCount.css
index 4d24d5702..998133d07 100644
--- a/client/coral-admin/src/routes/Moderation/components/CommentCount.css
+++ b/client/coral-admin/src/routes/Moderation/components/CommentCount.css
@@ -1,16 +1,16 @@
.count {
display: inline-block;
- background: #989797;
+ background: #616161;
margin: 2px;
vertical-align: middle;
- padding: 1px 7px;
+ padding: 1px 5px;
border-radius: 2px;
margin-left: 2px;
- line-height: 20px;
+ line-height: 18px;
box-sizing: border-box;
- height: 21px;
+ height: 18px;
right: 0;
- margin-top: -2px;
+ margin-top: 0px;
font-size: 12px;
color: white;
}
diff --git a/client/coral-admin/src/routes/Moderation/components/Moderation.js b/client/coral-admin/src/routes/Moderation/components/Moderation.js
index ce0c31e33..f50ff9c50 100644
--- a/client/coral-admin/src/routes/Moderation/components/Moderation.js
+++ b/client/coral-admin/src/routes/Moderation/components/Moderation.js
@@ -101,33 +101,19 @@ export default class Moderation extends Component {
}
render () {
- const {root, data, moderation, settings, viewUserDetail, hideUserDetail, activeTab, getModPath, premodEnabled, ...props} = this.props;
- const assetId = this.props.params.id;
+ const {root, data, moderation, settings, viewUserDetail, hideUserDetail, activeTab, getModPath, queueConfig, handleCommentChange, ...props} = this.props;
const {asset} = root;
+ const assetId = asset && asset.id;
const comments = root[activeTab];
- let activeTabCount;
- switch(activeTab) {
- case 'all':
- activeTabCount = root.allCount;
- break;
- case 'new':
- activeTabCount = root.newCount;
- break;
- case 'approved':
- activeTabCount = root.approvedCount;
- break;
- case 'premod':
- activeTabCount = root.premodCount;
- break;
- case 'reported':
- activeTabCount = root.reportedCount;
- break;
- case 'rejected':
- activeTabCount = root.rejectedCount;
- break;
- }
+ const activeTabCount = root[`${activeTab}Count`];
+ const menuItems = Object.keys(queueConfig).map((queue) => ({
+ key: queue,
+ name: queueConfig[queue].name,
+ icon: queueConfig[queue].icon,
+ count: root[`${queue}Count`]
+ }));
return (
@@ -139,16 +125,10 @@ export default class Moderation extends Component {
/>
diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js
index 62bbd4d2e..7b427ac54 100644
--- a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js
+++ b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js
@@ -9,16 +9,10 @@ import cn from 'classnames';
import t from 'coral-framework/services/i18n';
const ModerationMenu = ({
- asset = {},
- allCount,
- approvedCount,
- premodCount,
- newCount,
- rejectedCount,
- reportedCount,
+ asset = {},
+ items,
selectSort,
sort,
- premodEnabled,
getModPath,
activeTab
}) => {
@@ -27,49 +21,15 @@ const ModerationMenu = ({
-
- {
- premodEnabled ? (
-
- {t('modqueue.premod')}
-
- ) : (
-
- {t('modqueue.new')}
-
- )
- }
-
-
- {t('modqueue.reported')}
-
-
- {t('modqueue.approved')}
-
-
- {t('modqueue.rejected')}
-
-
- {t('modqueue.all')}
-
+ {items.map((queue) =>
+
+ {queue.name}
+
+ )}
{
+ return handleCommentChange(root, comment, this.props.data.variables.sort, notify, queueConfig, this.activeTab);
+ };
+
get activeTab() {
- const {root: {asset, settings}, router, route} = this.props;
+ const {root: {asset, settings}} = this.props;
+ const id = getAssetId(this.props);
+ const tab = getTab(this.props);
// Grab premod from asset or from settings
- const premod = !router.params.id ? settings.moderation : asset.settings.moderation;
+ const premod = !id ? settings.moderation : asset.settings.moderation;
const queue = isPremod(premod) ? 'premod' : 'new';
- const activeTab = route.path && route.path !== ':id' ? route.path : queue;
+ const activeTab = tab ? tab : queue;
return activeTab;
}
@@ -57,15 +78,10 @@ class ModerationContainer extends Component {
variables,
updateQuery: (prev, {subscriptionData: {data: {commentAccepted: comment}}}) => {
const user = comment.status_history[comment.status_history.length - 1].assigned_by;
- const sort = this.props.moderation.sortOrder;
const notify = this.props.auth.user.id === user.id
- ? {}
- : {
- activeQueue: this.activeTab,
- text: t('modqueue.notify_accepted', user.username, prepareNotificationText(comment.body)),
- anyQueue: false,
- };
- return handleCommentChange(prev, comment, sort, notify);
+ ? ''
+ : t('modqueue.notify_accepted', user.username, prepareNotificationText(comment.body));
+ return this.handleCommentChange(prev, comment, notify);
},
});
@@ -74,15 +90,10 @@ class ModerationContainer extends Component {
variables,
updateQuery: (prev, {subscriptionData: {data: {commentRejected: comment}}}) => {
const user = comment.status_history[comment.status_history.length - 1].assigned_by;
- const sort = this.props.moderation.sortOrder;
const notify = this.props.auth.user.id === user.id
- ? {}
- : {
- activeQueue: this.activeTab,
- text: t('modqueue.notify_rejected', user.username, prepareNotificationText(comment.body)),
- anyQueue: false,
- };
- return handleCommentChange(prev, comment, sort, notify);
+ ? ''
+ : t('modqueue.notify_rejected', user.username, prepareNotificationText(comment.body));
+ return this.handleCommentChange(prev, comment, notify);
},
});
@@ -90,13 +101,8 @@ class ModerationContainer extends Component {
document: COMMENT_EDITED_SUBSCRIPTION,
variables,
updateQuery: (prev, {subscriptionData: {data: {commentEdited: comment}}}) => {
- const sort = this.props.moderation.sortOrder;
- const notify = {
- activeQueue: this.activeTab,
- text: t('modqueue.notify_edited', comment.user.username, prepareNotificationText(comment.body)),
- anyQueue: false,
- };
- return handleCommentChange(prev, comment, sort, notify);
+ const notify = t('modqueue.notify_edited', comment.user.username, prepareNotificationText(comment.body));
+ return this.handleCommentChange(prev, comment, notify);
},
});
@@ -105,13 +111,8 @@ class ModerationContainer extends Component {
variables,
updateQuery: (prev, {subscriptionData: {data: {commentFlagged: comment}}}) => {
const user = comment.actions[comment.actions.length - 1].user;
- const sort = this.props.moderation.sortOrder;
- const notify = {
- activeQueue: this.activeTab,
- text: t('modqueue.notify_flagged', user.username, prepareNotificationText(comment.body)),
- anyQueue: true,
- };
- return handleCommentChange(prev, comment, sort, notify);
+ const notify = t('modqueue.notify_flagged', user.username, prepareNotificationText(comment.body));
+ return this.handleCommentChange(prev, comment, notify);
},
});
@@ -160,28 +161,9 @@ class ModerationContainer extends Component {
cursor: this.props.root[tab].endCursor,
sort: this.props.data.variables.sort,
asset_id: this.props.data.variables.asset_id,
+ statuses: queueConfig[tab].statuses,
+ action_type: queueConfig[tab].action_type,
};
- switch(tab) {
- case 'all':
- variables.statuses = null;
- break;
- case 'new':
- variables.statuses = ['NONE', 'PREMOD'];
- break;
- case 'approved':
- variables.statuses = ['ACCEPTED'];
- break;
- case 'premod':
- variables.statuses = ['PREMOD'];
- break;
- case 'reported':
- variables.statuses = ['NONE', 'PREMOD'];
- variables.action_type = 'FLAG';
- break;
- case 'rejected':
- variables.statuses = ['REJECTED'];
- break;
- }
return this.props.data.fetchMore({
query: LOAD_MORE_QUERY,
variables,
@@ -199,7 +181,8 @@ class ModerationContainer extends Component {
};
render () {
- const {root, root: {asset, settings}, data, params: {id: assetId}} = this.props;
+ const {root, root: {asset, settings}, data} = this.props;
+ const assetId = getAssetId(this.props);
if (data.error) {
return Error
;
@@ -222,6 +205,14 @@ class ModerationContainer extends Component {
return ;
}
+ const premodEnabled = assetId ? isPremod(asset.settings.moderation) : isPremod(settings.moderation);
+ const currentQueueConfig = Object.assign({}, queueConfig);
+ if (premodEnabled) {
+ delete currentQueueConfig.new;
+ } else {
+ delete currentQueueConfig.premod;
+ }
+
return ;
}
}
@@ -314,49 +306,25 @@ const commentConnectionFragment = gql`
const withModQueueQuery = withQuery(gql`
query CoralAdmin_Moderation($asset_id: ID, $sort: SORT_ORDER, $allAssets: Boolean!) {
- all: comments(query: {
- statuses: [NONE, PREMOD, ACCEPTED, REJECTED],
- asset_id: $asset_id,
- sort: $sort
- }) {
- ...CoralAdmin_Moderation_CommentConnection
- }
- new: comments(query: {
- statuses: [NONE, PREMOD],
- asset_id: $asset_id,
- sort: $sort
- }) {
- ...CoralAdmin_Moderation_CommentConnection
- }
- approved: comments(query: {
- statuses: [ACCEPTED],
- asset_id: $asset_id,
- sort: $sort
- }) {
- ...CoralAdmin_Moderation_CommentConnection
- }
- premod: comments(query: {
- statuses: [PREMOD],
+ ${Object.keys(queueConfig).map((queue) => `
+ ${queue}: comments(query: {
+ ${queueConfig[queue].statuses ? `statuses: [${queueConfig[queue].statuses.join(', ')}],` : ''}
+ ${queueConfig[queue].tags ? `tags: ["${queueConfig[queue].tags.join('", "')}"],` : ''}
+ ${queueConfig[queue].action_type ? `action_type: ${queueConfig[queue].action_type}` : ''}
asset_id: $asset_id,
sort: $sort
- }) {
- ...CoralAdmin_Moderation_CommentConnection
- }
- reported: comments(query: {
- action_type: FLAG,
+ }) {
+ ...CoralAdmin_Moderation_CommentConnection
+ }
+ `)}
+ ${Object.keys(queueConfig).map((queue) => `
+ ${queue}Count: commentCount(query: {
+ ${queueConfig[queue].statuses ? `statuses: [${queueConfig[queue].statuses.join(', ')}],` : ''}
+ ${queueConfig[queue].tags ? `tags: ["${queueConfig[queue].tags.join('", "')}"],` : ''}
+ ${queueConfig[queue].action_type ? `action_type: ${queueConfig[queue].action_type}` : ''}
asset_id: $asset_id,
- statuses: [NONE, PREMOD],
- sort: $sort
- }) {
- ...CoralAdmin_Moderation_CommentConnection
- }
- rejected: comments(query: {
- statuses: [REJECTED],
- asset_id: $asset_id,
- sort: $sort
- }) {
- ...CoralAdmin_Moderation_CommentConnection
- }
+ })
+ `)}
asset(id: $asset_id) @skip(if: $allAssets) {
id
title
@@ -365,30 +333,6 @@ const withModQueueQuery = withQuery(gql`
moderation
}
}
- allCount: commentCount(query: {
- asset_id: $asset_id
- })
- newCount: commentCount(query: {
- statuses: [NONE, PREMOD],
- asset_id: $asset_id
- })
- approvedCount: commentCount(query: {
- statuses: [ACCEPTED],
- asset_id: $asset_id
- })
- premodCount: commentCount(query: {
- statuses: [PREMOD],
- asset_id: $asset_id
- })
- rejectedCount: commentCount(query: {
- statuses: [REJECTED],
- asset_id: $asset_id
- })
- reportedCount: commentCount(query: {
- action_type: FLAG,
- asset_id: $asset_id,
- statuses: [NONE, PREMOD]
- })
settings {
organizationName
moderation
@@ -396,11 +340,12 @@ const withModQueueQuery = withQuery(gql`
}
${commentConnectionFragment}
`, {
- options: ({params: {id = null}, moderation: {sortOrder}}) => {
+ options: (props) => {
+ const id = getAssetId(props);
return {
variables: {
asset_id: id,
- sort: sortOrder,
+ sort: props.moderation.sortOrder,
allAssets: id === null
}
};
@@ -409,33 +354,18 @@ const withModQueueQuery = withQuery(gql`
const withQueueCountPolling = withQuery(gql`
query CoralAdmin_ModerationCountPoll($asset_id: ID) {
- allCount: commentCount(query: {
- asset_id: $asset_id
- })
- newCount: commentCount(query: {
- statuses: [NONE, PREMOD],
- asset_id: $asset_id
- })
- approvedCount: commentCount(query: {
- statuses: [ACCEPTED],
- asset_id: $asset_id
- })
- premodCount: commentCount(query: {
- statuses: [PREMOD],
- asset_id: $asset_id
- })
- rejectedCount: commentCount(query: {
- statuses: [REJECTED],
- asset_id: $asset_id
- })
- reportedCount: commentCount(query: {
- action_type: FLAG,
- asset_id: $asset_id,
- statuses: [NONE, PREMOD]
- })
+ ${Object.keys(queueConfig).map((queue) => `
+ ${queue}Count: commentCount(query: {
+ ${queueConfig[queue].statuses ? `statuses: [${queueConfig[queue].statuses.join(', ')}],` : ''}
+ ${queueConfig[queue].tags ? `tags: ["${queueConfig[queue].tags.join('", "')}"],` : ''}
+ ${queueConfig[queue].action_type ? `action_type: ${queueConfig[queue].action_type}` : ''}
+ asset_id: $asset_id,
+ })
+ `)}
}
`, {
- options: ({params: {id = null}}) => {
+ options: (props) => {
+ const id = getAssetId(props);
return {
pollInterval: 5000,
variables: {
@@ -446,9 +376,9 @@ const withQueueCountPolling = withQuery(gql`
});
const mapStateToProps = (state) => ({
- moderation: state.moderation.toJS(),
- settings: state.settings.toJS(),
- auth: state.auth.toJS(),
+ moderation: state.moderation,
+ settings: state.settings,
+ auth: state.auth,
});
const mapDispatchToProps = (dispatch) => ({
diff --git a/client/coral-admin/src/graphql/utils.js b/client/coral-admin/src/routes/Moderation/graphql.js
similarity index 74%
rename from client/coral-admin/src/graphql/utils.js
rename to client/coral-admin/src/routes/Moderation/graphql.js
index f729cc0ea..0d4991f72 100644
--- a/client/coral-admin/src/graphql/utils.js
+++ b/client/coral-admin/src/routes/Moderation/graphql.js
@@ -1,7 +1,6 @@
import update from 'immutability-helper';
import * as notification from 'coral-admin/src/services/notification';
-const queues = ['all', 'premod', 'reported', 'approved', 'rejected', 'new'];
const limit = 10;
const ascending = (a, b) => {
@@ -67,32 +66,24 @@ function addCommentToQueue(root, queue, comment, sort) {
/**
* getCommentQueues determines in which queues a comment should be placed.
*/
-function getCommentQueues(comment) {
- const queues = ['all'];
- const isFlagged = comment.actions && comment.actions.some((a) => a.__typename === 'FlagAction');
-
- switch(comment.status) {
- case 'ACCEPTED':
- queues.push('approved');
- break;
- case 'REJECTED':
- queues.push('rejected');
- break;
- case 'PREMOD':
- queues.push('premod');
- queues.push('new');
- if (isFlagged) {
- queues.push('reported');
+function getCommentQueues(comment, queueConfig) {
+ const queues = [];
+ Object.keys(queueConfig).forEach((key) => {
+ const {action_type, statuses, tags} = queueConfig[key];
+ let addToQueues = false;
+ if (statuses && statuses.indexOf(comment.status) >= 0) {
+ addToQueues = true;
}
- break;
- case 'NONE':
- queues.push('new');
- if (isFlagged) {
- queues.push('reported');
+ if (tags && comment.tags && comment.tags.some((tagLink) => tags.indexOf(tagLink.tag.name) >= 0)) {
+ addToQueues = true;
}
- break;
- }
-
+ if (action_type && comment.actions && comment.actions.some((a) => a.__typename.toLowerCase() === `${action_type}action`)) {
+ addToQueues = true;
+ }
+ if (addToQueues) {
+ queues.push(key);
+ }
+ });
return queues;
}
@@ -106,42 +97,42 @@ function getCommentQueues(comment) {
* @param {string} notify.text notification text to show
* @param {bool} notify.anyQueue if true show the notification when the comment is shown
* in the current active queue besides the 'all' queue.
+ * @param {Object} queueConfig queue configuration
* @return {Object} next state of the store
*/
-export function handleCommentChange(root, comment, sort, notify) {
+export function handleCommentChange(root, comment, sort, notify, queueConfig, activeQueue) {
let next = root;
- const nextQueues = getCommentQueues(comment);
+ const nextQueues = getCommentQueues(comment, queueConfig);
let notificationShown = false;
const showNotificationOnce = () => {
if (notificationShown) {
return;
}
- notification.info(notify.text);
+ notification.info(notify);
notificationShown = true;
};
- queues.forEach((queue) => {
+ Object.keys(queueConfig).forEach((queue) => {
if (nextQueues.indexOf(queue) >= 0) {
if (!queueHasComment(next, queue, comment.id)) {
next = addCommentToQueue(next, queue, comment, sort);
- if (notify && notify.activeQueue === queue && shouldCommentBeAdded(next, queue, comment, sort)) {
+ if (notify && activeQueue === queue && shouldCommentBeAdded(next, queue, comment, sort)) {
showNotificationOnce(comment);
}
}
} else if(queueHasComment(next, queue, comment.id)){
next = removeCommentFromQueue(next, queue, comment.id);
- if (notify && notify.activeQueue === queue) {
+ if (notify && activeQueue === queue) {
showNotificationOnce(comment);
}
}
if (
notify
- && (queue === 'all' || notify.anyQueue)
&& queueHasComment(next, queue, comment.id)
- && notify.activeQueue === queue
+ && activeQueue === queue
) {
showNotificationOnce(comment);
}
diff --git a/client/coral-admin/src/routes/Moderation/queueConfig.js b/client/coral-admin/src/routes/Moderation/queueConfig.js
new file mode 100644
index 000000000..b9e9e5320
--- /dev/null
+++ b/client/coral-admin/src/routes/Moderation/queueConfig.js
@@ -0,0 +1,37 @@
+import t from 'coral-framework/services/i18n';
+import {getModQueueConfigs} from 'coral-framework/helpers/plugins';
+
+export default {
+ premod: {
+ statuses: ['PREMOD'],
+ icon: 'access_time',
+ name: t('modqueue.premod'),
+ },
+ new: {
+ statuses: ['NONE', 'PREMOD'],
+ icon: 'question_answer',
+ name: t('modqueue.new'),
+ },
+ reported: {
+ action_type: 'FLAG',
+ statuses: ['NONE', 'PREMOD'],
+ icon: 'flag',
+ name: t('modqueue.reported'),
+ },
+ approved: {
+ statuses: ['ACCEPTED'],
+ icon: 'check',
+ name: t('modqueue.approved'),
+ },
+ rejected: {
+ statuses: ['REJECTED'],
+ icon: 'close',
+ name: t('modqueue.rejected'),
+ },
+ all: {
+ statuses: ['NONE', 'PREMOD', 'ACCEPTED', 'REJECTED'],
+ icon: 'question_answer',
+ name: t('modqueue.all'),
+ },
+ ...getModQueueConfigs(),
+};
diff --git a/client/coral-admin/src/routes/Stories/containers/Stories.js b/client/coral-admin/src/routes/Stories/containers/Stories.js
index 450efcf74..f645f310c 100644
--- a/client/coral-admin/src/routes/Stories/containers/Stories.js
+++ b/client/coral-admin/src/routes/Stories/containers/Stories.js
@@ -12,7 +12,7 @@ class StoriesContainer extends Component {
}
const mapStateToProps = (state) => ({
- assets: state.assets.toJS()
+ assets: state.assets
});
const mapDispatchToProps = (dispatch) =>
diff --git a/client/coral-configure/containers/ConfigureStreamContainer.js b/client/coral-configure/containers/ConfigureStreamContainer.js
index e7b937030..8489c809f 100644
--- a/client/coral-configure/containers/ConfigureStreamContainer.js
+++ b/client/coral-configure/containers/ConfigureStreamContainer.js
@@ -119,7 +119,7 @@ class ConfigureStreamContainer extends Component {
}
const mapStateToProps = (state) => ({
- asset: state.asset.toJS()
+ asset: state.asset
});
const mapDispatchToProps = (dispatch) => ({
diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js
index 06646a6c2..e21a683d1 100644
--- a/client/coral-embed-stream/src/containers/Embed.js
+++ b/client/coral-embed-stream/src/containers/Embed.js
@@ -170,7 +170,7 @@ export const withEmbedQuery = withQuery(EMBED_QUERY, {
});
const mapStateToProps = (state) => ({
- auth: state.auth.toJS(),
+ auth: state.auth,
commentId: state.stream.commentId,
assetId: state.stream.assetId,
assetUrl: state.stream.assetUrl,
diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js
index 73676713c..451b115a9 100644
--- a/client/coral-embed-stream/src/containers/Stream.js
+++ b/client/coral-embed-stream/src/containers/Stream.js
@@ -10,7 +10,6 @@ import {
import * as authActions from 'coral-framework/actions/auth';
import * as notificationActions from 'coral-framework/actions/notification';
-import {editName} from 'coral-framework/actions/user';
import {setActiveReplyBox, setActiveTab, viewAllComments} from '../actions/stream';
import Stream from '../components/Stream';
import Comment from './Comment';
@@ -26,7 +25,7 @@ import {
} from '../graphql/utils';
import omit from 'lodash/omit';
-const {showSignInDialog} = authActions;
+const {showSignInDialog, editName} = authActions;
const {addNotification} = notificationActions;
class StreamContainer extends React.Component {
@@ -298,7 +297,7 @@ const fragments = {
};
const mapStateToProps = (state) => ({
- auth: state.auth.toJS(),
+ auth: state.auth,
refetching: state.embed.refetching,
commentCountCache: state.stream.commentCountCache,
activeReplyBox: state.stream.activeReplyBox,
diff --git a/client/coral-embed-stream/src/graphql/index.js b/client/coral-embed-stream/src/graphql/index.js
index f52cc4e7d..b4c5ce163 100644
--- a/client/coral-embed-stream/src/graphql/index.js
+++ b/client/coral-embed-stream/src/graphql/index.js
@@ -147,13 +147,11 @@ const extension = {
__typename: 'Comment',
user: {
__typename: 'User',
- id: auth.toJS().user.id,
- username: auth.toJS().user.username
+ id: auth.user.id,
+ username: auth.user.username
},
created_at: new Date().toISOString(),
body,
- parent_id,
- asset_id,
action_summaries: [],
tags: tags.map((tag) => ({
tag: {
@@ -162,15 +160,21 @@ const extension = {
__typename: 'Tag'
},
assigned_by: {
- id: auth.toJS().user.id,
+ id: auth.user.id,
__typename: 'User'
},
__typename: 'TagLink'
})),
status: 'NONE',
replyCount: 0,
+ asset: {
+ __typename: 'Asset',
+ id: asset_id,
+ title: '',
+ url: '',
+ },
parent: parent_id
- ? {id: parent_id}
+ ? {__typename: 'Comment', id: parent_id}
: null,
replies: {
__typename: 'CommentConnection',
diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css
index 802cd2e5b..6ba376096 100644
--- a/client/coral-embed-stream/style/default.css
+++ b/client/coral-embed-stream/style/default.css
@@ -13,7 +13,7 @@ body {
width: 100%;
font-size: 14px;
margin: 0px;
- padding: 0px 0px 50px 0px;
+ padding: 0px 0px 100px 0px;
height: auto !important;
}
diff --git a/client/coral-framework/actions/asset.js b/client/coral-framework/actions/asset.js
index 24739ddde..aab3caf3a 100644
--- a/client/coral-framework/actions/asset.js
+++ b/client/coral-framework/actions/asset.js
@@ -13,7 +13,7 @@ const updateAssetSettingsSuccess = (settings) => ({type: actions.UPDATE_ASSET_SE
const updateAssetSettingsFailure = (error) => ({type: actions.UPDATE_ASSET_SETTINGS_FAILURE, error});
export const updateConfiguration = (newConfig) => (dispatch, getState) => {
- const assetId = getState().asset.toJS().id;
+ const assetId = getState().asset.id;
dispatch(updateAssetSettingsRequest());
coralApi(`/assets/${assetId}/settings`, {method: 'PUT', body: newConfig})
.then(() => {
@@ -27,7 +27,7 @@ export const updateConfiguration = (newConfig) => (dispatch, getState) => {
};
export const updateOpenStream = (closedBody) => (dispatch, getState) => {
- const assetId = getState().asset.toJS().id;
+ const assetId = getState().asset.id;
dispatch(fetchAssetRequest());
coralApi(`/assets/${assetId}/status`, {method: 'PUT', body: closedBody})
.then(() => {
diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js
index f4cf5217e..7c6d3bf89 100644
--- a/client/coral-framework/actions/auth.js
+++ b/client/coral-framework/actions/auth.js
@@ -4,6 +4,7 @@ import * as actions from '../constants/auth';
import * as Storage from '../helpers/storage';
import coralApi, {base} from '../helpers/request';
import pym from '../services/pym';
+import {addNotification} from '../actions/notification';
import {resetWebsocket} from 'coral-framework/services/client';
import t from 'coral-framework/services/i18n';
@@ -220,7 +221,7 @@ const signUpSuccess = (user) => ({type: actions.FETCH_SIGNUP_SUCCESS, user});
const signUpFailure = (error) => ({type: actions.FETCH_SIGNUP_FAILURE, error});
export const fetchSignUp = (formData) => (dispatch, getState) => {
- const redirectUri = getState().auth.toJS().redirectUri;
+ const redirectUri = getState().auth.redirectUri;
dispatch(signUpRequest());
coralApi('/users', {
@@ -257,7 +258,7 @@ const forgotPasswordFailure = (error) => ({
export const fetchForgotPassword = (email) => (dispatch, getState) => {
dispatch(forgotPasswordRequest(email));
- const redirectUri = getState().auth.toJS().redirectUri;
+ const redirectUri = getState().auth.redirectUri;
coralApi('/account/password/reset', {
method: 'POST',
body: {email, loc: redirectUri}
@@ -351,7 +352,7 @@ const verifyEmailFailure = () => ({
});
export const requestConfirmEmail = (email) => (dispatch, getState) => {
- const redirectUri = getState().auth.toJS().redirectUri;
+ const redirectUri = getState().auth.redirectUri;
dispatch(verifyEmailRequest());
return coralApi('/users/resend-verify', {
method: 'POST',
@@ -378,3 +379,23 @@ export const setRedirectUri = (uri) => ({
type: actions.SET_REDIRECT_URI,
uri,
});
+
+//==============================================================================
+// Edit Username
+//==============================================================================
+
+const editUsernameFailure = (error) => ({type: actions.EDIT_USERNAME_FAILURE, error});
+const editUsernameSuccess = () => ({type: actions.EDIT_USERNAME_SUCCESS});
+
+export const editName = (username) => (dispatch) => {
+ return coralApi('/account/username', {method: 'PUT', body: {username}})
+ .then(() => {
+ dispatch(editUsernameSuccess());
+ dispatch(addNotification('success', t('framework.success_name_update')));
+ })
+ .catch((error) => {
+ console.error(error);
+ const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
+ dispatch(editUsernameFailure(errorMessage));
+ });
+};
diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js
deleted file mode 100644
index a6ad45d4a..000000000
--- a/client/coral-framework/actions/user.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import {addNotification} from '../actions/notification';
-import coralApi from '../helpers/request';
-import * as actions from '../constants/auth';
-
-import t from 'coral-framework/services/i18n';
-
-const editUsernameFailure = (error) => ({type: actions.EDIT_USERNAME_FAILURE, error});
-const editUsernameSuccess = () => ({type: actions.EDIT_USERNAME_SUCCESS});
-
-export const editName = (username) => (dispatch) => {
- return coralApi('/account/username', {method: 'PUT', body: {username}})
- .then(() => {
- dispatch(editUsernameSuccess());
- dispatch(addNotification('success', t('framework.success_name_update')));
- })
- .catch((error) => {
- console.error(error);
- const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
- dispatch(editUsernameFailure(errorMessage));
- });
-};
diff --git a/client/coral-framework/constants/assets.js b/client/coral-framework/constants/assets.js
deleted file mode 100644
index 3883ee835..000000000
--- a/client/coral-framework/constants/assets.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const MULTIPLE_ASSETS_REQUEST = 'MULTIPLE_ASSETS_REQUEST';
-export const MULTIPLE_ASSETS_SUCCESS = 'MULTIPLE_ASSETS_SUCCESS';
-export const MULTIPLE_ASSSETS_FAILURE = 'MULTIPLE_ASSSETS_FAILURE';
diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js
deleted file mode 100644
index 1557a42c9..000000000
--- a/client/coral-framework/constants/user.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export const EDIT_NAME_REQUEST = 'EDIT_NAME_REQUEST';
-export const EDIT_NAME_SUCCESS = 'EDIT_NAME_SUCCESS';
-export const EDIT_NAME_FAILURE = 'EDIT_NAME_FAILURE';
-export const COMMENTS_BY_USER_REQUEST = 'COMMENTS_BY_USER_REQUEST';
-export const COMMENTS_BY_USER_SUCCESS = 'COMMENTS_BY_USER_SUCCESS';
-export const COMMENTS_BY_USER_FAILURE = 'COMMENTS_BY_USER_FAILURE';
-export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
-export const UPDATE_USERNAME = 'UPDATE_USERNAME';
-export const IGNORE_USER_SUCCESS = 'IGNORE_USER_SUCCESS';
-export const STOP_IGNORING_USER_SUCCESS = 'STOP_IGNORING_USER_SUCCESS';
diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js
index 5e5a8c0c0..b2ca78142 100644
--- a/client/coral-framework/helpers/plugins.js
+++ b/client/coral-framework/helpers/plugins.js
@@ -3,6 +3,7 @@ import uniq from 'lodash/uniq';
import pick from 'lodash/pick';
import merge from 'lodash/merge';
import flattenDeep from 'lodash/flattenDeep';
+import isEmpty from 'lodash/isEmpty';
import flatten from 'lodash/flatten';
import {loadTranslations} from 'coral-framework/services/i18n';
import {injectReducers} from 'coral-framework/services/store';
@@ -64,7 +65,13 @@ export function getSlotFragments(slot, part) {
export function getGraphQLExtensions() {
return plugins
.map((o) => pick(o.module, ['mutations', 'queries', 'fragments']))
- .filter((o) => o);
+ .filter((o) => !isEmpty(o));
+}
+
+export function getModQueueConfigs() {
+ return merge(...plugins
+ .map((o) => o.module.modQueues)
+ .filter((o) => o));
}
function getTranslations() {
diff --git a/client/coral-framework/hocs/withMutation.js b/client/coral-framework/hocs/withMutation.js
index c9108c0fe..7e2c3d09d 100644
--- a/client/coral-framework/hocs/withMutation.js
+++ b/client/coral-framework/hocs/withMutation.js
@@ -92,13 +92,13 @@ export default (document, config = {}) => (WrappedComponent) => {
// Do not run updates when we have mutation errors.
return prev;
}
- return map[key](prev, result);
+ return map[key](prev, result) || prev;
};
} else {
const existing = res[key];
res[key] = (prev, result) => {
const next = existing(prev, result);
- return map[key](next, result);
+ return map[key](next, result) || next;
};
}
});
diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js
index adb67b7b3..82c9c29d4 100644
--- a/client/coral-framework/hocs/withQuery.js
+++ b/client/coral-framework/hocs/withQuery.js
@@ -128,8 +128,10 @@ export default (document, config = {}) => (WrappedComponent) => {
const reducer = withSkipOnErrors(
reducerCallbacks.reduce(
- (a, b) => (prev, ...rest) =>
- b(a(prev, ...rest), ...rest),
+ (a, b) => (prev, ...rest) => {
+ const next = a(prev, ...rest);
+ return b(next, ...rest) || next;
+ }
));
return {
diff --git a/client/coral-framework/reducers/asset.js b/client/coral-framework/reducers/asset.js
index f9d0a55e3..b9837eddd 100644
--- a/client/coral-framework/reducers/asset.js
+++ b/client/coral-framework/reducers/asset.js
@@ -1,24 +1,27 @@
-import {Map} from 'immutable';
import * as actions from '../constants/asset';
-const initialState = Map({
+const initialState = {
closedAt: null,
settings: null,
title: null,
url: null,
- features: Map({}),
+ features: {},
status: 'open',
moderation: null
-});
+};
export default function asset (state = initialState, action) {
switch (action.type) {
case actions.FETCH_ASSET_SUCCESS:
- return state
- .merge(action.asset);
+ return {
+ ...state,
+ ...action.asset,
+ };
case actions.UPDATE_ASSET_SETTINGS_SUCCESS:
- return state
- .setIn(['settings'], action.settings);
+ return {
+ ...state,
+ settings: action.settings,
+ };
default:
return state;
}
diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js
index 5481b7a5e..1e18ddffa 100644
--- a/client/coral-framework/reducers/auth.js
+++ b/client/coral-framework/reducers/auth.js
@@ -1,8 +1,7 @@
-import {Map, fromJS} from 'immutable';
import * as actions from '../constants/auth';
import pym from 'coral-framework/services/pym';
-const initialState = Map({
+const initialState = {
isLoading: false,
loggedIn: false,
user: null,
@@ -21,27 +20,34 @@ const initialState = Map({
fromSignUp: false,
requireEmailConfirmation: false,
redirectUri: pym.parentUrl || location.href,
-});
+};
const purge = (user) => {
- const {settings, profiles, ...userData} = user; // eslint-disable-line
- return fromJS(userData);
+ const {settings, ...userData} = user; // eslint-disable-line
+ return userData;
};
export default function auth (state = initialState, action) {
switch (action.type) {
case actions.FOCUS_SIGNIN_DIALOG:
- return state
- .set('signInDialogFocus', true);
+ return {
+ ...state,
+ signInDialogFocus: true,
+ };
case actions.BLUR_SIGNIN_DIALOG:
- return state
- .set('signInDialogFocus', false);
- case actions.SHOW_SIGNIN_DIALOG :
- return state
- .set('showSignInDialog', true)
- .set('signInDialogFocus', true);
+ return {
+ ...state,
+ signInDialogFocus: false,
+ };
+ case actions.SHOW_SIGNIN_DIALOG:
+ return {
+ ...state,
+ showSignInDialog: true,
+ signInDialogFocus: true,
+ };
case actions.HIDE_SIGNIN_DIALOG :
- return state.merge(Map({
+ return {
+ ...state,
isLoading: false,
showSignInDialog: false,
signInDialogFocus: false,
@@ -53,125 +59,198 @@ export default function auth (state = initialState, action) {
emailVerificationSuccess: false,
emailVerificationLoading: false,
successSignUp: false
- }));
- case actions.SHOW_CREATEUSERNAME_DIALOG :
- return state
- .set('showCreateUsernameDialog', true);
- case actions.HIDE_CREATEUSERNAME_DIALOG :
- return state.merge(Map({
- showCreateUsernameDialog: false
- }));
- case actions.CREATE_USERNAME_SUCCESS :
- return state.merge(Map({
+ };
+ case actions.SHOW_CREATEUSERNAME_DIALOG:
+ return {
+ ...state,
+ showCreateUsernameDialog: true,
+ };
+ case actions.HIDE_CREATEUSERNAME_DIALOG:
+ return {
+ ...state,
showCreateUsernameDialog: false,
- error: ''
- }));
- case actions.CREATE_USERNAME_FAILURE :
- return state
- .set('error', action.error);
- case actions.CHANGE_VIEW :
- return state
- .set('error', '')
- .set('view', action.view);
+ };
+ case actions.CREATE_USERNAME_SUCCESS:
+ return {
+ ...state,
+ showCreateUsernameDialog: false,
+ error: '',
+ };
+ case actions.CREATE_USERNAME_FAILURE:
+ return {
+ ...state,
+ error: action.error,
+ };
+ case actions.CHANGE_VIEW:
+ return {
+ ...state,
+ error: action.error,
+ view: action.view,
+ };
case actions.CLEAN_STATE:
return initialState;
case actions.FETCH_SIGNIN_REQUEST:
- return state
- .set('isLoading', true);
+ return {
+ ...state,
+ isLoading: true,
+ };
case actions.CHECK_LOGIN_FAILURE:
- return state
- .set('checkedInitialLogin', true)
- .set('loggedIn', false)
- .set('user', null);
+ return {
+ ...state,
+ checkedInitialLogin: true,
+ loggedIn: false,
+ user: null,
+ };
case actions.CHECK_LOGIN_SUCCESS:
- return state
- .set('checkedInitialLogin', true)
- .set('loggedIn', true)
- .set('user', purge(action.user));
+ return {
+ ...state,
+ checkedInitialLogin: true,
+ loggedIn: true,
+ user: purge(action.user),
+ };
case actions.FETCH_SIGNIN_SUCCESS:
- return state
- .set('loggedIn', true)
- .set('user', purge(action.user));
+ return {
+ ...state,
+ loggedIn: true,
+ user: purge(action.user),
+ };
case actions.FETCH_SIGNIN_FAILURE:
- return state
- .set('isLoading', false)
- .set('error', action.error)
- .set('user', null);
+ return {
+ ...state,
+ isLoading: false,
+ error: action.error,
+ user: null,
+ };
case actions.FETCH_SIGNUP_FACEBOOK_REQUEST:
- return state
- .set('fromSignUp', true);
+ return {
+ ...state,
+ fromSignUp: true,
+ };
case actions.FETCH_SIGNIN_FACEBOOK_REQUEST:
- return state
- .set('fromSignUp', false);
+ return {
+ ...state,
+ fromSignUp: false,
+ };
case actions.FETCH_SIGNIN_FACEBOOK_SUCCESS:
- return state
- .set('user', purge(action.user))
- .set('loggedIn', true);
+ return {
+ ...state,
+ loggedIn: true,
+ user: purge(action.user),
+ };
case actions.FETCH_SIGNIN_FACEBOOK_FAILURE:
- return state
- .set('error', action.error)
- .set('user', null);
+ return {
+ ...state,
+ error: action.error,
+ user: null,
+ };
case actions.FETCH_SIGNUP_REQUEST:
- return state
- .set('isLoading', true);
+ return {
+ ...state,
+ isLoading: true,
+ };
case actions.FETCH_SIGNUP_FAILURE:
- return state
- .set('error', action.error)
- .set('isLoading', false);
+ return {
+ ...state,
+ error: action.error,
+ isLoading: false,
+ };
case actions.FETCH_SIGNUP_SUCCESS:
- return state
- .set('isLoading', false)
- .set('successSignUp', true);
+ return {
+ ...state,
+ isLoading: false,
+ successSignUp: true,
+ };
case actions.LOGOUT:
- return state
- .set('user', null)
- .set('isLoading', false)
- .set('loggedIn', false);
+ return {
+ ...state,
+ user: null,
+ isLoading: false,
+ loggedIn: false,
+ };
case actions.INVALID_FORM:
- return state
- .set('error', action.error);
+ return {
+ ...state,
+ error: action.error,
+ };
case actions.VALID_FORM:
- return state
- .set('error', '');
+ return {
+ ...state,
+ error: '',
+ };
case actions.FETCH_FORGOT_PASSWORD_SUCCESS:
- return state
- .set('passwordRequestFailure', null)
- .set('passwordRequestSuccess', 'If you have a registered account, a password reset link was sent to that email');
+ return {
+ ...state,
+ passwordRequestFailure: null,
+ passwordRequestSuccess: 'If you have a registered account, a password reset link was sent to that email',
+ };
case actions.FETCH_FORGOT_PASSWORD_FAILURE:
- return state
- .set('passwordRequestFailure', 'There was an error sending your password reset email. Please try again soon!')
- .set('passwordRequestSuccess', null);
+ return {
+ ...state,
+ passwordRequestFailure: 'There was an error sending your password reset email. Please try again soon!',
+ passwordRequestSuccess: null,
+ };
case actions.UPDATE_USERNAME:
- return state
- .setIn(['user', 'username'], action.username);
+ return {
+ ...state,
+ user: {
+ ...state.user,
+ username: action.username,
+ }
+ };
case actions.VERIFY_EMAIL_FAILURE:
- return state
- .set('emailVerificationFailure', true)
- .set('emailVerificationLoading', false);
+ return {
+ ...state,
+ emailVerificationFailure: true,
+ emailVerificationLoading: false,
+ };
case actions.VERIFY_EMAIL_REQUEST:
- return state.set('emailVerificationLoading', true);
+ return {
+ ...state,
+ emailVerificationLoading: true,
+ };
case actions.VERIFY_EMAIL_SUCCESS:
- return state
- .set('emailVerificationSuccess', true)
- .set('emailVerificationLoading', false);
+ return {
+ ...state,
+ emailVerificationSuccess: true,
+ emailVerificationLoading: false,
+ };
case actions.SET_REQUIRE_EMAIL_VERIFICATION:
- return state
- .set('requireEmailConfirmation', action.required);
+ return {
+ ...state,
+ requireEmailConfirmation: action.required,
+ };
case actions.SET_REDIRECT_URI:
- return state
- .set('redirectUri', action.uri);
+ return {
+ ...state,
+ redirectUri: action.uri,
+ };
case 'APOLLO_SUBSCRIPTION_RESULT':
if (action.operationName === 'UserBanned' && state.getIn(['user', 'id']) === action.variables.user_id) {
- return state
- .mergeIn(['user'], action.result.data.userBanned);
+ return {
+ ...state,
+ user: {
+ ...state.user,
+ ...action.result.data.userBanned,
+ },
+ };
}
if (action.operationName === 'UserSuspended' && state.getIn(['user', 'id']) === action.variables.user_id) {
- return state
- .mergeIn(['user'], action.result.data.userSuspended);
+ return {
+ ...state,
+ user: {
+ ...state.user,
+ ...action.result.data.userSuspended,
+ },
+ };
}
if (action.operationName === 'UsernameRejected' && state.getIn(['user', 'id']) === action.variables.user_id) {
- return state
- .mergeIn(['user'], action.result.data.usernameRejected);
+ return {
+ ...state,
+ user: {
+ ...state.user,
+ ...action.result.data.usernameRejected,
+ },
+ };
}
return state;
default :
diff --git a/client/coral-framework/reducers/index.js b/client/coral-framework/reducers/index.js
index f1ac580dc..6b9b730ca 100644
--- a/client/coral-framework/reducers/index.js
+++ b/client/coral-framework/reducers/index.js
@@ -1,11 +1,9 @@
import auth from './auth';
-import user from './user';
import asset from './asset';
import {reducer as commentBox} from '../../talk-plugin-commentbox';
export default {
auth,
- user,
asset,
commentBox,
};
diff --git a/client/coral-framework/reducers/user.js b/client/coral-framework/reducers/user.js
deleted file mode 100644
index bc40cf3ae..000000000
--- a/client/coral-framework/reducers/user.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import {Map} from 'immutable';
-import * as authActions from '../constants/auth';
-import * as actions from '../constants/user';
-import * as assetActions from '../constants/assets';
-
-const initialState = Map({
- username: '',
- profiles: [],
- settings: {},
- myComments: [],
- myAssets: [], // the assets from which myComments (above) originated
-});
-
-const purge = (user) => {
- const {_id, created_at, updated_at, __v, roles, ...userData} = user; // eslint-disable-line
- return userData;
-};
-
-export default function user (state = initialState, action) {
- switch (action.type) {
- case authActions.CHECK_LOGIN_SUCCESS:
- return state.merge(Map(purge(action.user)));
- case authActions.CHECK_LOGIN_FAILURE:
- return initialState;
- case authActions.FETCH_SIGNIN_SUCCESS:
- return state.merge(Map(purge(action.user)));
- case authActions.FETCH_SIGNIN_FAILURE:
- return initialState;
- case authActions.FETCH_SIGNIN_FACEBOOK_SUCCESS:
- return state.merge(Map(purge(action.user)));
- case authActions.FETCH_SIGNIN_FACEBOOK_FAILURE:
- return initialState;
- case actions.SAVE_BIO_SUCCESS:
- return state.set('settings', action.settings);
- case actions.COMMENTS_BY_USER_SUCCESS:
- return state.set('myComments', action.comments);
- case assetActions.MULTIPLE_ASSETS_SUCCESS:
- return state.set('myAssets', action.assets);
- case actions.LOGOUT_SUCCESS:
- return initialState;
- }
- return state;
-}
diff --git a/client/coral-settings/containers/ProfileContainer.js b/client/coral-settings/containers/ProfileContainer.js
index 0090847f7..e33c0b6cd 100644
--- a/client/coral-settings/containers/ProfileContainer.js
+++ b/client/coral-settings/containers/ProfileContainer.js
@@ -51,7 +51,7 @@ class ProfileContainer extends Component {
};
render() {
- const {auth, asset, showSignInDialog, stopIgnoringUser} = this.props;
+ const {auth, auth: {user}, asset, showSignInDialog, stopIgnoringUser} = this.props;
const {me} = this.props.root;
const loading = [1, 2, 4].indexOf(this.props.data.networkStatus) >= 0;
@@ -63,14 +63,14 @@ class ProfileContainer extends Component {
return ;
}
- const localProfile = this.props.user.profiles.find(
+ const localProfile = user.profiles.find(
(p) => p.provider === 'local'
);
const emailAddress = localProfile && localProfile.id;
return (
-
{this.props.user.username}
+
{user.username}
{emailAddress ?
{emailAddress}
: null}
{me.ignoredUsers && me.ignoredUsers.length
@@ -138,9 +138,8 @@ const withProfileQuery = withQuery(
`);
const mapStateToProps = (state) => ({
- user: state.user.toJS(),
- asset: state.asset.toJS(),
- auth: state.auth.toJS()
+ asset: state.asset,
+ auth: state.auth
});
const mapDispatchToProps = (dispatch) =>
diff --git a/client/coral-ui/components/Button.css b/client/coral-ui/components/Button.css
index 7bd1d4379..1b7efb205 100644
--- a/client/coral-ui/components/Button.css
+++ b/client/coral-ui/components/Button.css
@@ -27,9 +27,10 @@
}
.icon {
- margin-right: 13px;
+ margin-right: 5px;
font-size: 18px;
vertical-align: middle;
+ margin-top: -3px;
}
.type--black {
@@ -143,7 +144,7 @@
border-radius: 3px;
text-transform: capitalize;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.03), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.09);
- width: 128px;
+ width: 129px;
&:hover {
box-shadow: none;
@@ -166,7 +167,7 @@
border-radius: 3px;
text-transform: capitalize;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.03), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.09);
- width: 128px;
+ width: 129px;
&:hover {
color: white;
diff --git a/config.js b/config.js
index a2335822d..a92802ee7 100644
--- a/config.js
+++ b/config.js
@@ -7,6 +7,8 @@
// entrypoint for the entire applications configuration.
require('env-rewrite').rewrite();
+const uniq = require('lodash/uniq');
+
//==============================================================================
// CONFIG INITIALIZATION
//==============================================================================
@@ -31,6 +33,13 @@ const CONFIG = {
// token.
JWT_COOKIE_NAME: process.env.TALK_JWT_COOKIE_NAME || 'authorization',
+ // JWT_SIGNING_COOKIE_NAME will be the cookie set when cookies are issued.
+ // This defaults to the TALK_JWT_COOKIE_NAME value.
+ JWT_SIGNING_COOKIE_NAME: process.env.TALK_JWT_SIGNING_COOKIE_NAME || process.env.TALK_JWT_COOKIE_NAME || 'authorization',
+
+ // JWT_COOKIE_NAMES declares the many cookie names used for verification.
+ JWT_COOKIE_NAMES: process.env.TALK_JWT_COOKIE_NAMES || null,
+
// JWT_CLEAR_COOKIE_LOGOUT specifies whether the named cookie should be
// cleared when the user is logged out.
JWT_CLEAR_COOKIE_LOGOUT: process.env.TALK_JWT_CLEAR_COOKIE_LOGOUT ? process.env.TALK_JWT_CLEAR_COOKIE_LOGOUT !== 'FALSE' : true,
@@ -173,6 +182,16 @@ if (CONFIG.JWT_DISABLE_ISSUER) {
CONFIG.JWT_ISSUER = undefined;
}
+// Parse and handle cookie names.
+if (CONFIG.JWT_COOKIE_NAMES) {
+ CONFIG.JWT_COOKIE_NAMES = CONFIG.JWT_COOKIE_NAMES.split(',');
+} else {
+ CONFIG.JWT_COOKIE_NAMES = [];
+}
+
+// Add in the default cookie names and strip duplicates.
+CONFIG.JWT_COOKIE_NAMES = uniq(CONFIG.JWT_COOKIE_NAMES.concat([CONFIG.JWT_COOKIE_NAME, CONFIG.JWT_SIGNING_COOKIE_NAME]));
+
//------------------------------------------------------------------------------
// External database url's
//------------------------------------------------------------------------------
diff --git a/docs/_docs/02-01-configuration.md b/docs/_docs/02-01-configuration.md
index ac614fdb1..8f76ad999 100644
--- a/docs/_docs/02-01-configuration.md
+++ b/docs/_docs/02-01-configuration.md
@@ -94,8 +94,16 @@ on the contents of those variables.**
These are advanced settings for fine tuning the auth integration, and
is not needed in most situations.
-- `TALK_JWT_COOKIE_NAME` (_optional_) - the name of the cookie to extract the
- JWT from (Default `authorization`)
+- `TALK_JWT_COOKIE_NAME` (_optional_) - the default cookie name to check for a
+ valid JWT token to use for verifying a user. (Default `authorization`)
+- `TALK_JWT_SIGNING_COOKIE_NAME` (_optional_) - the default cookie name that is
+ use to set a cookie containing a JWT that was issued by Talk.
+ (Default `process.env.TALK_JWT_COOKIE_NAME`)
+- `TALK_JWT_COOKIE_NAMES` (_optional_) - the different cookie names to check for
+ a JWT token in, seperated by `,`. By default, we always use the
+ `process.env.TALK_JWT_COOKIE_NAME` and `process.env.TALK_JWT_SIGNING_COOKIE_NAME`
+ for this value. Any additional cookie names specified here will be appended to
+ the list of cookie names to inspect.
- `TALK_JWT_CLEAR_COOKIE_LOGOUT` (_optional_) - when `FALSE`, Talk will not
clear the cookie with name `TALK_JWT_COOKIE_NAME` when logging out (Default
`TRUE`)
@@ -122,6 +130,14 @@ will be used:
}
```
+When our passport middleware checks for JWT tokens, it searches in the following
+order:
+
+1. Custom cookies named from the list in `TALK_JWT_COOKIE_NAMES`.
+2. Default cookies named `TALK_JWT_COOKIE_NAME` then `TALK_JWT_SIGNING_COOKIE_NAME`.
+3. Query parameter `?access_token={TOKEN}`.
+4. Header: `Authorization: Bearer {TOKEN}`.
+
### Email
- `TALK_SMTP_EMAIL` (*required for email*) - the address to send emails from
diff --git a/docs/_docs/03-05-client-architecture.md b/docs/_docs/03-05-client-architecture.md
index 6392c20d0..f102573f2 100644
--- a/docs/_docs/03-05-client-architecture.md
+++ b/docs/_docs/03-05-client-architecture.md
@@ -6,7 +6,6 @@ permalink: /docs/architecture/client
## The Stack
- [React](#react)
- [Redux](#redux)
- - [ImmutableJS](#immutablejs)
## The Architecture
@@ -96,14 +95,6 @@ We use [Apollo](http://www.apollodata.com/) to handle graph requests and handle
## Redux
We use [Redux](http://redux.js.org/) to handle the auth state.
-
-## ImmutableJS
-We use Immutable JS to maintain our state immutable.
-We found some really good tradeoffs while building Talk.
-
-[How to use ImmutableJS and how we use it with Talk](https://facebook.github.io/immutable-js/docs/#/)
-
-
## Test
[How we do testing at Coral with Talk]({{ "/docs/development/tools" | absolute_url }})
diff --git a/package.json b/package.json
index 476e7eda5..c6b8a782d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "talk",
- "version": "3.0.0",
+ "version": "3.1.0",
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
"main": "app.js",
"scripts": {
@@ -93,7 +93,7 @@
"graphql-tools": "^0.10.1",
"helmet": "^3.5.0",
"immutability-helper": "^2.2.0",
- "inquirer": "^3.0.6",
+ "inquirer": "^3.2.1",
"joi": "^10.4.1",
"jsonwebtoken": "^7.3.0",
"jwt-decode": "^2.2.0",
@@ -116,7 +116,7 @@
"passport-local": "^1.0.0",
"prop-types": "^15.5.10",
"query-strings": "^0.0.1",
- "react-apollo": "^1.1.0",
+ "react-apollo": "^1.4.12",
"react-input-autosize": "^1.1.4",
"react-recaptcha": "^2.2.6",
"react-toastify": "^1.5.0",
@@ -136,7 +136,7 @@
"yamljs": "^0.2.10"
},
"devDependencies": {
- "apollo-client": "^1.0.4",
+ "apollo-client": "^1.9.1",
"autoprefixer": "^6.5.2",
"babel-cli": "^6.24.0",
"babel-core": "^6.24.0",
@@ -180,7 +180,6 @@
"hammerjs": "^2.0.8",
"history": "^3.0.0",
"ignore-styles": "^5.0.1",
- "immutable": "^3.8.1",
"imports-loader": "^0.7.1",
"istanbul": "^1.1.0-alpha.1",
"jsdom": "^9.8.3",
diff --git a/plugin-api/beta/client/hocs/withReaction.js b/plugin-api/beta/client/hocs/withReaction.js
index 7f43cc5b2..22c30d639 100644
--- a/plugin-api/beta/client/hocs/withReaction.js
+++ b/plugin-api/beta/client/hocs/withReaction.js
@@ -363,7 +363,7 @@ export default (reaction) => (WrappedComponent) => {
);
const mapStateToProps = (state) => ({
- user: state.auth.toJS().user,
+ user: state.auth.user,
});
const mapDispatchToProps = (dispatch) =>
diff --git a/plugin-api/beta/client/hocs/withTags.js b/plugin-api/beta/client/hocs/withTags.js
index e9b5bdff0..b0a74f4d7 100644
--- a/plugin-api/beta/client/hocs/withTags.js
+++ b/plugin-api/beta/client/hocs/withTags.js
@@ -84,7 +84,7 @@ export default (tag) => (WrappedComponent) => {
}
const mapStateToProps = (state) => ({
- user: state.auth.toJS().user,
+ user: state.auth.user,
});
const mapDispatchToProps = (dispatch) =>
diff --git a/plugins/talk-plugin-auth/client/components/ChangeUsername.js b/plugins/talk-plugin-auth/client/components/ChangeUsername.js
index ed3c5ace0..c1ad081b1 100644
--- a/plugins/talk-plugin-auth/client/components/ChangeUsername.js
+++ b/plugins/talk-plugin-auth/client/components/ChangeUsername.js
@@ -122,7 +122,7 @@ class ChangeUsernameContainer extends React.Component {
}
const mapStateToProps = ({auth}) => ({
- auth: auth.toJS()
+ auth: auth
});
const mapDispatchToProps = (dispatch) =>
diff --git a/plugins/talk-plugin-auth/client/components/CreateUsernameDialog.js b/plugins/talk-plugin-auth/client/components/CreateUsernameDialog.js
index 12916b4dd..4b0babed6 100644
--- a/plugins/talk-plugin-auth/client/components/CreateUsernameDialog.js
+++ b/plugins/talk-plugin-auth/client/components/CreateUsernameDialog.js
@@ -32,7 +32,7 @@ const CreateUsernameDialog = ({
className={styles.fakeComment}
username={formData.username}
created_at={Date.now()}
- comment={{body:t('createdisplay.fake_comment_body')}}
+ body={t('createdisplay.fake_comment_body')}
/>
{t('createdisplay.if_you_dont_change_your_name')}
diff --git a/plugins/talk-plugin-auth/client/components/FakeComment.css b/plugins/talk-plugin-auth/client/components/FakeComment.css
new file mode 100644
index 000000000..8b5521aea
--- /dev/null
+++ b/plugins/talk-plugin-auth/client/components/FakeComment.css
@@ -0,0 +1,37 @@
+.root {
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ margin-bottom: 10px;
+ padding: 8px 0px 10px 0px;
+ position: relative;
+}
+
+.body {
+
+}
+
+.footer {
+ display: flex;
+ justify-content: space-between;
+}
+
+.button {
+ color: #2a2a2a;
+ margin: 5px 10px 5px 0px;
+ background: none;
+ padding: 0px;
+ border: none;
+ font-size: inherit;
+ vertical-align: middle;
+
+ &:hover {
+ color: #767676;
+ cursor: pointer;
+ }
+}
+
+.icon {
+ font-size: 12px;
+ padding: 0 2px 0 5px;
+ vertical-align: middle;
+}
diff --git a/plugins/talk-plugin-auth/client/components/FakeComment.js b/plugins/talk-plugin-auth/client/components/FakeComment.js
index b6b4e7609..eefada587 100644
--- a/plugins/talk-plugin-auth/client/components/FakeComment.js
+++ b/plugins/talk-plugin-auth/client/components/FakeComment.js
@@ -2,65 +2,43 @@ import React from 'react';
import t from 'coral-framework/services/i18n';
import {ReplyButton} from 'talk-plugin-replies';
import PubDate from 'talk-plugin-pubdate/PubDate';
-import Slot from 'coral-framework/components/Slot';
import AuthorName from 'talk-plugin-author-name/AuthorName';
-import styles from 'coral-embed-stream/src/components/Comment.css';
+import styles from './FakeComment.css';
+import {Icon} from 'plugin-api/beta/client/components/ui';
-export const FakeComment = ({username, created_at, comment}) => (
-
-
-
+export const FakeComment = ({username, created_at, body}) => (
+
+
-
-
-
-