mirror of
https://github.com/wassname/talk.git
synced 2026-08-17 11:27:52 +08:00
Merge branch 'master' into subscriptions
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import * as actions from '../constants/asset';
|
||||
import coralApi from '../helpers/response';
|
||||
import {addNotification} from '../actions/notification';
|
||||
import {pym} from 'coral-framework';
|
||||
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from './../translations';
|
||||
@@ -39,7 +38,6 @@ export const updateOpenStream = closedBody => (dispatch, getState) => {
|
||||
|
||||
const openStream = () => ({type: actions.OPEN_COMMENTS});
|
||||
const closeStream = () => ({type: actions.CLOSE_COMMENTS});
|
||||
export const updateCountCache = (id, count) => ({type: actions.UPDATE_COUNT_CACHE, id, count});
|
||||
|
||||
export const updateOpenStatus = status => dispatch => {
|
||||
if (status === 'open') {
|
||||
@@ -51,36 +49,3 @@ export const updateOpenStatus = status => dispatch => {
|
||||
}
|
||||
};
|
||||
|
||||
function removeParam(key, sourceURL) {
|
||||
let rtn = sourceURL.split('?')[0];
|
||||
let param;
|
||||
let params_arr = [];
|
||||
let queryString = (sourceURL.indexOf('?') !== -1) ? sourceURL.split('?')[1] : '';
|
||||
if (queryString !== '') {
|
||||
params_arr = queryString.split('&');
|
||||
for (let i = params_arr.length - 1; i >= 0; i -= 1) {
|
||||
param = params_arr[i].split('=')[0];
|
||||
if (param === key) {
|
||||
params_arr.splice(i, 1);
|
||||
}
|
||||
}
|
||||
rtn = `${rtn}?${params_arr.join('&')}`;
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
export const viewAllComments = () => {
|
||||
|
||||
// remove the comment_id url param
|
||||
const modifiedUrl = removeParam('comment_id', location.href);
|
||||
try {
|
||||
|
||||
// "window" here refers to the embedded iframe
|
||||
window.history.replaceState({}, document.title, modifiedUrl);
|
||||
|
||||
// also change the parent url
|
||||
pym.sendMessage('coral-view-all-comments');
|
||||
} catch (e) { /* not sure if we're worried about old browsers */ }
|
||||
|
||||
return {type: actions.VIEW_ALL_COMMENTS};
|
||||
};
|
||||
|
||||
@@ -8,6 +8,4 @@ export const UPDATE_ASSET_SETTINGS_FAILURE = 'UPDATE_ASSET_SETTINGS_FAILURE';
|
||||
|
||||
export const OPEN_COMMENTS = 'OPEN_COMMENTS';
|
||||
export const CLOSE_COMMENTS = 'CLOSE_COMMENTS';
|
||||
export const UPDATE_COUNT_CACHE = 'UPDATE_COUNT_CACHE';
|
||||
|
||||
export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS';
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export const ADDTL_COMMENTS_ON_LOAD_MORE = 10;
|
||||
export const NEW_COMMENT_COUNT_POLL_INTERVAL = 20000;
|
||||
@@ -9,10 +9,6 @@ import REMOVE_COMMENT_TAG from './removeCommentTag.graphql';
|
||||
import IGNORE_USER from './ignoreUser.graphql';
|
||||
import STOP_IGNORING_USER from './stopIgnoringUser.graphql';
|
||||
|
||||
import MY_IGNORED_USERS from '../queries/myIgnoredUsers.graphql';
|
||||
import STREAM_QUERY from '../queries/streamQuery.graphql';
|
||||
import {variablesForStreamQuery} from '../queries';
|
||||
|
||||
import commentView from '../fragments/commentView.graphql';
|
||||
|
||||
export const postComment = graphql(POST_COMMENT, {
|
||||
@@ -45,7 +41,7 @@ export const postComment = graphql(POST_COMMENT, {
|
||||
}
|
||||
},
|
||||
updateQueries: {
|
||||
AssetQuery: (oldData, {mutationResult: {data: {createComment: {comment}}}}) => {
|
||||
EmbedQuery: (oldData, {mutationResult: {data: {createComment: {comment}}}}) => {
|
||||
|
||||
if (oldData.asset.settings.moderation === 'PRE' || comment.status === 'PREMOD' || comment.status === 'REJECTED') {
|
||||
return oldData;
|
||||
@@ -155,6 +151,7 @@ export const removeCommentTag = graphql(REMOVE_COMMENT_TAG, {
|
||||
}}),
|
||||
});
|
||||
|
||||
// TODO: don't rely on refetching.
|
||||
export const ignoreUser = graphql(IGNORE_USER, {
|
||||
props: ({mutate}) => ({
|
||||
ignoreUser: ({id}) => {
|
||||
@@ -162,15 +159,16 @@ export const ignoreUser = graphql(IGNORE_USER, {
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [{
|
||||
query: MY_IGNORED_USERS,
|
||||
}]
|
||||
refetchQueries: [
|
||||
'EmbedQuery', 'myIgnoredUsers',
|
||||
]
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
// TODO: don't rely on refetching.
|
||||
export const stopIgnoringUser = graphql(STOP_IGNORING_USER, {
|
||||
props: ({mutate, ownProps}) => {
|
||||
props: ({mutate}) => {
|
||||
return {
|
||||
stopIgnoringUser: ({id}) => {
|
||||
return mutate({
|
||||
@@ -178,13 +176,7 @@ export const stopIgnoringUser = graphql(STOP_IGNORING_USER, {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [
|
||||
{
|
||||
query: MY_IGNORED_USERS,
|
||||
},
|
||||
{
|
||||
query: STREAM_QUERY,
|
||||
variables: variablesForStreamQuery(ownProps),
|
||||
}
|
||||
'EmbedQuery', 'myIgnoredUsers',
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#import "../fragments/commentView.graphql"
|
||||
|
||||
query commentQuery($id: ID!) {
|
||||
comment(id: $id) {
|
||||
...commentView
|
||||
parent {
|
||||
...commentView
|
||||
replies {
|
||||
...commentView
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
query LoadCommentCounts($asset_id: ID, $limit: Int = 5, $sort: SORT_ORDER) {
|
||||
asset(id: $asset_id) {
|
||||
id
|
||||
commentCount
|
||||
comments(sort: $sort, limit: $limit) {
|
||||
id
|
||||
replyCount
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,153 +1,6 @@
|
||||
import {graphql} from 'react-apollo';
|
||||
import STREAM_QUERY from './streamQuery.graphql';
|
||||
import LOAD_MORE from './loadMore.graphql';
|
||||
import GET_COUNTS from './getCounts.graphql';
|
||||
import MY_COMMENT_HISTORY from './myCommentHistory.graphql';
|
||||
import MY_IGNORED_USERS from './myIgnoredUsers.graphql';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import isNil from 'lodash/isNil';
|
||||
|
||||
function getQueryVariable(variable) {
|
||||
let query = window.location.search.substring(1);
|
||||
let vars = query.split('&');
|
||||
for (let i = 0; i < vars.length; i++) {
|
||||
let pair = vars[i].split('=');
|
||||
if (decodeURIComponent(pair[0]) === variable) {
|
||||
return decodeURIComponent(pair[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, return null.
|
||||
return null;
|
||||
}
|
||||
|
||||
// get the counts of the top-level comments
|
||||
export const getCounts = (data) => ({asset_id, limit, sort}) => {
|
||||
return data.fetchMore({
|
||||
query: GET_COUNTS,
|
||||
variables: {
|
||||
asset_id,
|
||||
limit,
|
||||
sort,
|
||||
excludeIgnored: data.variables.excludeIgnored,
|
||||
},
|
||||
updateQuery: (oldData, {fetchMoreResult:{asset}}) => {
|
||||
return {
|
||||
...oldData,
|
||||
asset: {
|
||||
...oldData.asset,
|
||||
commentCount: asset.commentCount
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// handle paginated requests for more Comments pertaining to the Asset
|
||||
export const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, sort}, newComments) => {
|
||||
return data.fetchMore({
|
||||
query: LOAD_MORE,
|
||||
variables: {
|
||||
limit, // how many comments are we returning
|
||||
cursor, // the date of the first/last comment depending on the sort order
|
||||
parent_id, // if null, we're loading more top-level comments, if not, we're loading more replies to a comment
|
||||
asset_id, // the id of the asset we're currently on
|
||||
sort, // CHRONOLOGICAL or REVERSE_CHRONOLOGICAL
|
||||
excludeIgnored: data.variables.excludeIgnored,
|
||||
},
|
||||
updateQuery: (oldData, {fetchMoreResult:{new_top_level_comments}}) => {
|
||||
let updatedAsset;
|
||||
|
||||
if (!isNil(oldData.comment)) { // loaded replies on a highlighted (permalinked) comment
|
||||
|
||||
let comment = {};
|
||||
if (oldData.comment && oldData.comment.parent) {
|
||||
|
||||
// put comments (replies) onto the oldData.comment.parent object
|
||||
// the initial comment permalinked was a reply
|
||||
const uniqReplies = uniqBy([...new_top_level_comments, ...oldData.comment.parent.replies], 'id');
|
||||
comment.parent = {...oldData.comment.parent, replies: sortBy(uniqReplies, 'created_at')};
|
||||
} else if (oldData.comment) {
|
||||
|
||||
// put the comments (replies) directly onto oldData.comment
|
||||
// the initial comment permalinked was a top-level comment
|
||||
const uniqReplies = uniqBy([...new_top_level_comments, ...oldData.comment.replies], 'id');
|
||||
comment.replies = sortBy(uniqReplies, 'created_at');
|
||||
}
|
||||
|
||||
updatedAsset = {
|
||||
...oldData,
|
||||
comment: {
|
||||
...oldData.comment,
|
||||
...comment
|
||||
}
|
||||
};
|
||||
|
||||
} else if (parent_id) { // If loading more replies
|
||||
|
||||
updatedAsset = {
|
||||
...oldData,
|
||||
asset: {
|
||||
...oldData.asset,
|
||||
comments: oldData.asset.comments.map(comment => {
|
||||
|
||||
// since the dipslayed replies and the returned replies can overlap,
|
||||
// pull out the unique ones.
|
||||
const uniqueReplies = uniqBy([...new_top_level_comments, ...comment.replies], 'id');
|
||||
|
||||
// since we just gave the returned replies precedence, they're now out of order.
|
||||
// resort according to date.
|
||||
return comment.id === parent_id
|
||||
? {...comment, replies: sortBy(uniqueReplies, 'created_at')}
|
||||
: comment;
|
||||
})
|
||||
}
|
||||
};
|
||||
} else { // If loading more top-level comments
|
||||
|
||||
updatedAsset = {
|
||||
...oldData,
|
||||
asset: {
|
||||
...oldData.asset,
|
||||
comments: newComments ? [...new_top_level_comments.reverse(), ...oldData.asset.comments]
|
||||
: [...oldData.asset.comments, ...new_top_level_comments]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return updatedAsset;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const variablesForStreamQuery = ({auth}) => {
|
||||
|
||||
// where the query string is from the embeded iframe url
|
||||
let comment_id = getQueryVariable('comment_id');
|
||||
let has_comment = comment_id != null;
|
||||
return {
|
||||
asset_id: getQueryVariable('asset_id'),
|
||||
asset_url: getQueryVariable('asset_url'),
|
||||
comment_id: has_comment ? comment_id : 'no-comment',
|
||||
has_comment,
|
||||
excludeIgnored: Boolean(auth && auth.user && auth.user.id),
|
||||
};
|
||||
};
|
||||
|
||||
// load the comment stream.
|
||||
export const queryStream = graphql(STREAM_QUERY, {
|
||||
options: (props) => {
|
||||
return {
|
||||
variables: variablesForStreamQuery(props)
|
||||
};
|
||||
},
|
||||
props: ({data}) => ({
|
||||
data,
|
||||
loadMore: loadMore(data),
|
||||
getCounts: getCounts(data),
|
||||
})
|
||||
});
|
||||
|
||||
export const myCommentHistory = graphql(MY_COMMENT_HISTORY, {});
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#import "../fragments/commentView.graphql"
|
||||
|
||||
query LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER, $excludeIgnored: Boolean) {
|
||||
new_top_level_comments: comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort, excludeIgnored: $excludeIgnored}) {
|
||||
...commentView
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
replies(limit: 3) {
|
||||
...commentView
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#import "../fragments/commentView.graphql"
|
||||
|
||||
query AssetQuery($asset_id: ID, $asset_url: String, $comment_id: ID!, $has_comment: Boolean!, $excludeIgnored: Boolean) {
|
||||
# the comment here is for loading one comment and it's children, probably after following a permalink
|
||||
# $has_comment is derived from the comment_id query param in the iframe url,
|
||||
# which is in turn pulled from the host page url
|
||||
comment(id: $comment_id) @include(if: $has_comment) {
|
||||
...commentView
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
replies {
|
||||
...commentView
|
||||
}
|
||||
parent {
|
||||
...commentView
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
replies {
|
||||
...commentView
|
||||
}
|
||||
}
|
||||
}
|
||||
asset(id: $asset_id, url: $asset_url) {
|
||||
id
|
||||
title
|
||||
url
|
||||
closedAt
|
||||
created_at
|
||||
settings {
|
||||
moderation
|
||||
infoBoxEnable
|
||||
infoBoxContent
|
||||
premodLinksEnable
|
||||
questionBoxEnable
|
||||
questionBoxContent
|
||||
closeTimeout
|
||||
closedMessage
|
||||
charCountEnable
|
||||
charCount
|
||||
requireEmailConfirmation
|
||||
}
|
||||
lastComment {
|
||||
id
|
||||
}
|
||||
commentCount(excludeIgnored: $excludeIgnored)
|
||||
totalCommentCount(excludeIgnored: $excludeIgnored)
|
||||
comments(limit: 10, excludeIgnored: $excludeIgnored) {
|
||||
...commentView
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
replies(limit: 3, excludeIgnored: $excludeIgnored) {
|
||||
...commentView
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import React from 'react';
|
||||
import merge from 'lodash/merge';
|
||||
import flatten from 'lodash/flatten';
|
||||
import flattenDeep from 'lodash/flattenDeep';
|
||||
import uniq from 'lodash/uniq';
|
||||
import plugins from 'pluginsConfig';
|
||||
import {gql} from 'react-apollo';
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
|
||||
export const pluginReducers = merge(
|
||||
...plugins
|
||||
@@ -19,3 +23,53 @@ export function getSlotElements(slot, props = {}) {
|
||||
return components
|
||||
.map((component, i) => React.createElement(component, {...props, key: i}));
|
||||
}
|
||||
|
||||
function getComponentFragments(components) {
|
||||
return components
|
||||
.map(c => c.fragments)
|
||||
.filter(fragments => fragments)
|
||||
.reduce((res, fragments) => {
|
||||
Object.keys(fragments).forEach(key => {
|
||||
if (!(key in res)) {
|
||||
res[key] = {spreads: '', definitions: ''};
|
||||
}
|
||||
res[key].spreads += `...${getDefinitionName(fragments[key])}\n`;
|
||||
res[key].definitions = gql`${res[key].definitions}${fragments[key]}`;
|
||||
});
|
||||
return res;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that can be used to compose fragments or queries.
|
||||
*
|
||||
* Example:
|
||||
* const pluginFragments = getSlotsFragments(['commentInfoBar', 'commentDetail']);
|
||||
* const rootFragment = gql`
|
||||
* fragment Comment_root on RootQuery {
|
||||
+ ${pluginFragments.spreads('root')}
|
||||
* }
|
||||
* ${pluginFragments.definitions('root')}
|
||||
* `;
|
||||
*/
|
||||
export function getSlotsFragments(slots) {
|
||||
if (!Array.isArray(slots)) {
|
||||
slots = [slots];
|
||||
}
|
||||
const components = uniq(flattenDeep(slots.map(slot => {
|
||||
return plugins
|
||||
.filter(o => o.module.slots[slot])
|
||||
.map(o => o.module.slots[slot]);
|
||||
})));
|
||||
|
||||
const fragments = getComponentFragments(components);
|
||||
return {
|
||||
spreads(key) {
|
||||
return fragments[key] && fragments[key].spreads;
|
||||
},
|
||||
definitions(key) {
|
||||
return fragments[key] && fragments[key].definitions;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
// TODO: revisit `filtering` after https://github.com/apollographql/graphql-anywhere/issues/38.
|
||||
|
||||
function getDisplayName(WrappedComponent) {
|
||||
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
|
||||
}
|
||||
|
||||
export default fragments => WrappedComponent => {
|
||||
class WithFragments extends React.Component {
|
||||
render() {
|
||||
return <WrappedComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
WithFragments.fragments = fragments;
|
||||
WithFragments.displayName = `WithFragments(${getDisplayName(WrappedComponent)})`;
|
||||
return WithFragments;
|
||||
};
|
||||
@@ -19,9 +19,6 @@ export default function asset (state = initialState, action) {
|
||||
case actions.UPDATE_ASSET_SETTINGS_SUCCESS:
|
||||
return state
|
||||
.setIn(['settings'], action.settings);
|
||||
case actions.UPDATE_COUNT_CACHE:
|
||||
return state
|
||||
.setIn(['countCache', action.id], action.count);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -24,14 +24,22 @@ if (window.devToolsExtension) {
|
||||
middlewares.push(window.devToolsExtension());
|
||||
}
|
||||
|
||||
const store = createStore(
|
||||
combineReducers({
|
||||
...mainReducer,
|
||||
apollo: client.reducer()
|
||||
}),
|
||||
let storeReducers = {
|
||||
...mainReducer,
|
||||
apollo: client.reducer()
|
||||
};
|
||||
|
||||
export const store = createStore(
|
||||
combineReducers(storeReducers),
|
||||
{},
|
||||
compose(...middlewares)
|
||||
);
|
||||
|
||||
export default store;
|
||||
|
||||
export function injectReducers(reducers) {
|
||||
storeReducers = {...storeReducers, ...reducers};
|
||||
store.replaceReducer(combineReducers(storeReducers));
|
||||
}
|
||||
|
||||
window.coralStore = store;
|
||||
|
||||
@@ -29,3 +29,35 @@ export const getMyActionSummary = (type, comment) => {
|
||||
export const getActionSummary = (type, comment) => {
|
||||
return comment.action_summaries.filter(a => a.__typename === type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get name of first (or $pos-th) definition
|
||||
*/
|
||||
export function getDefinitionName(doc, pos = 0) {
|
||||
return doc.definitions[pos].name.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Separate apollo `data` props into `data` and `root`.
|
||||
* `data` will contain props like `loading`, `fetchMore`...
|
||||
* while `root` contains the actual query data.
|
||||
*/
|
||||
export function separateDataAndRoot(
|
||||
{
|
||||
fetchMore,
|
||||
loading,
|
||||
networkStatus,
|
||||
refetch,
|
||||
startPolling,
|
||||
stopPolling,
|
||||
subscribeToMore,
|
||||
updateQuery,
|
||||
variables,
|
||||
...root,
|
||||
}) {
|
||||
return {
|
||||
data: {fetchMore, loading, networkStatus, refetch, startPolling,
|
||||
stopPolling, subscribeToMore, updateQuery, variables},
|
||||
root,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user