mirror of
https://github.com/wassname/talk.git
synced 2026-07-01 14:54:42 +08:00
Merge branch 'master' into story-134624635
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
test
|
||||
.git
|
||||
|
||||
@@ -12,4 +12,3 @@ dump.rdb
|
||||
*.cfg
|
||||
.idea/
|
||||
coverage/
|
||||
yarn.lock
|
||||
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
FROM node:7
|
||||
|
||||
# Install yarn
|
||||
RUN npm install -g yarn
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
@@ -12,9 +15,9 @@ EXPOSE 5000
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install --production
|
||||
RUN yarn install --production
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
CMD [ "npm", "start" ]
|
||||
CMD [ "yarn", "start" ]
|
||||
|
||||
+6
-6
@@ -27,7 +27,7 @@ Navigate to a directory.
|
||||
```
|
||||
git clone https://github.com/coralproject/talk
|
||||
cd talk
|
||||
npm install
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Environmental Variables
|
||||
@@ -42,7 +42,7 @@ Talk uses environmental variables for configuration. You can learn about them in
|
||||
Starting the server:
|
||||
|
||||
```
|
||||
npm start
|
||||
yarn start
|
||||
```
|
||||
|
||||
Browse to `http://localhost:3000` (or your custom port.)
|
||||
@@ -54,13 +54,13 @@ Our build process will build all front end components registered [here](https://
|
||||
One time build:
|
||||
|
||||
```
|
||||
npm build
|
||||
yarn build
|
||||
```
|
||||
|
||||
Build, then rebuild when a file is updated (development build):
|
||||
|
||||
```
|
||||
npm build-watch
|
||||
yarn build-watch
|
||||
```
|
||||
|
||||
|
||||
@@ -69,13 +69,13 @@ npm build-watch
|
||||
Run all tests once:
|
||||
|
||||
`
|
||||
npm test
|
||||
yarn test
|
||||
`
|
||||
|
||||
Run our end to end tests (will install Selenium and nightwatch):
|
||||
|
||||
`
|
||||
npm run e2e
|
||||
yarn e2e
|
||||
`
|
||||
|
||||
_Please ensure all tests are passing before submitting a PR!_
|
||||
|
||||
+12
-3
@@ -4,11 +4,20 @@ machine:
|
||||
services:
|
||||
- docker
|
||||
- redis
|
||||
environment:
|
||||
PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
|
||||
NODE_ENV: "test"
|
||||
|
||||
dependencies:
|
||||
override:
|
||||
- yarn
|
||||
cache_directories:
|
||||
- ~/.cache/yarn
|
||||
post:
|
||||
# Build the static assets
|
||||
- yarn build
|
||||
# Lint the project here, before tests are ran.
|
||||
- npm run lint
|
||||
- yarn lint
|
||||
|
||||
database:
|
||||
post:
|
||||
@@ -20,9 +29,9 @@ database:
|
||||
test:
|
||||
override:
|
||||
# Run the tests using the junit reporter.
|
||||
- MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml NPM_PACKAGE_CONFIG_MOCHA_REPORTER=mocha-junit-reporter npm run test
|
||||
- MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test
|
||||
# Run the e2e test suite
|
||||
- E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e npm run e2e
|
||||
- E2E_REPORT_PATH=$CIRCLE_TEST_REPORTS/e2e yarn e2e
|
||||
|
||||
deployment:
|
||||
release:
|
||||
|
||||
@@ -5,19 +5,19 @@ This app handles moderation for Talk (and maybe more later on)
|
||||
|
||||
## Installation
|
||||
|
||||
$ npm install
|
||||
$ yarn install
|
||||
$ cp config.sample.json config.json
|
||||
|
||||
Then change `config.json` to adjust it to your project
|
||||
|
||||
## Building for production
|
||||
|
||||
$ npm run build
|
||||
$ yarn build
|
||||
|
||||
The public folder has everything you need for deployment. You can just copy that folder to your favorite static web server
|
||||
|
||||
## Development
|
||||
|
||||
$ npm start
|
||||
$ yarn start
|
||||
|
||||
A development server will be running at http://localhost:4132
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
|
||||
.Reply {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import {ReplyBox, ReplyButton} from 'coral-plugin-replies';
|
||||
import FlagComment from 'coral-plugin-flags/FlagComment';
|
||||
import LikeButton from 'coral-plugin-likes/LikeButton';
|
||||
|
||||
import styles from './Comment.css';
|
||||
|
||||
const getAction = (type, comment) => comment.actions.filter((a) => a.type === type)[0];
|
||||
|
||||
class Comment extends React.Component {
|
||||
@@ -87,7 +89,7 @@ class Comment extends React.Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={parentId ? 'reply' : 'comment'}
|
||||
className={parentId ? `reply ${styles.Reply}` : `comment ${styles.Comment}`}
|
||||
id={`c_${comment.id}`}
|
||||
style={{marginLeft: depth * 30}}>
|
||||
<hr aria-hidden={true} />
|
||||
@@ -103,11 +105,6 @@ class Comment extends React.Component {
|
||||
<PubDate created_at={comment.created_at} />
|
||||
<Content body={comment.body} />
|
||||
<div className="commentActionsLeft">
|
||||
<ReplyButton
|
||||
onClick={() => setActiveReplyBox(comment.id)}
|
||||
parentCommentId={parentId || comment.id}
|
||||
currentUserId={currentUser && currentUser.id}
|
||||
banned={false} />
|
||||
<LikeButton
|
||||
like={like}
|
||||
id={comment.id}
|
||||
@@ -115,8 +112,14 @@ class Comment extends React.Component {
|
||||
deleteAction={deleteAction}
|
||||
showSignInDialog={showSignInDialog}
|
||||
currentUser={currentUser} />
|
||||
<ReplyButton
|
||||
onClick={() => setActiveReplyBox(comment.id)}
|
||||
parentCommentId={parentId || comment.id}
|
||||
currentUserId={currentUser && currentUser.id}
|
||||
banned={false} />
|
||||
</div>
|
||||
<div className="commentActionsRight">
|
||||
<PermalinkButton articleURL={asset.url} commentId={comment.id} />
|
||||
<FlagComment
|
||||
flag={flag}
|
||||
id={comment.id}
|
||||
@@ -125,7 +128,6 @@ class Comment extends React.Component {
|
||||
deleteAction={deleteAction}
|
||||
showSignInDialog={showSignInDialog}
|
||||
currentUser={currentUser} />
|
||||
<PermalinkButton articleURL={asset.url} commentId={comment.id} />
|
||||
</div>
|
||||
{
|
||||
activeReplyBox === comment.id
|
||||
|
||||
@@ -3,7 +3,7 @@ import {compose} from 'react-apollo';
|
||||
import {connect} from 'react-redux';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import {TabBar, Tab, TabContent, Spinner} from '../../coral-ui';
|
||||
import {TabBar, Tab, TabContent, Spinner} from 'coral-ui';
|
||||
|
||||
const {logout, showSignInDialog, requestConfirmEmail} = authActions;
|
||||
const {addNotification, clearNotification} = notificationActions;
|
||||
@@ -17,28 +17,25 @@ import Stream from './Stream';
|
||||
import InfoBox from 'coral-plugin-infobox/InfoBox';
|
||||
import Count from 'coral-plugin-comment-count/CommentCount';
|
||||
import CommentBox from 'coral-plugin-commentbox/CommentBox';
|
||||
import UserBox from '../../coral-sign-in/components/UserBox';
|
||||
import SignInContainer from '../../coral-sign-in/containers/SignInContainer';
|
||||
import UserBox from 'coral-sign-in/components/UserBox';
|
||||
import SignInContainer from 'coral-sign-in/containers/SignInContainer';
|
||||
import SuspendedAccount from 'coral-framework/components/SuspendedAccount';
|
||||
import ChangeDisplayNameContainer from '../../coral-sign-in/containers/ChangeDisplayNameContainer';
|
||||
import SuspendedAccount from '../../coral-framework/components/SuspendedAccount';
|
||||
import SettingsContainer from '../../coral-settings/containers/SettingsContainer';
|
||||
import RestrictedContent from '../../coral-framework/components/RestrictedContent';
|
||||
import ConfigureStreamContainer from '../../coral-configure/containers/ConfigureStreamContainer';
|
||||
import SettingsContainer from 'coral-settings/containers/SettingsContainer';
|
||||
import RestrictedContent from 'coral-framework/components/RestrictedContent';
|
||||
import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer';
|
||||
|
||||
class Embed extends Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
state = {activeTab: 0, showSignInDialog: false};
|
||||
|
||||
this.state = {
|
||||
activeTab: 0,
|
||||
showSignInDialog: false
|
||||
};
|
||||
changeTab = (tab) => {
|
||||
|
||||
this.changeTab = this.changeTab.bind(this);
|
||||
}
|
||||
// Everytime the comes from another tab, the Stream needs to be updated.
|
||||
if (tab === 0) {
|
||||
this.props.data.refetch();
|
||||
}
|
||||
|
||||
changeTab (tab) {
|
||||
this.setState({
|
||||
activeTab: tab
|
||||
});
|
||||
@@ -70,7 +67,6 @@ class Embed extends Component {
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
@@ -92,87 +88,90 @@ class Embed extends Component {
|
||||
minHeight: document.body.scrollHeight + 200
|
||||
} : {};
|
||||
|
||||
return <div style={expandForLogin}>
|
||||
{
|
||||
loading ? <Spinner/>
|
||||
: <div className="commentStream">
|
||||
if (loading) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={expandForLogin}>
|
||||
<div className="commentStream">
|
||||
<TabBar onChange={this.changeTab} activeTab={activeTab}>
|
||||
<Tab><Count count={asset.commentCount}/></Tab>
|
||||
<Tab>Settings</Tab>
|
||||
<Tab restricted={!isAdmin}>Configure Stream</Tab>
|
||||
</TabBar>
|
||||
{loggedIn && user && <UserBox user={user} logout={this.props.logout} />}
|
||||
<TabContent show={activeTab === 0}>
|
||||
{
|
||||
openStream
|
||||
? <div id="commentBox">
|
||||
<InfoBox
|
||||
content={asset.settings.infoBoxContent}
|
||||
enable={asset.settings.infoBoxEnable}
|
||||
/>
|
||||
<RestrictedContent restricted={false} restrictedComp={<SuspendedAccount />}>
|
||||
{
|
||||
user
|
||||
? <CommentBox
|
||||
commentPostedHandler={refetch}
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
assetId={asset.id}
|
||||
premod={asset.settings.moderation}
|
||||
isReply={false}
|
||||
currentUser={this.props.auth.user}
|
||||
banned={false}
|
||||
authorId={user.id}
|
||||
charCount={asset.settings.charCountEnable && asset.settings.charCount} />
|
||||
: null
|
||||
}
|
||||
</RestrictedContent>
|
||||
</div>
|
||||
: <p>{asset.settings.closedMessage}</p>
|
||||
}
|
||||
{!loggedIn && <SignInContainer offset={signInOffset}/>}
|
||||
{loggedIn && user && <ChangeDisplayNameContainer loggedIn={loggedIn} offset={signInOffset} user={user} />}
|
||||
<Stream
|
||||
refetch={refetch}
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
asset={asset}
|
||||
currentUser={user}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
comments={asset.comments} />
|
||||
<Notification
|
||||
notifLength={4500}
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={{text: null}}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 1}>
|
||||
<SettingsContainer
|
||||
loggedIn={loggedIn}
|
||||
userData={this.props.userData}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 2}>
|
||||
<RestrictedContent restricted={!loggedIn}>
|
||||
<ConfigureStreamContainer
|
||||
status={status}
|
||||
onClick={this.toggleStatus}
|
||||
/>
|
||||
</RestrictedContent>
|
||||
</TabContent>
|
||||
<Notification
|
||||
notifLength={4500}
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={this.props.notification}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>;
|
||||
{loggedIn && <UserBox user={user} logout={this.props.logout} changeTab={this.changeTab} />}
|
||||
<TabContent show={activeTab === 0}>
|
||||
{
|
||||
openStream
|
||||
? <div id="commentBox">
|
||||
<InfoBox
|
||||
content={asset.settings.infoBoxContent}
|
||||
enable={asset.settings.infoBoxEnable}
|
||||
/>
|
||||
<RestrictedContent restricted={false} restrictedComp={<SuspendedAccount />}>
|
||||
{
|
||||
user
|
||||
? <CommentBox
|
||||
commentPostedHandler={refetch}
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
assetId={asset.id}
|
||||
premod={asset.settings.moderation}
|
||||
isReply={false}
|
||||
currentUser={this.props.auth.user}
|
||||
banned={false}
|
||||
authorId={user.id}
|
||||
charCount={asset.settings.charCountEnable && asset.settings.charCount} />
|
||||
: null
|
||||
}
|
||||
</RestrictedContent>
|
||||
</div>
|
||||
: <p>{asset.settings.closedMessage}</p>
|
||||
}
|
||||
{!loggedIn && <SignInContainer offset={signInOffset}/>}
|
||||
{loggedIn && user && <ChangeDisplayNameContainer loggedIn={loggedIn} offset={signInOffset} user={user} />}
|
||||
<Stream
|
||||
refetch={refetch}
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
asset={asset}
|
||||
currentUser={user}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
comments={asset.comments} />
|
||||
<Notification
|
||||
notifLength={4500}
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={{text: null}}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 1}>
|
||||
<SettingsContainer
|
||||
loggedIn={loggedIn}
|
||||
userData={this.props.userData}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 2}>
|
||||
<RestrictedContent restricted={!loggedIn}>
|
||||
<ConfigureStreamContainer
|
||||
status={status}
|
||||
onClick={this.toggleStatus}
|
||||
/>
|
||||
</RestrictedContent>
|
||||
</TabContent>
|
||||
<Notification
|
||||
notifLength={4500}
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={this.props.notification}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +185,13 @@ const mapStateToProps = state => ({
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
requestConfirmEmail: () => dispatch(requestConfirmEmail()),
|
||||
loadAsset: (asset) => dispatch(fetchAssetSuccess(asset)),
|
||||
addNotification: (type, text) => dispatch(addNotification(type, text)),
|
||||
addNotification: (type, text) => {
|
||||
pym.sendMessage('getPosition');
|
||||
|
||||
pym.onMessage('position', position => {
|
||||
dispatch(addNotification(type, text, position));
|
||||
});
|
||||
},
|
||||
clearNotification: () => dispatch(clearNotification()),
|
||||
showSignInDialog: (offset) => dispatch(showSignInDialog(offset)),
|
||||
logout: () => dispatch(logout()),
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
html, body {
|
||||
width:auto;
|
||||
height:auto;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Lato', sans-serif;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
@@ -46,7 +51,6 @@ hr {
|
||||
/* Notification styles */
|
||||
#coral-notif {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
border: 0;
|
||||
background: rgb(105,105,105);
|
||||
color: white;
|
||||
@@ -76,6 +80,8 @@ hr {
|
||||
.commentStream {
|
||||
/* prevent absolutely positioned final permalink popover from being clipped */
|
||||
padding-bottom: 50px;
|
||||
min-height: 600px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Comment Box Styles */
|
||||
@@ -219,8 +225,8 @@ hr {
|
||||
.coral-plugin-flags-popup span {
|
||||
min-width: 280px;
|
||||
bottom: 36px;
|
||||
left: -190px;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.coral-plugin-flags-popup-form {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
export const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
|
||||
export const CLEAR_NOTIFICATION = 'CLEAR_NOTIFICATION';
|
||||
|
||||
export const addNotification = (notifType, text) => {
|
||||
export const addNotification = (notifType, text, position) => {
|
||||
return {
|
||||
type: ADD_NOTIFICATION,
|
||||
notifType,
|
||||
text
|
||||
text,
|
||||
position
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import {SnackBar} from 'coral-ui';
|
||||
|
||||
const Notification = (props) => {
|
||||
if (props.notification.text) {
|
||||
@@ -6,14 +7,16 @@ const Notification = (props) => {
|
||||
props.clearNotification();
|
||||
}, props.notifLength);
|
||||
}
|
||||
return <div>
|
||||
{
|
||||
props.notification.text &&
|
||||
<dialog open id='coral-notif' className={`coral-notif-${ props.notification.type}`}>
|
||||
{props.notification.text}
|
||||
</dialog>
|
||||
}
|
||||
</div>;
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
props.notification.text &&
|
||||
<SnackBar id='coral-notif' className={`coral-notif-${props.notification.type}`} position={props.notification.position}>
|
||||
{props.notification.text}
|
||||
</SnackBar>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notification;
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
/* Items Notifications */
|
||||
|
||||
import * as actions from '../actions/notification';
|
||||
import {fromJS} from 'immutable';
|
||||
import {Map} from 'immutable';
|
||||
|
||||
const initialState = fromJS({});
|
||||
const initialState = Map({
|
||||
text: '',
|
||||
type: '',
|
||||
position: 400
|
||||
});
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case actions.ADD_NOTIFICATION:
|
||||
return state.set('text', action.text).set('type', action.notifType);
|
||||
return state
|
||||
.merge({
|
||||
type: action.notifType,
|
||||
text: action.text,
|
||||
position: action.position
|
||||
});
|
||||
case actions.CLEAR_NOTIFICATION:
|
||||
return initialState;
|
||||
default:
|
||||
|
||||
@@ -1,51 +1,47 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Tooltip} from 'coral-ui';
|
||||
import FlagBio from '../coral-plugin-flags/FlagBio';
|
||||
import FlagBio from 'coral-plugin-flags/FlagBio';
|
||||
const packagename = 'coral-plugin-author-name';
|
||||
import styles from './styles.css';
|
||||
|
||||
export default class AuthorName extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showTooltip: false
|
||||
};
|
||||
state = {showTooltip: false}
|
||||
|
||||
this.handleMouseOver = this.handleMouseOver.bind(this);
|
||||
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
||||
handleClick = () => {
|
||||
this.setState(state => ({
|
||||
showTooltip: !state.showTooltip
|
||||
}));
|
||||
}
|
||||
|
||||
handleMouseOver () {
|
||||
this.setState({
|
||||
showTooltip: true
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseLeave () {
|
||||
this.setState({
|
||||
showTooltip: false
|
||||
});
|
||||
handleMouseLeave = () => {
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
showTooltip: false
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {author} = this.props;
|
||||
const {showTooltip} = this.state;
|
||||
return (
|
||||
<div
|
||||
className={`${packagename}-text`}
|
||||
onMouseOver={this.handleMouseOver}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
{author && author.name}
|
||||
{ showTooltip && author.settings.bio && <Tooltip>
|
||||
<div className={`${packagename}-text ${styles.container}`} onClick={this.handleClick} onMouseLeave={this.handleMouseLeave}>
|
||||
<a className={`${styles.authorName} ${author.settings.bio ? styles.hasBio : ''}`}>
|
||||
{author && author.name}
|
||||
{author.settings.bio ? <span className={`${styles.arrowDown} ${showTooltip ? styles.arrowUp : ''}`} /> : null}
|
||||
</a>
|
||||
{showTooltip && author.settings.bio
|
||||
&& (
|
||||
<Tooltip>
|
||||
<div className={`${packagename}-bio`}>
|
||||
{author.settings.bio}
|
||||
</div>
|
||||
<div className={`${packagename}-bio-flag`}>
|
||||
<FlagBio {...this.props}/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
.authorName {
|
||||
color: black;
|
||||
display: inline-block;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.hasBio {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.arrowDown {
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-top: -2px;
|
||||
margin-left: 2px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
border-bottom: 0;
|
||||
border-left: 3px solid transparent;
|
||||
border-right: 3px solid transparent;
|
||||
border-top: 3px solid #000000;
|
||||
}
|
||||
|
||||
.arrowUp {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 0;
|
||||
border-left: 3px solid transparent;
|
||||
border-right: 3px solid transparent;
|
||||
border-bottom: 3px solid black;
|
||||
}
|
||||
@@ -10,8 +10,8 @@ const getPopupMenu = [
|
||||
return {
|
||||
header: lang.t('step-1-header'),
|
||||
options: [
|
||||
{val: 'USERS', text: lang.t('flag-username')},
|
||||
{val: 'COMMENTS', text: lang.t('flag-comment')}
|
||||
{val: 'COMMENTS', text: lang.t('flag-comment')},
|
||||
{val: 'USERS', text: lang.t('flag-username')}
|
||||
],
|
||||
button: lang.t('continue'),
|
||||
sets: 'itemType'
|
||||
|
||||
@@ -3,6 +3,8 @@ import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from './translations';
|
||||
import onClickOutside from 'react-onclickoutside';
|
||||
const name = 'coral-plugin-permalinks';
|
||||
import {Button} from 'coral-ui';
|
||||
import styles from './styles.css';
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
@@ -32,40 +34,37 @@ class PermalinkButton extends React.Component {
|
||||
this.permalinkInput.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
this.setState({copySuccessful: true});
|
||||
this.setState({copySuccessful: true, copyFailure: null});
|
||||
} catch (err) {
|
||||
this.setState({copyFailure: true});
|
||||
this.setState({copyFailure: true, copySuccessful: null});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.setState({copyFailure: null, copySuccessful: null});
|
||||
}, 4500);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {copySuccessful, copyFailure} = this.state;
|
||||
return (
|
||||
<div className={`${name}-container`}>
|
||||
<button onClick={this.toggle} className={`${name}-button`}>
|
||||
<i className={`${name}-icon material-icons`} aria-hidden={true}>link</i>
|
||||
{lang.t('permalink.permalink')}
|
||||
</button>
|
||||
<div
|
||||
className={`${name}-popover ${this.state.popoverOpen ? 'active' : ''}`}>
|
||||
<div className={`${name}-popover ${styles.container} ${this.state.popoverOpen ? 'active' : ''}`}>
|
||||
<input
|
||||
className={`${name}-copy-field`}
|
||||
type='text'
|
||||
ref={input => this.permalinkInput = input}
|
||||
value={`${this.props.articleURL}#${this.props.commentId}`}
|
||||
onChange={() => {}} />
|
||||
<button className={`${name}-copy-button`} onClick={this.copyPermalink}>Copy</button>
|
||||
{
|
||||
this.state.copySuccessful ? <p className={`${name}-copied-text`}>copied to clipboard</p> : null
|
||||
}
|
||||
{
|
||||
this.state.copyFailure
|
||||
? <p className={`${name}-copied-error`}>copying to clipboard not supported in this browser. Use Cmd + C.</p>
|
||||
: null
|
||||
}
|
||||
<Button className={`${name}-copy-button ${copySuccessful ? styles.success : ''} ${copyFailure ? styles.failure : ''}`}
|
||||
onClick={this.copyPermalink} >
|
||||
{!copyFailure && !copySuccessful && 'Copy'}
|
||||
{copySuccessful && 'Copied'}
|
||||
{copyFailure && 'Not supported'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
.container {
|
||||
border-radius: 3px;
|
||||
padding: 15px 10px;
|
||||
box-sizing: border-box;
|
||||
/* box-shadow: 3px 3px 5px 0 rgba(0, 0, 0, 0.3); */
|
||||
border: solid 1px rgba(153, 153, 153, 0.33);
|
||||
width: auto;
|
||||
margin: 0 auto;
|
||||
left: 0;
|
||||
margin: 0 5px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
margin-top: -13px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
border: 10px solid transparent;
|
||||
border-top-color: white;
|
||||
position: absolute;
|
||||
right: 8.69em;
|
||||
bottom: -20px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&::after{
|
||||
content: '';
|
||||
border: 10px solid transparent;
|
||||
border-top-color: rgba(153, 153, 153, 0.33);
|
||||
position: absolute;
|
||||
right: 8.69em;
|
||||
bottom: -21px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
input {
|
||||
display: inline-block;
|
||||
width: calc(100% - 78px);
|
||||
padding: 8px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px #e0e0e0;
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
button {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
background-color: #e0e0e0;
|
||||
font-size: 1em;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 2px;
|
||||
transition: background-color 0.4s ease;
|
||||
|
||||
&:hover{
|
||||
color: black;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: #00897B;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.failure {
|
||||
background-color: #FF5252;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"en": {
|
||||
"permalink": {
|
||||
"permalink": "Permalink"
|
||||
"permalink": "Link"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"permalink": {
|
||||
"permalink": "Enlace permanente"
|
||||
"permalink": "Enlace"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,11 @@ import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../translations';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const UserBox = ({className, user, logout, ...props}) => (
|
||||
<div
|
||||
className={`${styles.userBox} ${className ? className : ''}`}
|
||||
{...props}
|
||||
>
|
||||
const UserBox = ({className, user, logout, changeTab}) => (
|
||||
<div className={`${styles.userBox} ${className ? className : ''}`}>
|
||||
{lang.t('signIn.loggedInAs')}
|
||||
<a>{user.displayName}</a>. {lang.t('signIn.notYou')}
|
||||
<a onClick={logout} id='logout'>{lang.t('signIn.logout')}</a>
|
||||
<a onClick={() => changeTab(1)}>{user.displayName}</a>. {lang.t('signIn.notYou')}
|
||||
<a className={styles.logout} onClick={logout} id='logout'>{lang.t('signIn.logout')}</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -93,7 +93,10 @@ input.error{
|
||||
margin: 0px;
|
||||
margin-left: 4px;
|
||||
padding-bottom: 2px;
|
||||
border-bottom: solid 1px black;
|
||||
}
|
||||
|
||||
.userBox .logout {
|
||||
border-bottom: solid 1px black;
|
||||
}
|
||||
|
||||
.attention {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import styles from './Checkbox.css';
|
||||
|
||||
export default ({name, cStyle = 'base', onChange, label, className, info, ...props}) => (
|
||||
export default ({name, cStyle = 'base', onChange, label, className, info, ...attrs}) => (
|
||||
<label className={`${styles.label} ${styles[`type--${cStyle}`]} ${className}`} htmlFor={name}>
|
||||
<input type="checkbox" id={name} name={name} onChange={onChange} {...props} />
|
||||
<input type="checkbox" id={name} name={name} onChange={onChange} {...attrs} />
|
||||
<span className={styles.checkbox}></span>
|
||||
{label && <span>{label}</span>}
|
||||
{info && (
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
.SnackBar {
|
||||
position: fixed;
|
||||
cursor: default;
|
||||
background-color: #323232;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
will-change: transform;
|
||||
transition: transform .25s cubic-bezier(.4,0,1,1);
|
||||
pointer-events: none;
|
||||
padding: 18px 14px;
|
||||
vertical-align: middle;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import styles from './SnackBar.css';
|
||||
|
||||
const SnackBar = ({children, className, position, ...attrs}) => {
|
||||
return (
|
||||
<div className={`${styles.SnackBar} ${className}`}
|
||||
style={ position ? {top: `${position}px`} : fixedStyle}
|
||||
{...attrs} >
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const fixedStyle = {bottom: '200px', top: 'auto'};
|
||||
|
||||
export default SnackBar;
|
||||
@@ -3,7 +3,7 @@
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
border: solid 1px #2376D8;
|
||||
top: 33px;
|
||||
top: 48px;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
background: white;
|
||||
|
||||
@@ -16,3 +16,4 @@ export {default as Card} from './components/Card';
|
||||
export {default as FormField} from './components/FormField';
|
||||
export {default as Success} from './components/Success';
|
||||
export {default as Pager} from './components/Pager';
|
||||
export {default as SnackBar} from './components/SnackBar';
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
version: '2'
|
||||
services:
|
||||
|
||||
talk:
|
||||
image: coralproject/talk:latest
|
||||
build: .
|
||||
restart: always
|
||||
ports:
|
||||
- "5000:5000"
|
||||
- "2525:2525"
|
||||
environment:
|
||||
- "TALK_PORT=5000"
|
||||
- "TALK_MONGO_URL=mongodb://mongo"
|
||||
- "TALK_REDIS_URL=redis://redis"
|
||||
depends_on:
|
||||
- mongo
|
||||
- redis
|
||||
|
||||
mongo:
|
||||
image: mongo:3.2
|
||||
restart: always
|
||||
volumes:
|
||||
- mongo:/data/db
|
||||
|
||||
redis:
|
||||
image: redis:3.2-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- redis:/data
|
||||
|
||||
volumes:
|
||||
mongo:
|
||||
external: false
|
||||
redis:
|
||||
external: false
|
||||
@@ -112,7 +112,7 @@ http://eslint.org/docs/rules/#best-practices
|
||||
|
||||
## Lint the code
|
||||
```js
|
||||
npm run lint
|
||||
yarn lint
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -71,10 +71,10 @@ The `pree2e` script will create 3 users: a Commenter, a Moderator, and an Admin
|
||||
|
||||
## Run the tests
|
||||
Run Talk
|
||||
`dotenv npm run start`
|
||||
`dotenv yarn start`
|
||||
|
||||
Run e2e tests
|
||||
`npm run e2e`
|
||||
`yarn e2e`
|
||||
|
||||
|
||||
## Advanced Nightwatch and Selenium Settings
|
||||
|
||||
@@ -73,7 +73,7 @@ const getCountsByParentID = (context, parent_ids) => {
|
||||
* @param {Object} context graph context
|
||||
* @param {Object} query query terms to apply to the comments query
|
||||
*/
|
||||
const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, limit, cursor, sort}) => {
|
||||
const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, author_id, limit, cursor, sort}) => {
|
||||
let comments = CommentModel.find();
|
||||
|
||||
// Only administrators can search for comments with statuses that are not
|
||||
@@ -100,6 +100,10 @@ const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, limit,
|
||||
});
|
||||
}
|
||||
|
||||
if (user && (user.hasRoles('ADMIN') || user.id === author_id)) {
|
||||
comments = comments.where({author_id});
|
||||
}
|
||||
|
||||
if (asset_id) {
|
||||
comments = comments.where({asset_id});
|
||||
}
|
||||
|
||||
+9
-9
@@ -6,27 +6,27 @@
|
||||
"scripts": {
|
||||
"start": "./bin/cli serve --jobs",
|
||||
"dev-start": "nodemon --config .nodemon.json --exec \"./bin/cli -c .env serve --jobs\"",
|
||||
"build": "NODE_ENV=production webpack --config webpack.config.js --bail",
|
||||
"build-watch": "NODE_ENV=development webpack --config webpack.config.dev.js --watch",
|
||||
"build": "NODE_ENV=production webpack -p --config webpack.config.js --bail",
|
||||
"build-watch": "NODE_ENV=development webpack --config webpack.config.js --watch",
|
||||
"lint": "eslint bin/* .",
|
||||
"lint-fix": "eslint bin/* . --fix",
|
||||
"test": "TEST_MODE=unit NODE_ENV=test mocha -R ${NPM_PACKAGE_CONFIG_MOCHA_REPORTER:-spec}",
|
||||
"test": "TEST_MODE=unit NODE_ENV=test mocha -R ${MOCHA_REPORTER:-spec}",
|
||||
"test-cover": "TEST_MODE=unit NODE_ENV=test istanbul cover _mocha --report text --check-coverage -- -R spec",
|
||||
"pree2e": "NODE_ENV=test TALK_PORT=3011 scripts/pree2e.sh",
|
||||
"e2e": "NODE_ENV=test nightwatch",
|
||||
"poste2e": "NODE_ENV=test scripts/poste2e.sh",
|
||||
"embed-start": "NODE_ENV=development npm run build && ./bin/cli serve --jobs",
|
||||
"postinstall": "npm run build"
|
||||
"embed-start": "NODE_ENV=development yarn build && ./bin/cli serve --jobs",
|
||||
"heroku-postbuild": "yarn build"
|
||||
},
|
||||
"config": {
|
||||
"pre-git": {
|
||||
"commit-msg": [],
|
||||
"pre-commit": [
|
||||
"npm run lint",
|
||||
"npm test"
|
||||
"yarn lint",
|
||||
"yarn test"
|
||||
],
|
||||
"pre-push": [
|
||||
"npm test"
|
||||
"yarn test"
|
||||
],
|
||||
"post-commit": [],
|
||||
"post-merge": []
|
||||
@@ -159,7 +159,7 @@
|
||||
"style-loader": "^0.13.1",
|
||||
"supertest": "^2.0.1",
|
||||
"timeago.js": "^2.0.3",
|
||||
"webpack": "^1.13.3"
|
||||
"webpack": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
|
||||
@@ -28,8 +28,8 @@ describe ('notificationsReducer', () => {
|
||||
type: 'test'
|
||||
});
|
||||
const result = notificationReducer(store, action);
|
||||
expect(result.get('text')).to.equal(undefined);
|
||||
expect(result.get('type')).to.equal(undefined);
|
||||
expect(result.get('text')).to.equal('');
|
||||
expect(result.get('type')).to.equal('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+25
-1
@@ -28,6 +28,7 @@
|
||||
<script type='text/javascript' src='<%= basePath %>/pym.v1.min.js'></script>
|
||||
<script>
|
||||
var ready = false;
|
||||
var notificationOffset = 200;
|
||||
|
||||
// default to using the window.location
|
||||
var url = window.location.protocol + '//' + window.location.host + window.location.pathname;
|
||||
@@ -38,7 +39,21 @@
|
||||
<%}%>
|
||||
|
||||
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream?asset_url=' + encodeURIComponent(url), {title: 'Talk Comments', id:'coralStreamIframe', name: 'coralStreamIframe', asset_url: url});
|
||||
pymParent.onMessage('height', function(height) {document.querySelector('#coralStreamEmbed iframe').height = height + 'px'})
|
||||
pymParent.onMessage('height', function(height) {
|
||||
document.querySelector('#coralStreamEmbed iframe').height = height + 'px';
|
||||
})
|
||||
|
||||
|
||||
pymParent.onMessage('getPosition', function(notification) {
|
||||
var position = viewport().height + document.body.scrollTop;
|
||||
|
||||
if (position > notificationOffset) {
|
||||
position = position - notificationOffset;
|
||||
}
|
||||
|
||||
pymParent.sendMessage('position', position);
|
||||
});
|
||||
|
||||
pymParent.onMessage('childReady', function () {
|
||||
var interval = setInterval(function () {
|
||||
if (ready) {
|
||||
@@ -65,6 +80,15 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
ready = true;
|
||||
});
|
||||
|
||||
function viewport() {
|
||||
var e = window, a = 'inner';
|
||||
if ( !( 'innerWidth' in window ) ){
|
||||
a = 'client';
|
||||
e = document.documentElement || document.body;
|
||||
}
|
||||
return { width : e[ a+'Width' ] , height : e[ a+'Height' ] }
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
const path = require('path');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const precss = require('precss');
|
||||
const Copy = require('copy-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
|
||||
// Edit the build targets and embeds below.
|
||||
|
||||
const buildTargets = [
|
||||
'coral-admin'
|
||||
];
|
||||
|
||||
const buildEmbeds = [
|
||||
'stream'
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
devtool: 'inline-source-map',
|
||||
entry: buildTargets.reduce((entry, target) => {
|
||||
|
||||
// Add the entry for the bundle.
|
||||
entry[`${target}/bundle`] = [
|
||||
'babel-polyfill',
|
||||
path.join(__dirname, 'client/', target, '/src/index')
|
||||
];
|
||||
|
||||
return entry;
|
||||
}, buildEmbeds.reduce((entry, embed) => {
|
||||
|
||||
// Add the entry for the bundle.
|
||||
entry[`embed/${embed}/bundle`] = [
|
||||
'babel-polyfill',
|
||||
path.join(__dirname, 'client/', `coral-embed-${embed}`, '/src/index')
|
||||
];
|
||||
|
||||
return entry;
|
||||
}, {})),
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
loader: 'babel',
|
||||
exclude: /node_modules/,
|
||||
test: /\.js$/
|
||||
},
|
||||
{
|
||||
loader: 'json',
|
||||
test: /\.json$/,
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
loaders: [
|
||||
'style-loader',
|
||||
'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
||||
'postcss-loader'
|
||||
],
|
||||
test: /.css$/,
|
||||
},
|
||||
{
|
||||
loader: 'url-loader?limit=100000',
|
||||
test: /\.png$/
|
||||
},
|
||||
{
|
||||
loader: 'file-loader',
|
||||
test: /\.(jpg|png|gif|svg)$/
|
||||
},
|
||||
{
|
||||
loader: 'url?limit=100000',
|
||||
test: /\.woff$/
|
||||
},
|
||||
{
|
||||
test: /\.(graphql|gql)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'graphql-tag/loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new Copy([
|
||||
...buildEmbeds.map(embed => ({
|
||||
from: path.join(__dirname, 'client', `coral-embed-${embed}`, 'style'),
|
||||
to: path.join(__dirname, 'dist', 'embed', embed)
|
||||
})),
|
||||
{
|
||||
from: path.join(__dirname, 'client', 'lib'),
|
||||
to: path.join(__dirname, 'dist', 'embed', 'stream')
|
||||
}
|
||||
]),
|
||||
autoprefixer,
|
||||
precss,
|
||||
new webpack.ProvidePlugin({
|
||||
'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
'NODE_ENV': `"${process.env.NODE_ENV}"`,
|
||||
'VERSION': `"${require('./package.json').version}"`
|
||||
}
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
root: [
|
||||
path.join(__dirname, 'client'),
|
||||
...buildTargets.map(target => path.join(__dirname, 'client', target, 'src')),
|
||||
...buildEmbeds.map(embed => path.join(__dirname, 'client', `coral-embed-${embed}`, 'src'))
|
||||
]
|
||||
},
|
||||
postcss: require('./postcss.config.js')
|
||||
};
|
||||
+107
-13
@@ -1,17 +1,111 @@
|
||||
const path = require('path');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const precss = require('precss');
|
||||
const Copy = require('copy-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
const devConfig = require('./webpack.config.dev');
|
||||
|
||||
// Disable source maps.
|
||||
devConfig.devtool = null;
|
||||
// Edit the build targets and embeds below.
|
||||
|
||||
devConfig.plugins = devConfig.plugins.concat([
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.OccurrenceOrderPlugin(),
|
||||
new webpack.optimize.DedupePlugin()
|
||||
]);
|
||||
const buildTargets = [
|
||||
'coral-admin'
|
||||
];
|
||||
|
||||
module.exports = devConfig;
|
||||
const buildEmbeds = [
|
||||
'stream'
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
devtool: 'cheap-source-map',
|
||||
entry: buildTargets.reduce((entry, target) => {
|
||||
|
||||
// Add the entry for the bundle.
|
||||
entry[`${target}/bundle`] = [
|
||||
'babel-polyfill',
|
||||
path.join(__dirname, 'client/', target, '/src/index')
|
||||
];
|
||||
|
||||
return entry;
|
||||
}, buildEmbeds.reduce((entry, embed) => {
|
||||
|
||||
// Add the entry for the bundle.
|
||||
entry[`embed/${embed}/bundle`] = [
|
||||
'babel-polyfill',
|
||||
path.join(__dirname, 'client/', `coral-embed-${embed}`, '/src/index')
|
||||
];
|
||||
|
||||
return entry;
|
||||
}, {})),
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
test: /\.js$/
|
||||
},
|
||||
{
|
||||
loader: 'json-loader',
|
||||
test: /\.json$/,
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
loaders: [
|
||||
'style-loader',
|
||||
'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
||||
'postcss-loader'
|
||||
],
|
||||
test: /.css$/,
|
||||
},
|
||||
{
|
||||
loader: 'url-loader?limit=100000',
|
||||
test: /\.png$/
|
||||
},
|
||||
{
|
||||
loader: 'file-loader',
|
||||
test: /\.(jpg|png|gif|svg)$/
|
||||
},
|
||||
{
|
||||
loader: 'url-loader?limit=100000',
|
||||
test: /\.woff$/
|
||||
},
|
||||
{
|
||||
test: /\.(graphql|gql)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'graphql-tag/loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new Copy([
|
||||
...buildEmbeds.map(embed => ({
|
||||
from: path.join(__dirname, 'client', `coral-embed-${embed}`, 'style'),
|
||||
to: path.join(__dirname, 'dist', 'embed', embed)
|
||||
})),
|
||||
{
|
||||
from: path.join(__dirname, 'client', 'lib'),
|
||||
to: path.join(__dirname, 'dist', 'embed', 'stream')
|
||||
}
|
||||
]),
|
||||
autoprefixer,
|
||||
precss,
|
||||
new webpack.ProvidePlugin({
|
||||
'fetch': 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch'
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
'VERSION': `"${require('./package.json').version}"`
|
||||
}
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
modules: [
|
||||
path.resolve(__dirname, 'client'),
|
||||
...buildTargets.map(target => path.join(__dirname, 'client', target, 'src')),
|
||||
...buildEmbeds.map(embed => path.join(__dirname, 'client', `coral-embed-${embed}`, 'src')),
|
||||
'node_modules'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user