mirror of
https://github.com/wassname/talk.git
synced 2026-07-12 03:01:41 +08:00
Merge branch 'master' into base-path
This commit is contained in:
+32
-6
@@ -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: {
|
||||
|
||||
@@ -37,27 +37,11 @@ const routes = (
|
||||
<Route path='moderate' component={ModerationLayout}>
|
||||
<IndexRoute components={Moderation} />
|
||||
|
||||
<Route path='all' components={Moderation}>
|
||||
<Route path=':id' components={Moderation} />
|
||||
</Route>
|
||||
<Route path='new' components={Moderation}>
|
||||
<Route path=':id' components={Moderation} />
|
||||
</Route>
|
||||
<Route path='approved' components={Moderation}>
|
||||
<Route path=':id' components={Moderation} />
|
||||
</Route>
|
||||
<Route path='premod' components={Moderation}>
|
||||
<Route path=':id' components={Moderation} />
|
||||
</Route>
|
||||
<Route path='rejected' components={Moderation}>
|
||||
<Route path=':id' components={Moderation} />
|
||||
</Route>
|
||||
<Route path='reported' components={Moderation}>
|
||||
<Route path=':id' components={Moderation} />
|
||||
</Route>
|
||||
|
||||
<Route path=':id' components={Moderation} />
|
||||
<Route path=':tabOrId' components={Moderation} />
|
||||
|
||||
<Route path=':tab' components={Moderation}>
|
||||
<Route path=':id' components={Moderation} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
</div>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.flagBox {
|
||||
border-top: 1px solid rgba(66, 66, 66, 0.12);
|
||||
|
||||
margin-top: 10px;
|
||||
.container {
|
||||
padding: 0 14px;
|
||||
}
|
||||
|
||||
@@ -186,7 +186,6 @@
|
||||
.actionButton {
|
||||
transform: scale(.8);
|
||||
margin: 0;
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.minimal {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) => ({
|
||||
|
||||
@@ -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) => ({
|
||||
|
||||
@@ -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()));
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -23,7 +23,7 @@ class TableContainer extends Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
commenters: state.community.get('accounts'),
|
||||
commenters: state.community.accounts,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -17,11 +17,11 @@ const ActivityWidget = ({assets}) => {
|
||||
? assets.map((asset) => {
|
||||
return (
|
||||
<div className={styles.rowLinkify} key={asset.id}>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/${asset.id}`}>Moderate</Link>
|
||||
<p className={styles.widgetCount}>{asset.commentCount}</p>
|
||||
<Link className={styles.linkToAsset} to={`${asset.url}#coralStreamEmbed_iframe`} target="_blank">
|
||||
<a className={styles.linkToAsset} href={`${asset.url}`} target="_blank">
|
||||
<p className={styles.assetTitle}>{asset.title}</p>
|
||||
</Link>
|
||||
</a>
|
||||
<p className={styles.lede}>{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -24,11 +24,11 @@ const FlagWidget = ({assets}) => {
|
||||
|
||||
return (
|
||||
<div className={styles.rowLinkify} key={asset.id}>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/reported/${asset.id}`}>Moderate</Link>
|
||||
<p className={styles.widgetCount}>{flagSummary ? flagSummary.actionCount : 0}</p>
|
||||
<Link className={styles.linkToAsset} to={`${asset.url}#coralStreamEmbed_iframe`} target="_blank">
|
||||
<a className={styles.linkToAsset} href={`${asset.url}`} target="_blank">
|
||||
<p className={styles.assetTitle}>{asset.title}</p>
|
||||
</Link>
|
||||
</a>
|
||||
<p className={styles.lede}>{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,11 +19,11 @@ const LikeWidget = ({assets}) => {
|
||||
const likeSummary = asset.action_summaries.find((s) => s.type === 'LikeAssetActionSummary');
|
||||
return (
|
||||
<div className={styles.rowLinkify} key={asset.id}>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/flagged/${asset.id}`}>Moderate</Link>
|
||||
<Link className={styles.linkToModerate} to={`/admin/moderate/${asset.id}`}>Moderate</Link>
|
||||
<p className={styles.widgetCount}>{likeSummary ? likeSummary.actionCount : 0}</p>
|
||||
<Link className={styles.linkToAsset} to={`${asset.url}#coralStreamEmbed_iframe`} target="_blank">
|
||||
<a className={styles.linkToAsset} href={`${asset.url}`} target="_blank">
|
||||
<p className={styles.assetTitle}>{asset.title}</p>
|
||||
</Link>
|
||||
</a>
|
||||
<p className={styles.lede}>{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ InstallContainer.contextTypes = {
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
install: state.install.toJS()
|
||||
install: state.install
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
@@ -139,16 +125,10 @@ export default class Moderation extends Component {
|
||||
/>
|
||||
<ModerationMenu
|
||||
asset={asset}
|
||||
allCount={root.allCount}
|
||||
newCount={root.newCount}
|
||||
getModPath={getModPath}
|
||||
approvedCount={root.approvedCount}
|
||||
premodCount={root.premodCount}
|
||||
rejectedCount={root.rejectedCount}
|
||||
reportedCount={root.reportedCount}
|
||||
items={menuItems}
|
||||
selectSort={this.props.setSortOrder}
|
||||
sort={this.props.moderation.sortOrder}
|
||||
premodEnabled={premodEnabled}
|
||||
activeTab={activeTab}
|
||||
/>
|
||||
<ModerationQueue
|
||||
@@ -191,6 +171,7 @@ export default class Moderation extends Component {
|
||||
root={root}
|
||||
assset={asset}
|
||||
activeTab={activeTab}
|
||||
handleCommentChange={handleCommentChange}
|
||||
fill='adminModeration'
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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 = ({
|
||||
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
|
||||
<div className={styles.tabBarPadding} />
|
||||
<div>
|
||||
|
||||
{
|
||||
premodEnabled ? (
|
||||
<Link
|
||||
to={getModPath('premod', asset.id)}
|
||||
className={cn('mdl-tabs__tab', styles.tab, {[styles.active]: activeTab === 'premod'})}
|
||||
activeClassName={styles.active}>
|
||||
<Icon name='access_time' className={styles.tabIcon} /> {t('modqueue.premod')} <CommentCount count={premodCount} />
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
to={getModPath('new', asset.id)}
|
||||
className={cn('mdl-tabs__tab', styles.tab, {[styles.active]: activeTab === 'new'})}
|
||||
activeClassName={styles.active}>
|
||||
<Icon name='question_answer' className={styles.tabIcon} /> {t('modqueue.new')} <CommentCount count={newCount} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
<Link
|
||||
to={getModPath('reported', asset.id)}
|
||||
className={cn('mdl-tabs__tab', styles.tab, {[styles.active]: activeTab === 'reported'})}
|
||||
activeClassName={styles.active}>
|
||||
<Icon name='flag' className={styles.tabIcon} /> {t('modqueue.reported')} <CommentCount count={reportedCount} />
|
||||
</Link>
|
||||
<Link
|
||||
to={getModPath('approved', asset.id)}
|
||||
className={cn('mdl-tabs__tab', styles.tab, {[styles.active]: activeTab === 'approved'})}
|
||||
activeClassName={styles.active}>
|
||||
<Icon name='check' className={styles.tabIcon} /> {t('modqueue.approved')} <CommentCount count={approvedCount} />
|
||||
</Link>
|
||||
<Link
|
||||
to={getModPath('rejected', asset.id)}
|
||||
className={cn('mdl-tabs__tab', styles.tab, {[styles.active]: activeTab === 'rejected'})}
|
||||
activeClassName={styles.active}>
|
||||
<Icon name='close' className={styles.tabIcon} /> {t('modqueue.rejected')} <CommentCount count={rejectedCount} />
|
||||
</Link>
|
||||
<Link
|
||||
to={getModPath('all', asset.id)}
|
||||
className={cn('mdl-tabs__tab', styles.tab, {[styles.active]: activeTab === 'all'})}
|
||||
activeClassName={styles.active}>
|
||||
<Icon name='question_answer' className={styles.tabIcon} /> {t('modqueue.all')} <CommentCount count={allCount} />
|
||||
</Link>
|
||||
{items.map((queue) =>
|
||||
<Link
|
||||
key={queue.key}
|
||||
to={getModPath(queue.key, asset.id)}
|
||||
className={cn('mdl-tabs__tab', styles.tab, {[styles.active]: activeTab === queue.key})}
|
||||
activeClassName={styles.active}>
|
||||
<Icon name={queue.icon} className={styles.tabIcon} /> {queue.name} <CommentCount count={queue.count} />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<SelectField
|
||||
className={styles.selectField}
|
||||
@@ -85,10 +45,7 @@ const ModerationMenu = ({
|
||||
};
|
||||
|
||||
ModerationMenu.propTypes = {
|
||||
allCount: PropTypes.number.isRequired,
|
||||
premodCount: PropTypes.number.isRequired,
|
||||
rejectedCount: PropTypes.number.isRequired,
|
||||
reportedCount: PropTypes.number.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
asset: PropTypes.shape({
|
||||
id: PropTypes.string
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
color: #C0C0C0;
|
||||
color: #BDBDBD;
|
||||
text-transform: capitalize;
|
||||
font-weight: 100;
|
||||
font-size: 14px;
|
||||
@@ -29,7 +29,7 @@
|
||||
margin-right: 20px;
|
||||
&:hover {
|
||||
color: white;
|
||||
border-bottom: solid 2px #F36451;
|
||||
/*border-bottom: solid 2px #F36451;*/
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ span {
|
||||
color: white;
|
||||
text-transform: capitalize;
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
font-size: 20px;
|
||||
letter-spacing: 1px;
|
||||
transition: background-color 200ms;
|
||||
opacity: 1;
|
||||
@@ -173,7 +173,7 @@ span {
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
font-size: 18px;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
max-width: 650px;
|
||||
min-width: 400px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
@@ -470,7 +470,7 @@ span {
|
||||
|
||||
.searchTrigger {
|
||||
position: relative;
|
||||
top: .3em;
|
||||
top: .2em;
|
||||
}
|
||||
|
||||
.adminCommentInfoBar {
|
||||
|
||||
@@ -11,7 +11,7 @@ import NotFoundAsset from '../components/NotFoundAsset';
|
||||
import {isPremod, getModPath} from '../../../utils';
|
||||
|
||||
import {withSetCommentStatus} from 'coral-framework/graphql/mutations';
|
||||
import {handleCommentChange} from '../../../graphql/utils';
|
||||
import {handleCommentChange} from '../graphql';
|
||||
|
||||
import {fetchSettings} from 'actions/settings';
|
||||
import {showBanUserDialog} from 'actions/banUserDialog';
|
||||
@@ -30,23 +30,44 @@ import {
|
||||
import {Spinner} from 'coral-ui';
|
||||
import Moderation from '../components/Moderation';
|
||||
import Comment from './Comment';
|
||||
import queueConfig from '../queueConfig';
|
||||
|
||||
function prepareNotificationText(text) {
|
||||
return truncate(text, {length: 50}).replace('\n', ' ');
|
||||
}
|
||||
|
||||
function getAssetId(props) {
|
||||
if (props.params.tabOrId && !(props.params.tabOrId in queueConfig)) {
|
||||
return props.params.tabOrId;
|
||||
}
|
||||
return props.params.id || null;
|
||||
}
|
||||
|
||||
function getTab(props) {
|
||||
if (props.params.tabOrId && props.params.tabOrId in queueConfig) {
|
||||
return props.params.tabOrId;
|
||||
}
|
||||
return props.params.tab || null;
|
||||
}
|
||||
|
||||
class ModerationContainer extends Component {
|
||||
subscriptions = [];
|
||||
|
||||
handleCommentChange = (root, comment, notify) => {
|
||||
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 <div>Error</div>;
|
||||
@@ -222,6 +205,14 @@ class ModerationContainer extends Component {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
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 <Moderation
|
||||
{...this.props}
|
||||
getModPath={getModPath}
|
||||
@@ -229,7 +220,8 @@ class ModerationContainer extends Component {
|
||||
acceptComment={this.acceptComment}
|
||||
rejectComment={this.rejectComment}
|
||||
activeTab={this.activeTab}
|
||||
premodEnabled={assetId ? isPremod(asset.settings.moderation) : isPremod(settings.moderation)}
|
||||
queueConfig={currentQueueConfig}
|
||||
handleCommentChange={this.handleCommentChange}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
@@ -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) => ({
|
||||
|
||||
+24
-33
@@ -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);
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
@@ -12,7 +12,7 @@ class StoriesContainer extends Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
assets: state.assets.toJS()
|
||||
assets: state.assets
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -119,7 +119,7 @@ class ConfigureStreamContainer extends Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
asset: state.asset.toJS()
|
||||
asset: state.asset
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
};
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 :
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 <Spinner />;
|
||||
}
|
||||
|
||||
const localProfile = this.props.user.profiles.find(
|
||||
const localProfile = user.profiles.find(
|
||||
(p) => p.provider === 'local'
|
||||
);
|
||||
const emailAddress = localProfile && localProfile.id;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{this.props.user.username}</h2>
|
||||
<h2>{user.username}</h2>
|
||||
{emailAddress ? <p>{emailAddress}</p> : 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) =>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }})
|
||||
|
||||
|
||||
+4
-5
@@ -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",
|
||||
|
||||
@@ -363,7 +363,7 @@ export default (reaction) => (WrappedComponent) => {
|
||||
);
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
user: state.auth.toJS().user,
|
||||
user: state.auth.user,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -84,7 +84,7 @@ export default (tag) => (WrappedComponent) => {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
user: state.auth.toJS().user,
|
||||
user: state.auth.user,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -122,7 +122,7 @@ class ChangeUsernameContainer extends React.Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = ({auth}) => ({
|
||||
auth: auth.toJS()
|
||||
auth: auth
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -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')}
|
||||
/>
|
||||
<p className={styles.ifyoudont}>
|
||||
{t('createdisplay.if_you_dont_change_your_name')}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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}) => (
|
||||
<div className={`comment ${styles.Comment}`} style={{marginLeft: 0 * 30}}>
|
||||
<hr aria-hidden={true} />
|
||||
<AuthorName author={{name: username}} />
|
||||
export const FakeComment = ({username, created_at, body}) => (
|
||||
<div className={styles.root}>
|
||||
<AuthorName author={{username}} />
|
||||
<PubDate created_at={created_at} />
|
||||
<Slot comment={comment} />
|
||||
<div className="commentActionsLeft comment__action-container">
|
||||
<div className={`${'talk-plugin-likes'}-container`}>
|
||||
<button className={'talk-plugin-likes-button'}>
|
||||
<span className={'talk-plugin-likes-button-text'}>
|
||||
<div className={styles.body}>
|
||||
{body}
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<div>
|
||||
<button className={styles.button}>
|
||||
<span className={styles.label}>
|
||||
{t('like')}
|
||||
</span>
|
||||
<i
|
||||
className={`${'talk-plugin-likes'}-icon material-icons`}
|
||||
aria-hidden={true}
|
||||
>
|
||||
thumb_up
|
||||
</i>
|
||||
<Icon name="thumb_up" className={styles.icon} />
|
||||
</button>
|
||||
<ReplyButton
|
||||
onClick={() => {}}
|
||||
parentCommentId={'commentID'}
|
||||
currentUserId={{}}
|
||||
/>
|
||||
</div>
|
||||
<ReplyButton
|
||||
onClick={() => {}}
|
||||
parentCommentId={'commentID'}
|
||||
currentUserId={{}}
|
||||
/>
|
||||
</div>
|
||||
<div className="commentActionsRight comment__action-container">
|
||||
<div className="talk-plugin-permalinks-container">
|
||||
<button className="talk-plugin-permalinks-button">
|
||||
<span
|
||||
className={`comment__action-button comment__action-button--nowrap ${'talk-plugin-flags'}-button-text`}
|
||||
>
|
||||
<div>
|
||||
<button className={styles.button}>
|
||||
<span className={styles.label}>
|
||||
{t('permalink')}
|
||||
</span>
|
||||
<i
|
||||
className="talk-plugin-permalinks-icon material-icons"
|
||||
aria-hidden={true}
|
||||
>
|
||||
link
|
||||
</i>
|
||||
<Icon name="link" className={styles.icon} />
|
||||
</button>
|
||||
</div>
|
||||
<div className={`${'talk-plugin-flags'}-container`}>
|
||||
<button className={`${'talk-plugin-flags'}-button`}>
|
||||
<span
|
||||
className={`comment__action-button comment__action-button--nowrap ${'talk-plugin-flags'}-button-text`}
|
||||
>
|
||||
<button className={styles.button}>
|
||||
<span className={styles.label}>
|
||||
{t('report')}
|
||||
</span>
|
||||
<i
|
||||
className={`${'talk-plugin-flags'}-icon material-icons`}
|
||||
aria-hidden={true}
|
||||
>
|
||||
flag
|
||||
</i>
|
||||
<Icon name="flag" className={styles.icon} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ const SignInButton = ({loggedIn, showSignInDialog}) => (
|
||||
);
|
||||
|
||||
const mapStateToProps = ({auth}) => ({
|
||||
loggedIn: auth.toJS().loggedIn
|
||||
loggedIn: auth.loggedIn
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -176,7 +176,7 @@ class SignInContainer extends React.Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth.toJS()
|
||||
auth: state.auth
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -22,8 +22,8 @@ const UserBox = ({loggedIn, user, logout, onShowProfile}) => (
|
||||
);
|
||||
|
||||
const mapStateToProps = ({auth}) => ({
|
||||
loggedIn: auth.toJS().loggedIn,
|
||||
user: auth.toJS().user
|
||||
loggedIn: auth.loggedIn,
|
||||
user: auth.user
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
color: #696969;
|
||||
background-color: white;
|
||||
box-sizing: border-box;
|
||||
padding: 2px 8px;
|
||||
padding: 0px 5px;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
height: 28px;
|
||||
height: 24px;
|
||||
transition: background-color .2s cubic-bezier(.4,0,.2,1), color .2s cubic-bezier(.4,0,.2,1), border-color .2s cubic-bezier(.4,0,.2,1);
|
||||
margin: 2px 0px;
|
||||
letter-spacing: 0.4px;
|
||||
|
||||
}
|
||||
|
||||
.tag:hover {
|
||||
@@ -39,4 +40,3 @@
|
||||
font-size: 15px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import {gql} from 'react-apollo';
|
||||
import {connect} from 'react-redux';
|
||||
import Comment from 'coral-admin/src/routes/Moderation/containers/Comment';
|
||||
import {handleCommentChange} from 'coral-admin/src/graphql/utils';
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
import truncate from 'lodash/truncate';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
@@ -22,20 +21,14 @@ class ModSubscription extends React.Component {
|
||||
assetId: this.props.data.variables.asset_id,
|
||||
},
|
||||
updateQuery: (prev, {subscriptionData: {data: {commentFeatured: {user, comment}}}}) => {
|
||||
const sort = this.props.data.variables.sort;
|
||||
const text = this.props.user.id === user.id
|
||||
? {}
|
||||
const notify = this.props.user.id === user.id
|
||||
? ''
|
||||
: t(
|
||||
'talk-plugin-featured-comments.notify_featured',
|
||||
user.username,
|
||||
prepareNotificationText(comment.body),
|
||||
);
|
||||
const notify = {
|
||||
activeQueue: this.props.activeTab,
|
||||
text,
|
||||
anyQueue: true,
|
||||
};
|
||||
return handleCommentChange(prev, comment, sort, notify);
|
||||
return this.props.handleCommentChange(prev, comment, notify);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -44,20 +37,14 @@ class ModSubscription extends React.Component {
|
||||
assetId: this.props.data.variables.asset_id,
|
||||
},
|
||||
updateQuery: (prev, {subscriptionData: {data: {commentUnfeatured: {user, comment}}}}) => {
|
||||
const sort = this.props.data.variables.sort;
|
||||
const text = this.props.user.id === user.id
|
||||
? {}
|
||||
const notify = this.props.user.id === user.id
|
||||
? ''
|
||||
: t(
|
||||
'talk-plugin-featured-comments.notify_unfeatured',
|
||||
user.username,
|
||||
prepareNotificationText(comment.body),
|
||||
);
|
||||
const notify = {
|
||||
activeQueue: this.props.activeTab,
|
||||
text,
|
||||
anyQueue: true,
|
||||
};
|
||||
return handleCommentChange(prev, comment, sort, notify);
|
||||
return this.props.handleCommentChange(prev, comment, notify);
|
||||
}
|
||||
},
|
||||
];
|
||||
@@ -104,7 +91,7 @@ const COMMENT_UNFEATURED_SUBSCRIPTION = gql`
|
||||
`;
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
user: state.auth.toJS().user,
|
||||
user: state.auth.user,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(ModSubscription);
|
||||
|
||||
@@ -26,6 +26,9 @@ export default {
|
||||
IgnoreUser: ({variables}) => ({
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Embed: (previous) => {
|
||||
if (!previous.asset.featuredComments) {
|
||||
return previous;
|
||||
}
|
||||
const ignoredUserId = variables.id;
|
||||
const newNodes = previous.asset.featuredComments.nodes.filter((n) => n.user.id !== ignoredUserId);
|
||||
const removedCount = previous.asset.featuredComments.nodes.length - newNodes.length;
|
||||
|
||||
@@ -16,7 +16,6 @@ export default class OffTopicFilter extends React.Component {
|
||||
this.props.removeCommentClassName(idx);
|
||||
this.props.toggleCheckbox();
|
||||
}
|
||||
this.props.closeViewingOptions();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -3,9 +3,6 @@ import {bindActionCreators} from 'redux';
|
||||
import {toggleCheckbox} from '../actions';
|
||||
import {commentClassNamesSelector} from 'plugin-api/alpha/client/selectors';
|
||||
import OffTopicFilter from '../components/OffTopicFilter';
|
||||
import {
|
||||
closeViewingOptions
|
||||
} from 'plugins/talk-plugin-viewing-options/client/actions';
|
||||
import {
|
||||
addCommentClassName,
|
||||
removeCommentClassName
|
||||
@@ -20,7 +17,6 @@ const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators(
|
||||
{
|
||||
toggleCheckbox,
|
||||
closeViewingOptions,
|
||||
addCommentClassName,
|
||||
removeCommentClassName
|
||||
},
|
||||
|
||||
+15
-8
@@ -23,7 +23,8 @@ const {
|
||||
JWT_ALG,
|
||||
RECAPTCHA_SECRET,
|
||||
RECAPTCHA_ENABLED,
|
||||
JWT_COOKIE_NAME,
|
||||
JWT_SIGNING_COOKIE_NAME,
|
||||
JWT_COOKIE_NAMES,
|
||||
JWT_CLEAR_COOKIE_LOGOUT,
|
||||
JWT_USER_ID_CLAIM,
|
||||
} = require('../config');
|
||||
@@ -53,7 +54,7 @@ const GenerateToken = (user) => {
|
||||
const SetTokenForSafari = (req, res, token) => {
|
||||
const browser = bowser._detect(req.headers['user-agent']);
|
||||
if (browser.ios || browser.safari) {
|
||||
res.cookie(JWT_COOKIE_NAME, token, {
|
||||
res.cookie(JWT_SIGNING_COOKIE_NAME, token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
expires: new Date(Date.now() + ms(JWT_EXPIRY))
|
||||
@@ -169,7 +170,7 @@ const HandleLogout = (req, res, next) => {
|
||||
|
||||
// Only clear the cookie on logout if enabled.
|
||||
if (JWT_CLEAR_COOKIE_LOGOUT) {
|
||||
res.clearCookie(JWT_COOKIE_NAME);
|
||||
res.clearCookie(JWT_SIGNING_COOKIE_NAME);
|
||||
}
|
||||
|
||||
res.status(204).end();
|
||||
@@ -209,14 +210,20 @@ const CheckBlacklisted = async (jwt) => {
|
||||
const JwtStrategy = require('passport-jwt').Strategy;
|
||||
const ExtractJwt = require('passport-jwt').ExtractJwt;
|
||||
|
||||
let cookieExtractor = function(req) {
|
||||
let token = null;
|
||||
|
||||
let cookieExtractor = (req) => {
|
||||
if (req && req.cookies) {
|
||||
token = req.cookies[JWT_COOKIE_NAME];
|
||||
|
||||
// Walk over all the cookie names in JWT_COOKIE_NAMES.
|
||||
for (const cookieName of JWT_COOKIE_NAMES) {
|
||||
|
||||
// Check to see if that cookie is set.
|
||||
if (cookieName in req.cookies && req.cookies[cookieName] !== null && req.cookies[cookieName].length > 0) {
|
||||
return req.cookies[cookieName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
return null;
|
||||
};
|
||||
|
||||
// Override the JwtVerifier method on the JwtStrategy so we can pack the
|
||||
|
||||
+2
-2
@@ -205,8 +205,8 @@ class TagsService {
|
||||
return updateModel(item_type, query, {
|
||||
$pull: {
|
||||
tags: {
|
||||
name: link.tag.name
|
||||
}
|
||||
'tag.name': link.tag.name,
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('services.TagsService', () => {
|
||||
const id = comment.id;
|
||||
const name = 'BEST';
|
||||
const assigned_by = user.id;
|
||||
|
||||
|
||||
await TagsService.add(id, 'COMMENTS', {
|
||||
tag: {
|
||||
name
|
||||
@@ -45,7 +45,7 @@ describe('services.TagsService', () => {
|
||||
const id = comment.id;
|
||||
const name = 'BEST';
|
||||
const assigned_by = user.id;
|
||||
|
||||
|
||||
await TagsService.add(id, 'COMMENTS', {
|
||||
tag: {
|
||||
name
|
||||
@@ -103,5 +103,43 @@ describe('services.TagsService', () => {
|
||||
expect(tags.length).to.equal(0);
|
||||
}
|
||||
});
|
||||
it('removes a tag out of 2', async () => {
|
||||
const id = comment.id;
|
||||
const name = 'BEST';
|
||||
const assigned_by = user.id;
|
||||
|
||||
await TagsService.add(id, 'COMMENTS', {
|
||||
tag: {
|
||||
name: 'ANOTHER'
|
||||
},
|
||||
assigned_by
|
||||
});
|
||||
|
||||
await TagsService.add(id, 'COMMENTS', {
|
||||
tag: {
|
||||
name
|
||||
},
|
||||
assigned_by
|
||||
});
|
||||
|
||||
{
|
||||
const {tags} = await CommentsService.findById(id);
|
||||
expect(tags.length).to.equal(2);
|
||||
}
|
||||
|
||||
// ok now to remove it
|
||||
await TagsService.remove(id, 'COMMENTS', {
|
||||
tag: {
|
||||
name
|
||||
},
|
||||
assigned_by
|
||||
});
|
||||
|
||||
{
|
||||
const {tags} = await CommentsService.findById(id);
|
||||
expect(tags.length).to.equal(1);
|
||||
expect(tags[0].tag.name).to.equal('ANOTHER');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
git-url-parse "^6.0.2"
|
||||
shelljs "^0.7.0"
|
||||
|
||||
"@types/async@^2.0.31":
|
||||
version "2.0.40"
|
||||
resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.40.tgz#ac02de68e66c004a61b7cb16df8b1db3a254cca9"
|
||||
|
||||
"@types/express-serve-static-core@*":
|
||||
version "4.0.44"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.0.44.tgz#a1c3bd5d80e93c72fba91a03f5412c47f21d4ae7"
|
||||
@@ -26,18 +22,18 @@
|
||||
"@types/express-serve-static-core" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/graphql@0.10.2":
|
||||
version "0.10.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.10.2.tgz#d7c79acbaa17453b6681c80c34b38fcb10c4c08c"
|
||||
|
||||
"@types/graphql@^0.8.5", "@types/graphql@^0.8.6":
|
||||
version "0.8.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.8.6.tgz#b34fb880493ba835b0c067024ee70130d6f9bb68"
|
||||
|
||||
"@types/graphql@^0.9.0", "@types/graphql@^0.9.1":
|
||||
"@types/graphql@^0.9.1":
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.1.tgz#b04ebe84bc997cc60dbea2ed4d0d4342c737f99d"
|
||||
|
||||
"@types/isomorphic-fetch@0.0.33":
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.33.tgz#3ea1b86f8b73e6a7430d01d4dbd5b1f63fd72718"
|
||||
|
||||
"@types/mime@*":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-0.0.29.tgz#fbcfd330573b912ef59eeee14602bface630754b"
|
||||
@@ -162,6 +158,10 @@ ansi-escapes@^1.1.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
|
||||
|
||||
ansi-escapes@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b"
|
||||
|
||||
ansi-regex@^1.0.0, ansi-regex@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-1.1.1.tgz#41c847194646375e6a1a5d10c3ca054ef9fc980d"
|
||||
@@ -170,10 +170,20 @@ ansi-regex@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
||||
|
||||
ansi-regex@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||
|
||||
ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||
|
||||
ansi-styles@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
any-promise@^0.1.0, any-promise@~0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-0.1.0.tgz#830b680aa7e56f33451d4b049f3bd8044498ee27"
|
||||
@@ -189,20 +199,27 @@ anymatch@^1.3.0:
|
||||
arrify "^1.0.0"
|
||||
micromatch "^2.1.5"
|
||||
|
||||
apollo-client@^1.0.2, apollo-client@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.0.4.tgz#af75db8cdd27e08a835ddfb39807849e178540f9"
|
||||
apollo-client@^1.4.0, apollo-client@^1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.9.1.tgz#9e6a383605572c755038cf5d7fdac9382bcdc040"
|
||||
dependencies:
|
||||
graphql "^0.9.3"
|
||||
apollo-link-core "^0.5.0"
|
||||
graphql "^0.10.0"
|
||||
graphql-anywhere "^3.0.1"
|
||||
graphql-tag "^2.0.0"
|
||||
redux "^3.4.0"
|
||||
symbol-observable "^1.0.2"
|
||||
whatwg-fetch "^2.0.0"
|
||||
optionalDependencies:
|
||||
"@types/async" "^2.0.31"
|
||||
"@types/graphql" "^0.9.0"
|
||||
"@types/isomorphic-fetch" "0.0.33"
|
||||
"@types/graphql" "0.10.2"
|
||||
|
||||
apollo-link-core@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-core/-/apollo-link-core-0.5.0.tgz#dc87da1aaa63b029321ae70938dc26257f5ab8c6"
|
||||
dependencies:
|
||||
graphql "^0.10.3"
|
||||
graphql-tag "^2.4.2"
|
||||
zen-observable-ts "^0.4.0"
|
||||
|
||||
app-module-path@^2.2.0:
|
||||
version "2.2.0"
|
||||
@@ -1498,6 +1515,14 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
|
||||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e"
|
||||
dependencies:
|
||||
ansi-styles "^3.1.0"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^4.0.0"
|
||||
|
||||
change-emitter@^0.1.2:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515"
|
||||
@@ -1727,7 +1752,7 @@ codemirror@*:
|
||||
version "5.25.2"
|
||||
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.25.2.tgz#8c77677ca9c9248d757d3a07ed1e89a8404850b7"
|
||||
|
||||
color-convert@^1.3.0:
|
||||
color-convert@^1.3.0, color-convert@^1.9.0:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
|
||||
dependencies:
|
||||
@@ -3053,10 +3078,12 @@ extend@^1.2.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-1.3.0.tgz#d1516fb0ff5624d2ebf9123ea1dac5a1994004f8"
|
||||
|
||||
external-editor@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.1.tgz#4c597c6c88fa6410e41dbbaa7b1be2336aa31095"
|
||||
external-editor@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.4.tgz#1ed9199da9cbfe2ef2f7a31b2fde8b0d12368972"
|
||||
dependencies:
|
||||
iconv-lite "^0.4.17"
|
||||
jschardet "^1.4.2"
|
||||
tmp "^0.0.31"
|
||||
|
||||
extglob@^0.3.1:
|
||||
@@ -3687,6 +3714,10 @@ graphql-tag@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.0.0.tgz#f3efe3b4d64f33bfe8479ae06a461c9d72f2a6fe"
|
||||
|
||||
graphql-tag@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.4.2.tgz#6a63297d8522d03a2b72d26f1b239aab343840cd"
|
||||
|
||||
graphql-tools@^0.10.1:
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-0.10.1.tgz#274aa338d50b1c0b3ed6936eafd8ed3a19ed1828"
|
||||
@@ -3697,13 +3728,19 @@ graphql-tools@^0.10.1:
|
||||
optionalDependencies:
|
||||
"@types/graphql" "^0.8.5"
|
||||
|
||||
graphql@^0.10.0, graphql@^0.10.3:
|
||||
version "0.10.5"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.10.5.tgz#c9be17ca2bdfdbd134077ffd9bbaa48b8becd298"
|
||||
dependencies:
|
||||
iterall "^1.1.0"
|
||||
|
||||
graphql@^0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.7.2.tgz#cc894a32823399b8a0cb012b9e9ecad35cd00f72"
|
||||
dependencies:
|
||||
iterall "1.0.2"
|
||||
|
||||
graphql@^0.9.1, graphql@^0.9.3:
|
||||
graphql@^0.9.1:
|
||||
version "0.9.3"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.9.3.tgz#71fc0fa331bffb9c20678485861cfb370803118e"
|
||||
dependencies:
|
||||
@@ -3757,6 +3794,10 @@ has-flag@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
|
||||
|
||||
has-flag@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
|
||||
|
||||
has-unicode@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||
@@ -3848,6 +3889,10 @@ hoist-non-react-statics@^1.0.0, hoist-non-react-statics@^1.0.3, hoist-non-react-
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
|
||||
|
||||
hoist-non-react-statics@^2.2.0:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.2.2.tgz#c0eca5a7d5a28c5ada3107eb763b01da6bfa81fb"
|
||||
|
||||
home-or-tmp@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
|
||||
@@ -3968,10 +4013,14 @@ iconv-lite@0.4.13:
|
||||
version "0.4.13"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
|
||||
|
||||
iconv-lite@0.4.15, iconv-lite@^0.4.5, iconv-lite@~0.4.13:
|
||||
iconv-lite@0.4.15:
|
||||
version "0.4.15"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
|
||||
|
||||
iconv-lite@^0.4.17, iconv-lite@^0.4.5, iconv-lite@~0.4.13:
|
||||
version "0.4.18"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2"
|
||||
|
||||
icss-replace-symbols@1.0.2, icss-replace-symbols@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz#cb0b6054eb3af6edc9ab1d62d01933e2d4c8bfa5"
|
||||
@@ -4002,10 +4051,6 @@ immutability-helper@^2.2.0:
|
||||
dependencies:
|
||||
invariant "^2.2.0"
|
||||
|
||||
immutable@^3.8.1:
|
||||
version "3.8.1"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2"
|
||||
|
||||
imports-loader@^0.7.1:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-0.7.1.tgz#f204b5f34702a32c1db7d48d89d5e867a0441253"
|
||||
@@ -4103,22 +4148,23 @@ inquirer@0.8.2:
|
||||
rx "^2.4.3"
|
||||
through "^2.3.6"
|
||||
|
||||
inquirer@^3.0.6:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.0.6.tgz#e04aaa9d05b7a3cb9b0f407d04375f0447190347"
|
||||
inquirer@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.2.1.tgz#06ceb0f540f45ca548c17d6840959878265fa175"
|
||||
dependencies:
|
||||
ansi-escapes "^1.1.0"
|
||||
chalk "^1.0.0"
|
||||
ansi-escapes "^2.0.0"
|
||||
chalk "^2.0.0"
|
||||
cli-cursor "^2.1.0"
|
||||
cli-width "^2.0.0"
|
||||
external-editor "^2.0.1"
|
||||
external-editor "^2.0.4"
|
||||
figures "^2.0.0"
|
||||
lodash "^4.3.0"
|
||||
mute-stream "0.0.7"
|
||||
run-async "^2.2.0"
|
||||
rx "^4.1.0"
|
||||
string-width "^2.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
rx-lite "^4.0.8"
|
||||
rx-lite-aggregates "^4.0.8"
|
||||
string-width "^2.1.0"
|
||||
strip-ansi "^4.0.0"
|
||||
through "^2.3.6"
|
||||
|
||||
interpret@^1.0.0:
|
||||
@@ -4498,7 +4544,7 @@ iterall@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.0.3.tgz#e0b31958f835013c323ff0b10943829ac69aa4b7"
|
||||
|
||||
iterall@^1.1.1:
|
||||
iterall@^1.1.0, iterall@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.1.tgz#f7f0af11e9a04ec6426260f5019d9fcca4d50214"
|
||||
|
||||
@@ -4560,6 +4606,10 @@ jsbn@~0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||
|
||||
jschardet@^1.4.2:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.1.tgz#c519f629f86b3a5bedba58a88d311309eec097f9"
|
||||
|
||||
jsdom@^7.0.2:
|
||||
version "7.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-7.2.2.tgz#40b402770c2bda23469096bee91ab675e3b1fc6e"
|
||||
@@ -6875,14 +6925,14 @@ react-addons-test-utils@^15.4.2:
|
||||
fbjs "^0.8.4"
|
||||
object-assign "^4.1.0"
|
||||
|
||||
react-apollo@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-1.1.1.tgz#136a6be6e0ed7bfa5292e67073e1829fe12e1e17"
|
||||
react-apollo@^1.4.12:
|
||||
version "1.4.12"
|
||||
resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-1.4.12.tgz#0cacbdd335acec4c1079feb48047df1c43f77bdf"
|
||||
dependencies:
|
||||
apollo-client "^1.0.2"
|
||||
apollo-client "^1.4.0"
|
||||
graphql-anywhere "^3.0.0"
|
||||
graphql-tag "^2.0.0"
|
||||
hoist-non-react-statics "^1.2.0"
|
||||
hoist-non-react-statics "^2.2.0"
|
||||
invariant "^2.2.1"
|
||||
lodash.flatten "^4.2.0"
|
||||
lodash.isequal "^4.1.1"
|
||||
@@ -6890,10 +6940,8 @@ react-apollo@^1.1.0:
|
||||
lodash.pick "^4.4.0"
|
||||
object-assign "^4.0.1"
|
||||
prop-types "^15.5.8"
|
||||
optionalDependencies:
|
||||
react-dom "0.14.x || 15.* || ^15.0.0"
|
||||
|
||||
"react-dom@0.14.x || 15.* || ^15.0.0", react-dom@^15.3.1, react-dom@^15.4.2:
|
||||
react-dom@^15.3.1, react-dom@^15.4.2:
|
||||
version "15.5.4"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.5.4.tgz#ba0c28786fd52ed7e4f2135fe0288d462aef93da"
|
||||
dependencies:
|
||||
@@ -7434,6 +7482,16 @@ run-async@^2.2.0:
|
||||
dependencies:
|
||||
is-promise "^2.1.0"
|
||||
|
||||
rx-lite-aggregates@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
|
||||
dependencies:
|
||||
rx-lite "*"
|
||||
|
||||
rx-lite@*, rx-lite@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
|
||||
|
||||
rx-lite@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
|
||||
@@ -7442,10 +7500,6 @@ rx@^2.4.3:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/rx/-/rx-2.5.3.tgz#21adc7d80f02002af50dae97fd9dbf248755f566"
|
||||
|
||||
rx@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@~5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
|
||||
@@ -7826,12 +7880,12 @@ string-width@^1.0.1, string-width@^1.0.2:
|
||||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
string-width@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e"
|
||||
string-width@^2.0.0, string-width@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||
dependencies:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
string.prototype.codepointat@^0.2.0:
|
||||
version "0.2.0"
|
||||
@@ -7871,6 +7925,12 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1:
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
strip-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
|
||||
dependencies:
|
||||
ansi-regex "^3.0.0"
|
||||
|
||||
strip-bom@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
|
||||
@@ -7961,6 +8021,12 @@ supports-color@^3.1.2, supports-color@^3.2.3:
|
||||
dependencies:
|
||||
has-flag "^1.0.0"
|
||||
|
||||
supports-color@^4.0.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836"
|
||||
dependencies:
|
||||
has-flag "^2.0.0"
|
||||
|
||||
svgo@^0.7.0:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
|
||||
@@ -8754,3 +8820,7 @@ yauzl@^2.5.0:
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.0.1"
|
||||
|
||||
zen-observable-ts@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.4.0.tgz#a74bc9fe59747948a577bd513d438e70fcfae7e2"
|
||||
|
||||
Reference in New Issue
Block a user