mirror of
https://github.com/wassname/talk.git
synced 2026-07-08 06:59:31 +08:00
Implement excludeIf for the plugins API
This commit is contained in:
@@ -6,6 +6,8 @@ import t from 'coral-framework/services/i18n';
|
||||
|
||||
import {TabBar, Tab, TabContent, TabPane} from 'coral-ui';
|
||||
import ProfileContainer from 'coral-settings/containers/ProfileContainer';
|
||||
import Popup from 'coral-framework/components/Popup';
|
||||
import IfSlotIsNotEmpty from 'coral-framework/components/IfSlotIsNotEmpty';
|
||||
import ConfigureStreamContainer
|
||||
from 'coral-configure/containers/ConfigureStreamContainer';
|
||||
import cn from 'classnames';
|
||||
@@ -26,12 +28,24 @@ export default class Embed extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {activeTab, commentId} = this.props;
|
||||
const {activeTab, commentId, auth: {showSignInDialog, signInDialogFocus}, blurSignInDialog, focusSignInDialog, hideSignInDialog} = this.props;
|
||||
const {user} = this.props.auth;
|
||||
const hasHighlightedComment = !!commentId;
|
||||
|
||||
return (
|
||||
<div className={cn('talk-embed-stream', {'talk-embed-stream-highlight-comment': hasHighlightedComment})}>
|
||||
<IfSlotIsNotEmpty slot="login">
|
||||
<Popup
|
||||
href='/embed/stream/login'
|
||||
title='Login'
|
||||
features='menubar=0,resizable=0,width=500,height=550,top=200,left=500'
|
||||
open={showSignInDialog}
|
||||
focus={signInDialogFocus}
|
||||
onFocus={focusSignInDialog}
|
||||
onBlur={blurSignInDialog}
|
||||
onClose={hideSignInDialog}
|
||||
/>
|
||||
</IfSlotIsNotEmpty>
|
||||
<TabBar
|
||||
onTabClick={this.changeTab}
|
||||
activeTab={activeTab}
|
||||
|
||||
@@ -75,6 +75,7 @@ class Stream extends React.Component {
|
||||
viewAllComments,
|
||||
auth: {loggedIn, user},
|
||||
removeTag,
|
||||
pluginConfig,
|
||||
editName
|
||||
} = this.props;
|
||||
const {keepCommentBox} = this.state;
|
||||
@@ -98,6 +99,7 @@ class Stream extends React.Component {
|
||||
};
|
||||
|
||||
const showCommentBox = loggedIn && ((!banned && !temporarilySuspended && !highlightedComment) || keepCommentBox);
|
||||
const streamTabProps = {data, root, asset};
|
||||
|
||||
if (!comment && !comments) {
|
||||
console.error('Talk: No comments came back from the graph given that query. Please, check the query params.');
|
||||
@@ -207,13 +209,11 @@ class Stream extends React.Component {
|
||||
<Slot fill="streamFilter" />
|
||||
</div>
|
||||
<TabBar activeTab={activeStreamTab} onTabClick={setActiveStreamTab} sub>
|
||||
{getSlotComponents('streamTabs').map((PluginComponent) => (
|
||||
{getSlotComponents('streamTabs', pluginConfig, streamTabProps).map((PluginComponent) => (
|
||||
<Tab tabId={PluginComponent.talkPluginName} key={PluginComponent.talkPluginName}>
|
||||
<PluginComponent
|
||||
{...streamTabProps}
|
||||
active={activeStreamTab === PluginComponent.talkPluginName}
|
||||
data={data}
|
||||
root={root}
|
||||
asset={asset}
|
||||
/>
|
||||
</Tab>
|
||||
))}
|
||||
@@ -222,12 +222,10 @@ class Stream extends React.Component {
|
||||
</Tab>
|
||||
</TabBar>
|
||||
<TabContent activeTab={activeStreamTab} sub>
|
||||
{getSlotComponents('streamTabPanes').map((PluginComponent) => (
|
||||
{getSlotComponents('streamTabPanes', pluginConfig, streamTabProps).map((PluginComponent) => (
|
||||
<TabPane tabId={PluginComponent.talkPluginName} key={PluginComponent.talkPluginName}>
|
||||
<PluginComponent
|
||||
data={data}
|
||||
root={root}
|
||||
asset={asset}
|
||||
{...streamTabProps}
|
||||
/>
|
||||
</TabPane>
|
||||
))}
|
||||
|
||||
@@ -19,7 +19,7 @@ import t from 'coral-framework/services/i18n';
|
||||
|
||||
import {setActiveTab} from '../actions/embed';
|
||||
|
||||
const {logout, checkLogin} = authActions;
|
||||
const {logout, checkLogin, focusSignInDialog, blurSignInDialog, hideSignInDialog} = authActions;
|
||||
const {fetchAssetSuccess} = assetActions;
|
||||
|
||||
class EmbedContainer extends React.Component {
|
||||
@@ -185,6 +185,9 @@ const mapDispatchToProps = (dispatch) =>
|
||||
setActiveTab,
|
||||
fetchAssetSuccess,
|
||||
addNotification,
|
||||
focusSignInDialog,
|
||||
blurSignInDialog,
|
||||
hideSignInDialog,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
@@ -310,7 +310,8 @@ const mapStateToProps = (state) => ({
|
||||
previousTab: state.embed.previousTab,
|
||||
activeStreamTab: state.stream.activeTab,
|
||||
previousStreamTab: state.stream.previousTab,
|
||||
commentClassNames: state.stream.commentClassNames
|
||||
commentClassNames: state.stream.commentClassNames,
|
||||
pluginConfig: state.config.plugin_config,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
|
||||
@@ -7,45 +7,33 @@ import pym from '../services/pym';
|
||||
|
||||
import {resetWebsocket} from 'coral-framework/services/client';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import {isSlotEmpty} from 'plugin-api/beta/client/services';
|
||||
|
||||
export const showSignInDialog = () => (dispatch, getState) => {
|
||||
if (isSlotEmpty('login')) {
|
||||
return;
|
||||
}
|
||||
const signInPopUp = window.open(
|
||||
'/embed/stream/login',
|
||||
'Login',
|
||||
'menubar=0,resizable=0,width=500,height=550,top=200,left=500'
|
||||
);
|
||||
export const showSignInDialog = () => ({
|
||||
type: actions.SHOW_SIGNIN_DIALOG,
|
||||
});
|
||||
|
||||
// Workaround odd behavior in older WebKit versions, where
|
||||
// onunload is called twice. (Encountered in IOS 8.3)
|
||||
let loaded = false;
|
||||
signInPopUp.onload = () => {
|
||||
loaded = true;
|
||||
|
||||
// Fire some actions inside the popups reducer, to initialize required state.
|
||||
const required = getState().asset.toJS().settings.requireEmailConfirmation;
|
||||
const redirectUri = getState().auth.toJS().redirectUri;
|
||||
signInPopUp.coralStore.dispatch(setRequireEmailVerification(required));
|
||||
signInPopUp.coralStore.dispatch(setRedirectUri(redirectUri));
|
||||
};
|
||||
|
||||
// Use `onunload` instead of `onbeforeunload` which is not supported in IOS Safari.
|
||||
signInPopUp.onunload = () => {
|
||||
if (loaded) {
|
||||
dispatch(checkLogin());
|
||||
}
|
||||
};
|
||||
|
||||
dispatch({type: actions.SHOW_SIGNIN_DIALOG});
|
||||
};
|
||||
export const hideSignInDialog = () => (dispatch) => {
|
||||
if (window.opener && window.opener !== window) {
|
||||
|
||||
// TODO: We need to address this when we refactor the
|
||||
// login popup out of the embed.
|
||||
|
||||
// we are in a popup
|
||||
window.close();
|
||||
} else {
|
||||
dispatch(checkLogin());
|
||||
}
|
||||
dispatch({type: actions.HIDE_SIGNIN_DIALOG});
|
||||
window.close();
|
||||
};
|
||||
|
||||
export const focusSignInDialog = () => ({
|
||||
type: actions.FOCUS_SIGNIN_DIALOG,
|
||||
});
|
||||
|
||||
export const blurSignInDialog = () => ({
|
||||
type: actions.BLUR_SIGNIN_DIALOG,
|
||||
});
|
||||
|
||||
export const createUsernameRequest = () => ({
|
||||
type: actions.CREATE_USERNAME_REQUEST
|
||||
});
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {isSlotEmpty} from 'coral-framework/helpers/plugins';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function IfSlotIsEmpty({slot, className, pluginConfig, component: Component = 'div', children, ...rest}) {
|
||||
return (
|
||||
<Component className={className}>
|
||||
{isSlotEmpty(slot, pluginConfig, rest) ? children : null}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
||||
IfSlotIsEmpty.propTypes = {
|
||||
slot: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({pluginConfig: state.config.plugin_config});
|
||||
|
||||
export default connect(mapStateToProps, null)(IfSlotIsEmpty);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {isSlotEmpty} from 'coral-framework/helpers/plugins';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function IfSlotIsNotEmpty({slot, className, pluginConfig, component: Component = 'div', children, ...rest}) {
|
||||
return (
|
||||
<Component className={className}>
|
||||
{!isSlotEmpty(slot, pluginConfig, rest) ? children : null}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
||||
IfSlotIsNotEmpty.propTypes = {
|
||||
slot: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({pluginConfig: state.config.plugin_config});
|
||||
|
||||
export default connect(mapStateToProps, null)(IfSlotIsNotEmpty);
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class Popup extends Component {
|
||||
ref = null;
|
||||
detectCloseInterval = null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
if (props.open) {
|
||||
this.openWindow(props);
|
||||
}
|
||||
}
|
||||
|
||||
openWindow(props = this.props) {
|
||||
this.ref = window.open(
|
||||
props.href,
|
||||
props.title,
|
||||
props.features,
|
||||
);
|
||||
|
||||
this.setCallbacks();
|
||||
}
|
||||
|
||||
setCallbacks() {
|
||||
this.ref.onload = () => {
|
||||
clearInterval(this.detectCloseInterval);
|
||||
this.onLoad();
|
||||
};
|
||||
|
||||
this.ref.onfocus = () => {
|
||||
this.onFocus();
|
||||
};
|
||||
|
||||
this.ref.onblur = () => {
|
||||
this.onBlur();
|
||||
};
|
||||
|
||||
// Use `onunload` instead of `onbeforeunload` which is not supported in IOS Safari.
|
||||
this.ref.onunload = () => {
|
||||
this.onUnload();
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (this.ref.onload === null) {
|
||||
this.setCallbacks();
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 50);
|
||||
|
||||
this.detectCloseInterval = setInterval(() => {
|
||||
if (this.ref.closed) {
|
||||
clearInterval(this.detectCloseInterval);
|
||||
this.onClose();
|
||||
}
|
||||
}, 50);
|
||||
};
|
||||
}
|
||||
|
||||
closeWindow() {
|
||||
if (this.ref) {
|
||||
if (!this.ref.closed) {
|
||||
this.ref.close();
|
||||
}
|
||||
this.ref = null;
|
||||
}
|
||||
}
|
||||
|
||||
focusWindow() {
|
||||
if (this.ref && !this.ref.closed) {
|
||||
this.ref.focus();
|
||||
}
|
||||
}
|
||||
|
||||
blurWindow() {
|
||||
if (this.ref && !this.ref.closed) {
|
||||
this.ref.blur();
|
||||
}
|
||||
}
|
||||
|
||||
onLoad = () => {
|
||||
if (this.props.onLoad) {
|
||||
this.props.onLoad();
|
||||
}
|
||||
}
|
||||
|
||||
onUnload = () => {
|
||||
if (this.props.onUnload) {
|
||||
this.props.onUnload();
|
||||
}
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
if (this.props.onClose) {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
onFocus = () => {
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus();
|
||||
}
|
||||
}
|
||||
|
||||
onBlur = () => {
|
||||
if (this.props.onBlur) {
|
||||
this.props.onBlur();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.open && !this.ref) {
|
||||
this.openWindow(nextProps);
|
||||
}
|
||||
|
||||
if (this.props.open && !nextProps.open) {
|
||||
this.closeWindow();
|
||||
}
|
||||
|
||||
if (!this.props.focus && nextProps.focus) {
|
||||
this.focusWindow();
|
||||
}
|
||||
|
||||
if (this.props.focus && !nextProps.focus) {
|
||||
this.blurWindow();
|
||||
}
|
||||
|
||||
if (this.props.href !== nextProps.href) {
|
||||
this.ref.location.href = nextProps.href;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.closeWindow();
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Popup.propTypes = {
|
||||
open: PropTypes.bool,
|
||||
focus: PropTypes.bool,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
onLoad: PropTypes.func,
|
||||
onUnload: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
href: PropTypes.string.isRequired,
|
||||
features: PropTypes.string,
|
||||
};
|
||||
@@ -4,14 +4,14 @@ import styles from './Slot.css';
|
||||
import {connect} from 'react-redux';
|
||||
import {getSlotElements} from 'coral-framework/helpers/plugins';
|
||||
|
||||
function Slot ({fill, inline = false, className, plugin_config: config, defaultComponent: DefaultComponent, ...rest}) {
|
||||
let children = getSlotElements(fill, {...rest, config});
|
||||
function Slot ({fill, inline = false, className, pluginConfig = {}, defaultComponent: DefaultComponent, ...rest}) {
|
||||
let children = getSlotElements(fill, pluginConfig, rest);
|
||||
if (children.length === 0 && DefaultComponent) {
|
||||
children = <DefaultComponent {...rest} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn({[styles.inline]: inline, [styles.debug]: config.debug}, className)}>
|
||||
<div className={cn({[styles.inline]: inline, [styles.debug]: pluginConfig.debug}, className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -21,7 +21,7 @@ Slot.propTypes = {
|
||||
fill: React.PropTypes.string
|
||||
};
|
||||
|
||||
const mapStateToProps = ({config: {plugin_config = {}}}) => ({plugin_config});
|
||||
const mapStateToProps = (state) => ({pluginConfig: state.config.plugin_config});
|
||||
|
||||
export default connect(mapStateToProps, null)(Slot);
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ export const CLEAN_STATE = 'CLEAN_STATE';
|
||||
|
||||
export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG';
|
||||
export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG';
|
||||
export const FOCUS_SIGNIN_DIALOG = 'FOCUS_SIGNIN_DIALOG';
|
||||
export const BLUR_SIGNIN_DIALOG = 'BLUR_SIGNIN_DIALOG';
|
||||
|
||||
export const CREATE_USERNAME_REQUEST = 'CREATE_USERNAME_REQUEST';
|
||||
export const CREATE_USERNAME_SUCCESS = 'CREATE_USERNAME_SUCCESS';
|
||||
|
||||
@@ -7,32 +7,31 @@ 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 {injectReducers} from 'coral-framework/services/store';
|
||||
import camelize from './camelize';
|
||||
|
||||
export function getSlotComponents(slot) {
|
||||
const pluginConfig = getStore().getState().config.plugin_config;
|
||||
|
||||
export function getSlotComponents(slot, pluginConfig, props = {}) {
|
||||
return flatten(plugins
|
||||
|
||||
// Filter out components that have slots and have been disabled in `plugin_config`
|
||||
// Filter out components that have slots and have been disabled in `plugin_config`
|
||||
.filter((o) => o.module.slots && (!pluginConfig || !pluginConfig[o.name] || !pluginConfig[o.name].disable_components))
|
||||
|
||||
.filter((o) => o.module.slots[slot])
|
||||
.map((o) => o.module.slots[slot])
|
||||
);
|
||||
)
|
||||
.filter((component) => !component.isExcluded || !component.isExcluded({...props, config: pluginConfig}));
|
||||
}
|
||||
|
||||
export function isSlotEmpty(slot) {
|
||||
return getSlotComponents(slot).length === 0;
|
||||
export function isSlotEmpty(slot, pluginConfig, props) {
|
||||
return getSlotComponents(slot, pluginConfig, props).length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns React Elements for given slot.
|
||||
*/
|
||||
export function getSlotElements(slot, props = {}) {
|
||||
return getSlotComponents(slot)
|
||||
.map((component, i) => React.createElement(component, {key: i, ...props}));
|
||||
export function getSlotElements(slot, pluginConfig, props = {}) {
|
||||
return getSlotComponents(slot, pluginConfig, props)
|
||||
.map((component, i) => React.createElement(component, {key: i, ...props, config: pluginConfig}));
|
||||
}
|
||||
|
||||
function getComponentFragments(components) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default (condition) => (BaseComponent) => {
|
||||
BaseComponent.isExcluded = condition;
|
||||
return BaseComponent;
|
||||
};
|
||||
@@ -1,14 +1,5 @@
|
||||
import React from 'react';
|
||||
import {getDisplayName} from '../helpers/hoc';
|
||||
|
||||
// TODO: revisit `filtering` after https://github.com/apollographql/graphql-anywhere/issues/38.
|
||||
export default (fragments) => (WrappedComponent) => {
|
||||
class WithFragments extends React.Component {
|
||||
render() {
|
||||
return <WrappedComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
WithFragments.fragments = fragments;
|
||||
WithFragments.displayName = `WithFragments(${getDisplayName(WrappedComponent)})`;
|
||||
return WithFragments;
|
||||
export default (fragments) => (BaseComponent) => {
|
||||
BaseComponent.fragments = fragments;
|
||||
return BaseComponent;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ const initialState = Map({
|
||||
loggedIn: false,
|
||||
user: null,
|
||||
showSignInDialog: false,
|
||||
signInDialogFocus: false,
|
||||
showCreateUsernameDialog: false,
|
||||
checkedInitialLogin: false,
|
||||
view: 'SIGNIN',
|
||||
@@ -29,13 +30,21 @@ const purge = (user) => {
|
||||
|
||||
export default function auth (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case actions.FOCUS_SIGNIN_DIALOG:
|
||||
return state
|
||||
.set('signInDialogFocus', true);
|
||||
case actions.BLUR_SIGNIN_DIALOG:
|
||||
return state
|
||||
.set('signInDialogFocus', false);
|
||||
case actions.SHOW_SIGNIN_DIALOG :
|
||||
return state
|
||||
.set('showSignInDialog', true);
|
||||
.set('showSignInDialog', true)
|
||||
.set('signInDialogFocus', true);
|
||||
case actions.HIDE_SIGNIN_DIALOG :
|
||||
return state.merge(Map({
|
||||
isLoading: false,
|
||||
showSignInDialog: false,
|
||||
signInDialogFocus: false,
|
||||
view: 'SIGNIN',
|
||||
error: '',
|
||||
passwordRequestFailure: null,
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export {default as withReaction} from './withReaction';
|
||||
export {default as withFragments} from 'coral-framework/hocs/withFragments';
|
||||
export {default as excludeIf} from 'coral-framework/hocs/excludeIf';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const pluginConfigSelector = (state) => state.config.pluginConfig;
|
||||
@@ -1,5 +1,6 @@
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import excludeIf from 'coral-framework/hocs/excludeIf';
|
||||
import Tab from '../components/Tab';
|
||||
|
||||
// TODO: This is just example code, and needs to replaced by an actual implementation.
|
||||
@@ -12,6 +13,7 @@ const enhance = compose(
|
||||
}
|
||||
}`,
|
||||
}),
|
||||
excludeIf((props) => props.asset.recentComments.length === 0)
|
||||
);
|
||||
|
||||
export default enhance(Tab);
|
||||
|
||||
Reference in New Issue
Block a user