mirror of
https://github.com/wassname/talk.git
synced 2026-07-13 17:45:56 +08:00
Merge branch 'resolve-slot-fragments-lazy' into featured-comments
Conflicts: client/coral-embed-stream/src/containers/Stream.js client/coral-framework/utils/index.js plugin-api/beta/client/utils/index.js
This commit is contained in:
@@ -1,22 +1,21 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import Comment from '../components/Comment';
|
||||
import {getSlotsFragments} from 'coral-framework/helpers/plugins';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import {getSlotFragmentSpreads} from 'coral-framework/utils';
|
||||
|
||||
const pluginFragments = getSlotsFragments([
|
||||
const slots = [
|
||||
'adminCommentInfoBar',
|
||||
'adminCommentContent',
|
||||
'adminSideActions',
|
||||
'adminCommentDetailArea',
|
||||
]);
|
||||
];
|
||||
|
||||
export default withFragments({
|
||||
root: gql`
|
||||
fragment CoralAdmin_ModerationComment_root on RootQuery {
|
||||
__typename
|
||||
${pluginFragments.spreads('root')}
|
||||
${getSlotFragmentSpreads(slots, 'root')}
|
||||
}
|
||||
${pluginFragments.definitions('root')}
|
||||
`,
|
||||
comment: gql`
|
||||
fragment CoralAdmin_ModerationComment_comment on Comment {
|
||||
@@ -52,8 +51,7 @@ export default withFragments({
|
||||
editing {
|
||||
edited
|
||||
}
|
||||
${pluginFragments.spreads('comment')}
|
||||
${getSlotFragmentSpreads(slots, 'comment')}
|
||||
}
|
||||
${pluginFragments.definitions('comment')}
|
||||
`
|
||||
})(Comment);
|
||||
|
||||
@@ -4,8 +4,7 @@ import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import UserDetail from '../components/UserDetail';
|
||||
import withQuery from 'coral-framework/hocs/withQuery';
|
||||
import {getSlotsFragments} from 'coral-framework/helpers/plugins';
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
import {getDefinitionName, getSlotFragmentSpreads} from 'coral-framework/utils';
|
||||
import {
|
||||
changeUserDetailStatuses,
|
||||
clearUserDetailSelections,
|
||||
@@ -26,9 +25,9 @@ const commentConnectionFragment = gql`
|
||||
${Comment.fragments.comment}
|
||||
`;
|
||||
|
||||
const pluginFragments = getSlotsFragments([
|
||||
const slots = [
|
||||
'userProfile',
|
||||
]);
|
||||
];
|
||||
|
||||
class UserDetailContainer extends React.Component {
|
||||
static propTypes = {
|
||||
@@ -80,7 +79,7 @@ export const withUserDetailQuery = withQuery(gql`
|
||||
id
|
||||
provider
|
||||
}
|
||||
${pluginFragments.spreads('user')}
|
||||
${getSlotFragmentSpreads(slots, 'user')}
|
||||
}
|
||||
totalComments: commentCount(query: {author_id: $author_id})
|
||||
rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]})
|
||||
@@ -90,11 +89,8 @@ export const withUserDetailQuery = withQuery(gql`
|
||||
}) {
|
||||
...CoralAdmin_Moderation_CommentConnection
|
||||
}
|
||||
${pluginFragments.spreads('root')}
|
||||
${getSlotFragmentSpreads(slots, 'root')}
|
||||
}
|
||||
${Comment.fragments.comment}
|
||||
${pluginFragments.definitions('user')}
|
||||
${pluginFragments.definitions('root')}
|
||||
${commentConnectionFragment}
|
||||
`, {
|
||||
options: ({id, moderation: {userDetailStatuses: statuses}}) => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import Comment from '../components/Comment';
|
||||
import {withFragments} from 'coral-framework/hocs';
|
||||
import {getSlotsFragments} from 'coral-framework/helpers/plugins';
|
||||
import {getSlotFragmentSpreads} from 'coral-framework/utils';
|
||||
|
||||
const pluginFragments = getSlotsFragments([
|
||||
const slots = [
|
||||
'streamQuestionArea',
|
||||
'commentInputArea',
|
||||
'commentInputDetailArea',
|
||||
@@ -12,22 +12,20 @@ const pluginFragments = getSlotsFragments([
|
||||
'commentContent',
|
||||
'commentReactions',
|
||||
'commentAvatar'
|
||||
]);
|
||||
];
|
||||
|
||||
export default withFragments({
|
||||
root: gql`
|
||||
fragment CoralEmbedStream_Comment_root on RootQuery {
|
||||
__typename
|
||||
${pluginFragments.spreads('root')}
|
||||
${getSlotFragmentSpreads(slots, 'root')}
|
||||
}
|
||||
${pluginFragments.definitions('root')}
|
||||
`,
|
||||
asset: gql`
|
||||
fragment CoralEmbedStream_Comment_asset on Asset {
|
||||
__typename
|
||||
${pluginFragments.spreads('asset')}
|
||||
${getSlotFragmentSpreads(slots, 'asset')}
|
||||
}
|
||||
${pluginFragments.definitions('asset')}
|
||||
`,
|
||||
comment: gql`
|
||||
fragment CoralEmbedStream_Comment_comment on Comment {
|
||||
@@ -56,8 +54,7 @@ export default withFragments({
|
||||
edited
|
||||
editableUntil
|
||||
}
|
||||
${pluginFragments.spreads('comment')}
|
||||
${getSlotFragmentSpreads(slots, 'comment')}
|
||||
}
|
||||
${pluginFragments.definitions('comment')}
|
||||
`
|
||||
})(Comment);
|
||||
|
||||
@@ -15,9 +15,8 @@ import {setActiveReplyBox, setActiveTab, viewAllComments} from '../actions/strea
|
||||
import Stream from '../components/Stream';
|
||||
import Comment from './Comment';
|
||||
import {withFragments} from 'coral-framework/hocs';
|
||||
import {getSlotsFragments} from 'coral-framework/helpers/plugins';
|
||||
import {getDefinitionName, getSlotFragmentSpreads} from 'coral-framework/utils';
|
||||
import {Spinner} from 'coral-ui';
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
import {
|
||||
findCommentInEmbedQuery,
|
||||
insertCommentIntoEmbedQuery,
|
||||
@@ -229,10 +228,10 @@ const LOAD_MORE_QUERY = gql`
|
||||
${Comment.fragments.comment}
|
||||
`;
|
||||
|
||||
const pluginFragments = getSlotsFragments([
|
||||
const slots = [
|
||||
'streamTabs',
|
||||
'streamTabPanes',
|
||||
]);
|
||||
];
|
||||
|
||||
const fragments = {
|
||||
root: gql`
|
||||
@@ -275,7 +274,7 @@ const fragments = {
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
${pluginFragments.spreads('asset')}
|
||||
${getSlotFragmentSpreads(slots, 'asset')}
|
||||
...${getDefinitionName(Comment.fragments.asset)}
|
||||
}
|
||||
me {
|
||||
@@ -287,11 +286,9 @@ const fragments = {
|
||||
settings {
|
||||
organizationName
|
||||
}
|
||||
${pluginFragments.spreads('root')}
|
||||
${getSlotFragmentSpreads(slots, 'root')}
|
||||
...${getDefinitionName(Comment.fragments.root)}
|
||||
}
|
||||
${pluginFragments.definitions('asset')}
|
||||
${pluginFragments.definitions('root')}
|
||||
${Comment.fragments.asset}
|
||||
${Comment.fragments.root}
|
||||
${commentFragment}
|
||||
|
||||
@@ -5,7 +5,6 @@ import merge from 'lodash/merge';
|
||||
import plugins from 'pluginsConfig';
|
||||
import flatten from 'lodash/flatten';
|
||||
import flattenDeep from 'lodash/flattenDeep';
|
||||
import {getDefinitionName, mergeDocuments} from 'coral-framework/utils';
|
||||
import {loadTranslations} from 'coral-framework/services/i18n';
|
||||
import {injectReducers, getStore} from 'coral-framework/services/store';
|
||||
import camelize from './camelize';
|
||||
@@ -35,62 +34,21 @@ export function getSlotElements(slot, props = {}) {
|
||||
.map((component, i) => React.createElement(component, {key: i, ...props}));
|
||||
}
|
||||
|
||||
function getComponentFragments(components) {
|
||||
const res = components
|
||||
export function getSlotFragments(slot, part) {
|
||||
const components = uniq(flattenDeep(plugins
|
||||
.filter((o) => o.module.slots ? o.module.slots[slot] : false)
|
||||
.map((o) => o.module.slots[slot])
|
||||
));
|
||||
|
||||
const documents = components
|
||||
.map((c) => c.fragments)
|
||||
.filter((fragments) => fragments)
|
||||
.filter((fragments) => fragments && fragments[part])
|
||||
.reduce((res, fragments) => {
|
||||
Object.keys(fragments).forEach((key) => {
|
||||
if (!(key in res)) {
|
||||
res[key] = {spreads: [], definitions: []};
|
||||
}
|
||||
res[key].spreads.push(getDefinitionName(fragments[key]));
|
||||
res[key].definitions.push(fragments[key]);
|
||||
});
|
||||
res.push(fragments[part]);
|
||||
return res;
|
||||
}, {});
|
||||
}, []);
|
||||
|
||||
Object.keys(res).forEach((key) => {
|
||||
|
||||
// Assemble arguments for `gql` to call it directly without using template literals.
|
||||
res[key].spreads = `...${res[key].spreads.join('\n...')}\n`;
|
||||
res[key].definitions = mergeDocuments(res[key].definitions);
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that can be used to compose fragments or queries.
|
||||
*
|
||||
* Example:
|
||||
* const pluginFragments = getSlotsFragments(['commentInfoBar', 'commentActions']);
|
||||
* 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 ? o.module.slots[slot] : false)
|
||||
.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) || '';
|
||||
},
|
||||
};
|
||||
return documents;
|
||||
}
|
||||
|
||||
export function getGraphQLExtensions() {
|
||||
|
||||
@@ -15,14 +15,42 @@ const withSkipOnErrors = (reducer) => (prev, action, ...rest) => {
|
||||
* apply query options registered in the graphRegistry.
|
||||
*/
|
||||
export default (document, config = {}) => (WrappedComponent) => {
|
||||
config = {
|
||||
const wrappedConfig = {
|
||||
...config,
|
||||
options: config.options || {},
|
||||
props: config.props || (({data}) => separateDataAndRoot(data)),
|
||||
props: (args) => {
|
||||
const wrappedArgs = {
|
||||
...args,
|
||||
data: {
|
||||
...args.data,
|
||||
subscribeToMore(stmArgs) {
|
||||
|
||||
// Resolve document fragments before passing it to `apollo-client`.
|
||||
return args.data.subscribeToMore({
|
||||
...stmArgs,
|
||||
document: resolveFragments(stmArgs.document),
|
||||
});
|
||||
},
|
||||
fetchMore(lmArgs) {
|
||||
|
||||
// Resolve document fragments before passing it to `apollo-client`.
|
||||
return args.data.fetchMore({
|
||||
...lmArgs,
|
||||
query: resolveFragments(lmArgs.query),
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
return config.props
|
||||
? config.props(wrappedArgs)
|
||||
: separateDataAndRoot(wrappedArgs.data);
|
||||
},
|
||||
};
|
||||
|
||||
const wrappedOptions = (data) => {
|
||||
const base = (typeof config.options === 'function') ? config.options(data) : config.options;
|
||||
const base = (typeof wrappedConfig.options === 'function')
|
||||
? wrappedConfig.options(data)
|
||||
: wrappedConfig.options;
|
||||
const name = getDefinitionName(document);
|
||||
const configs = getQueryOptions(name);
|
||||
const reducerCallbacks =
|
||||
@@ -45,7 +73,7 @@ export default (document, config = {}) => (WrappedComponent) => {
|
||||
let memoized = null;
|
||||
const getWrapped = () => {
|
||||
if (!memoized) {
|
||||
memoized = graphql(resolveFragments(document), {...config, options: wrappedOptions})(WrappedComponent);
|
||||
memoized = graphql(resolveFragments(document), {...wrappedConfig, options: wrappedOptions})(WrappedComponent);
|
||||
}
|
||||
return memoized;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {getDefinitionName, mergeDocuments} from 'coral-framework/utils';
|
||||
import {getGraphQLExtensions} from 'coral-framework/helpers/plugins';
|
||||
import {getGraphQLExtensions, getSlotFragments} from 'coral-framework/helpers/plugins';
|
||||
import globalFragments from 'coral-framework/graphql/fragments';
|
||||
import uniq from 'lodash/uniq';
|
||||
import {gql} from 'react-apollo';
|
||||
|
||||
const fragments = {};
|
||||
const mutationOptions = {};
|
||||
@@ -139,18 +140,47 @@ export function getQueryOptions(key) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a document with a fragment named `key`, which contains
|
||||
* all fragments added under this key.
|
||||
* getSlotFragmentDocument handles `key`s in the form of TalkSlot_SlotName_Resource.
|
||||
* It parses the slot name and the resource and usees the plugin API to assemble
|
||||
* the fragment document.
|
||||
*/
|
||||
export function getFragmentDocument(key) {
|
||||
init();
|
||||
function getSlotFragmentDocument(key) {
|
||||
const match = key.match(/TalkSlot_(.*)_(.*)/);
|
||||
if (!match) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const slot = match[1][0].toLowerCase() + match[1].substr(1);
|
||||
const resource = match[2];
|
||||
const documents = getSlotFragments(slot, resource);
|
||||
|
||||
if (documents.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const names = documents.map((d) => getDefinitionName(d));
|
||||
const typeName = getTypeName(documents[0]);
|
||||
|
||||
// Assemble arguments for `gql` to call it directly without using template literals.
|
||||
const main = `
|
||||
fragment ${key} on ${typeName} {
|
||||
...${names.join('\n...')}\n
|
||||
}
|
||||
`;
|
||||
return mergeDocuments([main, ...documents]);
|
||||
}
|
||||
|
||||
/**
|
||||
* getRegistryFragmentDocument assembles a fragment document using
|
||||
* all registered fragment under given `key`.
|
||||
*/
|
||||
function getRegistryFragmentDocument(key) {
|
||||
if (!(key in fragments)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let documents = fragments[key] ? fragments[key].documents : [];
|
||||
let fields = fragments[key] ? `...${fragments[key].names.join('\n...')}\n` : ' __typename';
|
||||
let documents = fragments[key].documents;
|
||||
let fields = `...${fragments[key].names.join('\n...')}\n`;
|
||||
|
||||
// Assemble arguments for `gql` to call it directly without using template literals.
|
||||
const main = `
|
||||
@@ -161,6 +191,16 @@ export function getFragmentDocument(key) {
|
||||
return mergeDocuments([main, ...documents]);
|
||||
}
|
||||
|
||||
/**
|
||||
* getFragmentDocument returns a fragment that assembles all registered
|
||||
* fragments under given `key` or if `key` refers to Slot fragments it will
|
||||
* return the slot fragments specified by this key.
|
||||
*/
|
||||
export function getFragmentDocument(key) {
|
||||
init();
|
||||
return getRegistryFragmentDocument(key) || getSlotFragmentDocument(key);
|
||||
}
|
||||
|
||||
// The fragments and configs are lazily loaded to allow circular dependencies to work.
|
||||
// TODO: We might want to change this to an explicit add after we have lazy Queries and Mutations.
|
||||
let initialized = false;
|
||||
@@ -178,19 +218,35 @@ function init() {
|
||||
getGraphQLExtensions().forEach((ext) => add(ext));
|
||||
}
|
||||
|
||||
/**
|
||||
* resolveFragments finds fragment spread names and attachs
|
||||
* the related fragment document to the given root document.
|
||||
*/
|
||||
export function resolveFragments(document) {
|
||||
if (document.loc.source) {
|
||||
|
||||
// resolve fragments from registry
|
||||
const matchedSubFragments = document.loc.source.body.match(/\.\.\.(.*)/g) || [];
|
||||
const subFragments =
|
||||
uniq(matchedSubFragments.map((f) => f.replace('...', '')))
|
||||
.map((key) => getFragmentDocument(key))
|
||||
.filter((i) => i);
|
||||
const subFragments = [];
|
||||
let body = document.loc.source.body;
|
||||
|
||||
const matchedSubFragments = body.match(/\.\.\.(.*)/g) || [];
|
||||
|
||||
uniq(matchedSubFragments.map((f) => f.replace('...', '')))
|
||||
.forEach((key) => {
|
||||
const doc = getFragmentDocument(key);
|
||||
if (doc) {
|
||||
subFragments.push(doc);
|
||||
} else if(key.startsWith('TalkSlot_')) {
|
||||
|
||||
// Remove fragment spread of slots with zero fragments.
|
||||
body = body.replace(`...${key}\n`, '');
|
||||
}
|
||||
});
|
||||
|
||||
if (subFragments.length > 0) {
|
||||
return mergeDocuments([document, ...subFragments]);
|
||||
return mergeDocuments([body, ...subFragments]);
|
||||
}
|
||||
|
||||
return gql`${body}`;
|
||||
} else {
|
||||
console.warn('Can only resolve fragments from documents definied using the gql tag.');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import {capitalize} from 'coral-framework/helpers/strings';
|
||||
|
||||
export const getTotalActionCount = (type, comment) => {
|
||||
return comment.action_summaries
|
||||
@@ -176,3 +177,13 @@ export function buildUrl({protocol, hostname, port, pathname, search, hash} = wi
|
||||
}
|
||||
return `${protocol}//${hostname}${port ? `:${port}` : ''}${pathname}${search}${hash}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* getSlotFragmentSpreads will return a string in the
|
||||
* expected format for slot fragments, given `slots` and `resource`.
|
||||
* e.g. `getSlotFragmentSpreads(['slotName'], 'root')` returns
|
||||
* `...TalkSlot_SlotName_root`.
|
||||
*/
|
||||
export function getSlotFragmentSpreads(slots, resource) {
|
||||
return `...${slots.map((s) => `TalkSlot_${capitalize(s)}_${resource}`).join('\n...')}\n`;
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export {isTagged, insertCommentsSorted} from 'coral-framework/utils';
|
||||
export {getSlotFragmentSpreads} from 'coral-framework/utils';
|
||||
|
||||
Reference in New Issue
Block a user