mirror of
https://github.com/wassname/talk.git
synced 2026-07-01 01:27:39 +08:00
Merge branch 'master' into user-documentation
This commit is contained in:
@@ -4,7 +4,7 @@ import {Button, Icon} from 'react-mdl';
|
||||
import timeago from 'timeago.js';
|
||||
import styles from './CommentList.css';
|
||||
import I18n from 'coral-framework/i18n/i18n';
|
||||
import translations from '../translations';
|
||||
import translations from '../translations.json';
|
||||
|
||||
// Render a single comment for the list
|
||||
export default props => (
|
||||
|
||||
@@ -2,22 +2,26 @@ import React from 'react';
|
||||
import {Layout, Navigation, Drawer, Header} from 'react-mdl';
|
||||
import {Link} from 'react-router';
|
||||
import styles from './Header.css';
|
||||
import I18n from 'coral-framework/i18n/i18n';
|
||||
import translations from '../translations.json';
|
||||
|
||||
// App header. If we add a navbar it should be here
|
||||
export default (props) => (
|
||||
<Layout fixedDrawer>
|
||||
<Header title='Talk'>
|
||||
<Navigation>
|
||||
<Link className={styles.navLink} to={'/admin/'}>Moderate</Link>
|
||||
<Link className={styles.navLink} to={'/admin/configure'}>Configure</Link>
|
||||
<Link className={styles.navLink} to={'/admin/'}>{lang.t('configure.moderate')}</Link>
|
||||
<Link className={styles.navLink} to={'/admin/configure'}>{lang.t('Configure')}</Link>
|
||||
</Navigation>
|
||||
</Header>
|
||||
<Drawer>
|
||||
<Navigation>
|
||||
<Link className={styles.navLink} to={'/admin/'}>Moderate</Link>
|
||||
<Link className={styles.navLink} to={'/admin/configure'}>Configure</Link>
|
||||
<Link className={styles.navLink} to={'/admin/'}>{lang.t('configure.moderate')}</Link>
|
||||
<Link className={styles.navLink} to={'/admin/configure'}>{lang.t('configure.Configure')}</Link>
|
||||
</Navigation>
|
||||
</Drawer>
|
||||
{props.children}
|
||||
</Layout>
|
||||
);
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import I18n from 'coral-framework/i18n/i18n';
|
||||
import translations from '../translations';
|
||||
import translations from '../translations.json';
|
||||
import React from 'react';
|
||||
import Modal from 'components/Modal';
|
||||
import styles from './ModerationKeysModal.css';
|
||||
|
||||
@@ -2,13 +2,17 @@ import React from 'react';
|
||||
import {Navigation, Drawer} from 'react-mdl';
|
||||
import {Link} from 'react-router';
|
||||
import styles from './Header.css';
|
||||
import I18n from 'coral-framework/i18n/i18n';
|
||||
import translations from '../../translations.json';
|
||||
|
||||
export default () => (
|
||||
<Drawer>
|
||||
<Navigation>
|
||||
<Link className={styles.navLink} to="/admin">Moderate</Link>
|
||||
<Link className={styles.navLink} to="/admin/community">Community</Link>
|
||||
<Link className={styles.navLink} to="/admin/configure">Configure</Link>
|
||||
<Link className={styles.navLink} to="/admin">{lang.t('configure.moderate')}</Link>
|
||||
<Link className={styles.navLink} to="/admin/community">{lang.t('configure.community')}</Link>
|
||||
<Link className={styles.navLink} to="/admin/configure">{lang.t('configure.configure')}</Link>
|
||||
</Navigation>
|
||||
</Drawer>
|
||||
);
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
@@ -2,16 +2,20 @@ import React from 'react';
|
||||
import {Navigation, Header} from 'react-mdl';
|
||||
import {Link} from 'react-router';
|
||||
import styles from './Header.css';
|
||||
import I18n from 'coral-framework/i18n/i18n';
|
||||
import translations from '../../translations.json';
|
||||
|
||||
export default () => (
|
||||
<Header title='Talk'>
|
||||
<Navigation>
|
||||
<Link className={styles.navLink} to="/admin">Moderate</Link>
|
||||
<Link className={styles.navLink} to="/admin/community">Community</Link>
|
||||
<Link className={styles.navLink} to="/admin/configure">Configure</Link>
|
||||
<Link className={styles.navLink} to="/admin">{lang.t('configure.moderate')}</Link>
|
||||
<Link className={styles.navLink} to="/admin/community">{lang.t('configure.community')}</Link>
|
||||
<Link className={styles.navLink} to="/admin/configure">{lang.t('configure.configure')}</Link>
|
||||
<span>
|
||||
{`v${process.env.VERSION}`}
|
||||
</span>
|
||||
</Navigation>
|
||||
</Header>
|
||||
);
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import I18n from 'coral-framework/i18n/i18n';
|
||||
import translations from '../../translations';
|
||||
import translations from '../../translations.json';
|
||||
import {Grid, Cell} from 'react-mdl';
|
||||
|
||||
import styles from './Community.css';
|
||||
|
||||
@@ -23,6 +23,21 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.configSettingInfoBox {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
width: auto;
|
||||
height: auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.configSettingInfoBox p {
|
||||
font-size: 12px;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.configSettingEmbed {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
@@ -53,3 +68,7 @@
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ import {
|
||||
ListItem,
|
||||
ListItemContent,
|
||||
ListItemAction,
|
||||
//Textfield,
|
||||
Textfield,
|
||||
Checkbox,
|
||||
Button,
|
||||
Icon
|
||||
} from 'react-mdl';
|
||||
import styles from './Configure.css';
|
||||
import I18n from 'coral-framework/i18n/i18n';
|
||||
import translations from '../translations';
|
||||
import translations from '../translations.json';
|
||||
|
||||
class Configure extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -24,6 +24,8 @@ class Configure extends React.Component {
|
||||
|
||||
this.copyToClipBoard = this.copyToClipBoard.bind(this);
|
||||
this.updateModeration = this.updateModeration.bind(this);
|
||||
this.updateInfoBoxEnable = this.updateInfoBoxEnable.bind(this);
|
||||
this.updateInfoBoxContent = this.updateInfoBoxContent.bind(this);
|
||||
this.saveSettings = this.saveSettings.bind(this);
|
||||
}
|
||||
|
||||
@@ -36,6 +38,16 @@ class Configure extends React.Component {
|
||||
this.props.dispatch(updateSettings({moderation}));
|
||||
}
|
||||
|
||||
updateInfoBoxEnable () {
|
||||
const infoBoxEnable = !this.props.settings.infoBoxEnable;
|
||||
this.props.dispatch(updateSettings({infoBoxEnable}));
|
||||
}
|
||||
|
||||
updateInfoBoxContent (event) {
|
||||
const infoBoxContent = event.target.value;
|
||||
this.props.dispatch(updateSettings({infoBoxContent}));
|
||||
}
|
||||
|
||||
saveSettings () {
|
||||
this.props.dispatch(saveSettingsToServer());
|
||||
}
|
||||
@@ -48,22 +60,30 @@ class Configure extends React.Component {
|
||||
onClick={this.updateModeration}
|
||||
checked={this.props.settings.moderation === 'pre'} />
|
||||
</ListItemAction>
|
||||
Enable pre-moderation
|
||||
{lang.t('configure.enable-pre-moderation')}
|
||||
</ListItem>
|
||||
{/*
|
||||
<ListItem className={styles.configSetting}>
|
||||
<ListItemAction><Checkbox /></ListItemAction>
|
||||
Include Comment Stream Description for Readers
|
||||
<ListItem threeLine className={styles.configSettingInfoBox}>
|
||||
<ListItemAction>
|
||||
<Checkbox
|
||||
onClick={this.updateInfoBoxEnable}
|
||||
checked={this.props.settings.infoBoxEnable} />
|
||||
</ListItemAction>
|
||||
<ListItemContent>
|
||||
{lang.t('configure.include-comment-stream')}
|
||||
<p>
|
||||
{lang.t('configure.include-comment-stream-desc')}
|
||||
</p>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
<ListItem className={styles.configSetting}>
|
||||
<ListItemAction><Checkbox /></ListItemAction>
|
||||
Limit Comment Length
|
||||
<Textfield
|
||||
pattern='-?[0-9]*(\.[0-9]+)?'
|
||||
error='Input is not a number!'
|
||||
label='Maximum Characters' />
|
||||
<ListItem className={`${styles.configSettingInfoBox} ${this.props.settings.infoBoxEnable ? null : styles.hidden}`} >
|
||||
<ListItemContent>
|
||||
<Textfield
|
||||
onChange={this.updateInfoBoxContent}
|
||||
value={this.props.settings.infoBoxContent}
|
||||
label={lang.t('configure.include-text')}
|
||||
rows={3}/>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
*/}
|
||||
</List>;
|
||||
}
|
||||
|
||||
@@ -84,7 +104,7 @@ class Configure extends React.Component {
|
||||
|
||||
return <List>
|
||||
<ListItem className={styles.configSettingEmbed}>
|
||||
<p>Copy and paste code below into your CMS to embed your comment box in your articles</p>
|
||||
<p>{lang.t('configure.copy-and-paste')}</p>
|
||||
<textarea rows={5} type='text' className={styles.embedInput} value={embedText} readOnly={true}/>
|
||||
<Button raised colored className={styles.copyButton} onClick={this.copyToClipBoard}>
|
||||
{lang.t('embedlink.copy')}
|
||||
@@ -100,8 +120,8 @@ class Configure extends React.Component {
|
||||
|
||||
render () {
|
||||
let pageTitle = this.state.activeSection === 'comments'
|
||||
? 'Comment Settings'
|
||||
: 'Embed Comment Stream';
|
||||
? lang.t('configure.comment-settings')
|
||||
: lang.t('configure.embed-comment-stream');
|
||||
|
||||
if (this.props.fetchingSettings) {
|
||||
pageTitle += ' - Loading...';
|
||||
@@ -114,16 +134,16 @@ class Configure extends React.Component {
|
||||
<ListItem className={styles.settingOption}>
|
||||
<ListItemContent
|
||||
onClick={this.changeSection.bind(this, 'comments')}
|
||||
icon='settings'>Comment Settings</ListItemContent>
|
||||
icon='settings'>{lang.t('configure.comment-settings')}</ListItemContent>
|
||||
</ListItem>
|
||||
<ListItem className={styles.settingOption}>
|
||||
<ListItemContent
|
||||
onClick={this.changeSection.bind(this, 'embed')}
|
||||
icon='code'>Embed Comment Stream</ListItemContent>
|
||||
icon='code'>{lang.t('configure.embed-comment-stream')}</ListItemContent>
|
||||
</ListItem>
|
||||
</List>
|
||||
<Button raised colored onClick={this.saveSettings}>
|
||||
<Icon name='save' /> Save Changes
|
||||
<Icon name='save' /> {lang.t('configure.save-changes')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {updateStatus} from 'actions/comments';
|
||||
import styles from './ModerationQueue.css';
|
||||
import key from 'keymaster';
|
||||
import I18n from 'coral-framework/i18n/i18n';
|
||||
import translations from '../translations';
|
||||
import translations from '../translations.json';
|
||||
|
||||
/*
|
||||
* Renders the moderation queue as a tabbed layout with 3 moderation
|
||||
@@ -91,7 +91,7 @@ class ModerationQueue extends React.Component {
|
||||
commentIds={
|
||||
comments
|
||||
.get('ids')
|
||||
.filter(id =>
|
||||
.filter(id =>
|
||||
comments
|
||||
.get('byId')
|
||||
.get(id)
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
export default {
|
||||
en: {
|
||||
'community': {
|
||||
username_and_email: 'Username and Email',
|
||||
account_creation_date: 'Account Creation Date',
|
||||
newsroom_role: 'Newsroom Role',
|
||||
admin: 'Administrator',
|
||||
moderator: 'Moderator',
|
||||
role: 'Select role...'
|
||||
},
|
||||
'modqueue': {
|
||||
'pending': 'pending',
|
||||
'rejected': 'rejected',
|
||||
'flagged': 'flagged',
|
||||
'shortcuts': 'Shortcuts',
|
||||
'close': 'Close',
|
||||
'actions': 'Actions',
|
||||
'navigation': 'Navigation',
|
||||
'approve': 'Approve comment',
|
||||
'reject': 'Reject comment',
|
||||
'nextcomment': 'Go to the next comment',
|
||||
'prevcomment': 'Go to the previous comment',
|
||||
'singleview': 'Toggle single comment edit view',
|
||||
'thismenu': 'Open this menu'
|
||||
},
|
||||
'comment': {
|
||||
'flagged': 'flagged',
|
||||
'anon': 'Anonymous'
|
||||
},
|
||||
'embedlink': {
|
||||
'copy': 'Copy to Clipboard'
|
||||
}
|
||||
},
|
||||
es: {
|
||||
'community': {
|
||||
username_and_email: 'Usuario y E-mail',
|
||||
account_creation_date: 'Fecha de creación de la cuenta',
|
||||
newsroom_role: 'Rol en la redacción',
|
||||
admin: 'Administrador',
|
||||
moderator: 'Moderador',
|
||||
role: 'Select role...'
|
||||
},
|
||||
'modqueue': {
|
||||
'pending': 'pendiente',
|
||||
'rejected': 'rechazado',
|
||||
'flagged': 'marcado',
|
||||
'shortcuts': 'Atajos de teclado',
|
||||
'close': 'Cerrar'
|
||||
},
|
||||
'comment': {
|
||||
'flagged': 'marcado',
|
||||
'anon': 'Anónimo'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"en": {
|
||||
"community": {
|
||||
"username_and_email": "Username and Email",
|
||||
"account_creation_date": "Account Creation Date",
|
||||
"newsroom_role": "Newsroom Role",
|
||||
"admin": "Administrator",
|
||||
"moderator": "Moderator",
|
||||
"role": "Select role..."
|
||||
},
|
||||
"modqueue": {
|
||||
"pending": "pending",
|
||||
"rejected": "rejected",
|
||||
"flagged": "flagged",
|
||||
"shortcuts": "Shortcuts",
|
||||
"close": "Close",
|
||||
"actions": "Actions",
|
||||
"navigation": "Navigation",
|
||||
"approve": "Approve comment",
|
||||
"reject": "Reject comment",
|
||||
"nextcomment": "Go to the next comment",
|
||||
"prevcomment": "Go to the previous comment",
|
||||
"singleview": "Toggle single comment edit view",
|
||||
"thismenu": "Open this menu"
|
||||
},
|
||||
"comment": {
|
||||
"flagged": "flagged",
|
||||
"anon": "Anonymous"
|
||||
},
|
||||
"embedlink": {
|
||||
"copy": "Copy to Clipboard"
|
||||
},
|
||||
"configure": {
|
||||
"enable-pre-moderation": "Enable pre-moderation",
|
||||
"include-comment-stream": "Include Comment Stream Description for Readers.",
|
||||
"include-comment-stream-desc": "Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc.",
|
||||
"include-text": "Include your text here.",
|
||||
"comment-settings": "Comment Settings",
|
||||
"embed-comment-stream": "Embed Comment Stream",
|
||||
"save-changes": "Save Changes",
|
||||
"copy-and-paste": "Copy and paste code below into your CMS to embed your comment box in your articles",
|
||||
"moderate": "Moderate",
|
||||
"configure": "Configure",
|
||||
"community": "Community"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"community": {
|
||||
"username_and_email": "Usuario y E-mail",
|
||||
"account_creation_date": "Fecha de creación de la cuenta",
|
||||
"newsroom_role": "Rol en la redacción",
|
||||
"admin": "Administrador",
|
||||
"moderator": "Moderador",
|
||||
"role": "Select role..."
|
||||
},
|
||||
"modqueue": {
|
||||
"pending": "pendiente",
|
||||
"rejected": "rechazado",
|
||||
"flagged": "marcado",
|
||||
"shortcuts": "Atajos de teclado",
|
||||
"close": "Cerrar"
|
||||
},
|
||||
"comment": {
|
||||
"flagged": "marcado",
|
||||
"anon": "Anónimo"
|
||||
},
|
||||
"configure": {
|
||||
"enable-pre-moderation": "Habilitar pre-moderación",
|
||||
"include-comment-stream": "Incluir la Descripción a un Hilo de Comentario para los y las Lectoras.",
|
||||
"include-comment-stream-desc": "Escribir un mensaje que será agregado a la parte de arriba del tu hilo de comentarios. Por ejemplo, un tema, guias de comunidad, etc.",
|
||||
"include-text": "Incluir tu texto aqui.",
|
||||
"comment-settings": "Configuración de Comentarios",
|
||||
"embed-comment-stream": "Colocar Hilo de Comentarios",
|
||||
"save-changes": "Guardar Cambios",
|
||||
"copy-and-paste": "Copiar y pegar el código de más abajo en tu CMS para colocar la caja de comentarios en tus articulos",
|
||||
"moderate": "Moderar",
|
||||
"configure": "Configurar",
|
||||
"community": "Comunidad"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '../../coral-framework';
|
||||
import {connect} from 'react-redux';
|
||||
import CommentBox from '../../coral-plugin-commentbox/CommentBox';
|
||||
import InfoBox from '../../coral-plugin-infobox/InfoBox';
|
||||
import Content from '../../coral-plugin-commentcontent/CommentContent';
|
||||
import PubDate from '../../coral-plugin-pubdate/PubDate';
|
||||
import Count from '../../coral-plugin-comment-count/CommentCount';
|
||||
@@ -76,34 +77,25 @@ class CommentStream extends Component {
|
||||
componentDidMount () {
|
||||
// Set up messaging between embedded Iframe an parent component
|
||||
// Using recommended Pym init code which violates .eslint standards
|
||||
new Pym.Child({polling: 500});
|
||||
this.props.getStream('assetTest');
|
||||
const pym = new Pym.Child({polling: 100});
|
||||
const path = /https?\:\/\/([^?]+)/.exec(pym.parentUrl)[1];
|
||||
this.props.getStream(path);
|
||||
}
|
||||
|
||||
render () {
|
||||
if (Object.keys(this.props.items).length === 0) {
|
||||
// Loading mock asset
|
||||
this.props.postItem({
|
||||
comments: [],
|
||||
url: 'http://coralproject.net'
|
||||
}, 'asset', 'assetTest');
|
||||
|
||||
// Loading mock user
|
||||
this.props.postItem({name: 'Ban Ki-Moon'}, 'user', 'user_8989')
|
||||
.then((id) => {
|
||||
this.props.setLoggedInUser(id);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Replace teststream id with id from params
|
||||
|
||||
const rootItemId = 'assetTest';
|
||||
const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0];
|
||||
const rootItem = this.props.items.assets && this.props.items.assets[rootItemId];
|
||||
return <div>
|
||||
{
|
||||
rootItem
|
||||
? <div>
|
||||
<div id="commentBox">
|
||||
<InfoBox
|
||||
content={this.props.config.infoBoxContent}
|
||||
enable={this.props.config.infoBoxEnable}/>
|
||||
<Count
|
||||
id={rootItemId}
|
||||
items={this.props.items}/>
|
||||
@@ -117,7 +109,7 @@ class CommentStream extends Component {
|
||||
reply={false}/>
|
||||
</div>
|
||||
{
|
||||
rootItem.comments.map((commentId) => {
|
||||
rootItem.comments && rootItem.comments.map((commentId) => {
|
||||
const comment = this.props.items.comments[commentId];
|
||||
return <div className="comment" key={commentId}>
|
||||
<hr aria-hidden={true}/>
|
||||
|
||||
@@ -49,6 +49,23 @@ hr {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Info Box Styles */
|
||||
.coral-plugin-infobox-info {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
border: 0;
|
||||
background: rgb(105,105,105);
|
||||
color: white;
|
||||
border-radius: 2px;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Comment Box Styles */
|
||||
.coral-plugin-commentbox-container {
|
||||
display: flex;
|
||||
|
||||
@@ -94,9 +94,9 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) =>
|
||||
* @dispatches
|
||||
* A set of items to the item store
|
||||
*/
|
||||
export function getStream (assetId) {
|
||||
export function getStream (assetUrl) {
|
||||
return (dispatch) => {
|
||||
return fetch(`/api/v1/stream?asset_id=${assetId}`, getInit('GET'))
|
||||
return fetch(`/api/v1/stream?asset_url=${encodeURIComponent(assetUrl)}`)
|
||||
.then(responseHandler)
|
||||
.then((json) => {
|
||||
|
||||
@@ -108,6 +108,8 @@ export function getStream (assetId) {
|
||||
}
|
||||
}
|
||||
|
||||
const assetId = json.assets[0].id;
|
||||
|
||||
/* Sort comments by date*/
|
||||
json.comments.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
||||
const rels = json.comments.reduce((h, item) => {
|
||||
@@ -125,10 +127,7 @@ export function getStream (assetId) {
|
||||
return h;
|
||||
}, {rootComments: [], childComments: {}});
|
||||
|
||||
dispatch(addItem({
|
||||
id: assetId,
|
||||
comments: rels.rootComments,
|
||||
}, 'assets'));
|
||||
dispatch(updateItem(assetId, 'comments', rels.rootComments, 'assets'));
|
||||
|
||||
const childKeys = Object.keys(rels.childComments);
|
||||
for (let i = 0; i < childKeys.length; i++ ) {
|
||||
|
||||
@@ -5,7 +5,7 @@ const name = 'coral-plugin-comment-count';
|
||||
|
||||
const CommentCount = ({items, id}) => {
|
||||
let count = 0;
|
||||
if (items.assets[id]) {
|
||||
if (items.assets[id] && items.assets[id].comments) {
|
||||
count += items.assets[id].comments.length;
|
||||
}
|
||||
const itemKeys = Object.keys(items.comments);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
const packagename = 'coral-plugin-infobox';
|
||||
|
||||
const InfoBox = ({enable, content}) =>
|
||||
<div
|
||||
className={`${packagename}-info ${enable ? null : ', hidden'}` }>
|
||||
{content}
|
||||
</div>;
|
||||
|
||||
export default InfoBox;
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import {expect} from 'chai';
|
||||
import InfoBox from '../InfoBox';
|
||||
|
||||
describe('InfoBox', () => {
|
||||
let comment;
|
||||
let render;
|
||||
beforeEach(() => {
|
||||
comment = {};
|
||||
const postItem = (item) => {
|
||||
comment.posted = item;
|
||||
return Promise.resolve(4);
|
||||
};
|
||||
render = shallow(<InfoBox
|
||||
postItem={postItem}
|
||||
updateItem={(e) => comment.text = e.target.value}
|
||||
item_id={'1'}
|
||||
comments={['1', '2', '3']}/>);
|
||||
});
|
||||
|
||||
it('should render the InfoBox appropriately', () => {
|
||||
expect(render.contains('<div class="InfoBox"')).to.be.truthy;
|
||||
expect(render.contains('<button class="postCommentButton"')).to.be.truthy;
|
||||
});
|
||||
});
|
||||
+11
-1
@@ -3,7 +3,9 @@ const Schema = mongoose.Schema;
|
||||
|
||||
const SettingSchema = new Schema({
|
||||
id: {type: String, default: '1'},
|
||||
moderation: {type: String, enum: ['pre', 'post'], default: 'pre'}
|
||||
moderation: {type: String, enum: ['pre', 'post'], default: 'pre'},
|
||||
infoBoxEnable: {type: Boolean, default: false},
|
||||
infoBoxContent: {type: String, default: ''}
|
||||
}, {
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
@@ -35,6 +37,14 @@ SettingSchema.statics.getModerationSetting = function () {
|
||||
return this.findOne({id: '1'}).select('moderation');
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the info box settings and sends it back
|
||||
* @return {Promise} content the content of the info Box
|
||||
*/
|
||||
SettingSchema.statics.getInfoBoxSetting = function () {
|
||||
return this.findOne({id: '1'}).select('infoBoxEnable', 'infoBoxContent');
|
||||
};
|
||||
|
||||
/**
|
||||
* This will update the settings object with whatever you pass in
|
||||
* @param {object} setting a hash of whatever settings you want to update
|
||||
|
||||
@@ -3,35 +3,45 @@ const express = require('express');
|
||||
const Comment = require('../../../models/comment');
|
||||
const User = require('../../../models/user');
|
||||
const Action = require('../../../models/action');
|
||||
const Asset = require('../../../models/asset');
|
||||
|
||||
const Setting = require('../../../models/setting');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Find all the comments by a specific asset_id.
|
||||
// Find all the comments by a specific asset_url.
|
||||
// . if pre: get the comments that are accepted.
|
||||
// . if post: get the comments that are new and accepted.
|
||||
router.get('/', (req, res, next) => {
|
||||
const commentsPromise = Setting.getModerationSetting().then(({moderation}) => {
|
||||
|
||||
// Get the asset_id for this url (or create it if it doesn't exist)
|
||||
Promise.all([
|
||||
Asset.findOrCreateByUrl(decodeURIComponent(req.query.asset_url)),
|
||||
Setting.getModerationSetting()
|
||||
])
|
||||
.then(([asset, {moderation}]) => {
|
||||
// Get the sitewide moderation setting and return the appropriate comments
|
||||
switch(moderation){
|
||||
case 'pre':
|
||||
return Comment.findAcceptedByAssetId(req.query.asset_id);
|
||||
return Promise.all([Comment.findAcceptedByAssetId(asset.id), asset]);
|
||||
case 'post':
|
||||
return Comment.findAcceptedAndNewByAssetId(req.query.asset_id);
|
||||
return Promise.all([Comment.findAcceptedAndNewByAssetId(asset.id), asset]);
|
||||
default:
|
||||
throw new Error('Moderation setting not found.');
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
// Get all the users and actions for those comments.
|
||||
commentsPromise.then(comments => {
|
||||
.then(([comments, asset]) => {
|
||||
return Promise.all([
|
||||
[asset],
|
||||
comments,
|
||||
User.findByIdArray(comments.map((comment) => comment.author_id)),
|
||||
Action.getActionSummaries(comments.map((comment) => comment.id))
|
||||
]);
|
||||
}).then(([comments, users, actions]) => {
|
||||
})
|
||||
.then(([assets, comments, users, actions]) => {
|
||||
res.json({
|
||||
assets,
|
||||
comments,
|
||||
users,
|
||||
actions
|
||||
|
||||
+5
-1
@@ -6,7 +6,11 @@ router.use('/admin', require('./admin'));
|
||||
router.use('/embed', require('./embed'));
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
return res.render('home', {});
|
||||
return res.render('article', {title: 'Coral Talk'});
|
||||
});
|
||||
|
||||
router.get('/assets/:asset_title', (req, res) => {
|
||||
return res.render('article', {title: req.params.asset_title.split('-').join(' ')});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -18,8 +18,11 @@ describe('itemActions', () => {
|
||||
});
|
||||
|
||||
describe('getStream', () => {
|
||||
const rootId = '1234';
|
||||
const assetUrl = 'http://www.test.com';
|
||||
const response = {
|
||||
assets: [{
|
||||
id: '1234', url: assetUrl
|
||||
}],
|
||||
comments: [
|
||||
{body: 'stuff', id: '123'},
|
||||
{body: 'morestuff', id: '456'}
|
||||
@@ -42,17 +45,17 @@ describe('itemActions', () => {
|
||||
|
||||
it('should get an stream from an asset_id and send the appropriate dispatches', () => {
|
||||
fetchMock.get('*', JSON.stringify(response));
|
||||
return actions.getStream(rootId)(store.dispatch)
|
||||
return actions.getStream(assetUrl)(store.dispatch)
|
||||
.then((res) => {
|
||||
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/stream?asset_id=1234');
|
||||
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/stream?asset_url=http%3A%2F%2Fwww.test.com');
|
||||
expect(res).to.deep.equal(response);
|
||||
expect(store.getActions()[0]).to.deep.equal({
|
||||
expect(store.getActions()[1]).to.deep.equal({
|
||||
type: actions.ADD_ITEM,
|
||||
item: response.comments[0],
|
||||
item_type: 'comments',
|
||||
id: '123'
|
||||
});
|
||||
expect(store.getActions()[1]).to.deep.equal({
|
||||
expect(store.getActions()[2]).to.deep.equal({
|
||||
type: actions.ADD_ITEM,
|
||||
item: response.comments[1],
|
||||
item_type: 'comments',
|
||||
@@ -62,7 +65,7 @@ describe('itemActions', () => {
|
||||
});
|
||||
it('should handle an error', () => {
|
||||
fetchMock.get('*', 404);
|
||||
return actions.getStream(rootId)(store.dispatch)
|
||||
return actions.getStream(assetUrl)(store.dispatch)
|
||||
.catch((err) => {
|
||||
expect(err).to.be.truthy;
|
||||
});
|
||||
|
||||
+10
-2
@@ -8,7 +8,7 @@ const expect = require('chai').expect;
|
||||
describe('Setting: model', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const defaults = {id: 1, moderation: 'pre'};
|
||||
const defaults = {id: 1};
|
||||
return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
|
||||
});
|
||||
|
||||
@@ -18,13 +18,21 @@ describe('Setting: model', () => {
|
||||
expect(settings).to.have.property('moderation').and.to.equal('pre');
|
||||
});
|
||||
});
|
||||
it('should have two infoBox fields defined', () => {
|
||||
return Setting.getSettings().then(settings => {
|
||||
expect(settings).to.have.property('infoBoxEnable').and.to.equal(false);
|
||||
expect(settings).to.have.property('infoBoxContent').and.to.equal('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateSettings()', () => {
|
||||
it('should update the settings with a passed object', () => {
|
||||
const mockSettings = {moderation: 'post'};
|
||||
const mockSettings = {moderation: 'post', infoBoxEnable: true, infoBoxContent: 'yeah'};
|
||||
return Setting.updateSettings(mockSettings).then(updatedSettings => {
|
||||
expect(updatedSettings).to.have.property('moderation').and.to.equal('post');
|
||||
expect(updatedSettings).to.have.property('infoBoxEnable', true);
|
||||
expect(updatedSettings).to.have.property('infoBoxContent', 'yeah');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ chai.use(require('chai-http'));
|
||||
const Action = require('../../../../models/action');
|
||||
const User = require('../../../../models/user');
|
||||
const Comment = require('../../../../models/comment');
|
||||
const Asset = require('../../../../models/asset');
|
||||
|
||||
const Setting = require('../../../../models/setting');
|
||||
|
||||
@@ -21,14 +22,12 @@ describe('api/stream: routes', () => {
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
asset_id: 'asset',
|
||||
author_id: '',
|
||||
parent_id: '',
|
||||
status: 'accepted'
|
||||
}, {
|
||||
id: 'def',
|
||||
body: 'comment 20',
|
||||
asset_id: 'asset',
|
||||
author_id: '',
|
||||
parent_id: '',
|
||||
status: ''
|
||||
@@ -66,29 +65,33 @@ describe('api/stream: routes', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
return User
|
||||
.createLocalUsers(users)
|
||||
.then(users => {
|
||||
return Promise.all([
|
||||
User.createLocalUsers(users),
|
||||
Asset.findOrCreateByUrl('http://test.com')
|
||||
])
|
||||
.then(([users, asset]) => {
|
||||
|
||||
comments[0].author_id = users[0].id;
|
||||
comments[1].author_id = users[1].id;
|
||||
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
Action.create(actions),
|
||||
Setting.create(settings)
|
||||
]);
|
||||
comments[0].author_id = users[0].id;
|
||||
comments[1].author_id = users[1].id;
|
||||
|
||||
});
|
||||
comments[0].asset_id = asset.id;
|
||||
comments[1].asset_id = asset.id;
|
||||
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
Action.create(actions),
|
||||
Setting.create(settings)
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a stream with comments, users and actions', () => {
|
||||
it('should return a stream with comments, users and actions for an existing asset', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/stream')
|
||||
.query({'asset_id': 'asset'})
|
||||
.query({'asset_url': 'http://test.com'})
|
||||
.then(res => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body.assets.length).to.equal(1);
|
||||
expect(res.body.comments.length).to.equal(1);
|
||||
expect(res.body.users.length).to.equal(1);
|
||||
expect(res.body.actions.length).to.equal(1);
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:title" content="<%= title %>" />
|
||||
<meta property="og:author" content="A. J. Ournalist" />
|
||||
<meta property="og:description" content="A description of this article." />
|
||||
|
||||
<meta property="article:published" itemprop="datePublished" content="2016-11-16T11:46:06-05:00" />
|
||||
<meta property="article:modified" itemprop="dateModified" content="2016-11-16T12:09:44-05:00" />
|
||||
<meta property="article:section" itemprop="articleSection" content="The Section!" />
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div style="margin-left:auto; margin-right:auto; width:500px">
|
||||
<h1>Corem ipsal</h1>
|
||||
<h1><%= title %></h1>
|
||||
<p>Lorem ipsum dolor sponge amet, consectetur adipiscing clam. Ut lobortis sollicitudin pillar a ornare. Curabitur dignissim vestibulum cay non rhoncus. Cras laoreet ante vel nunc hendrerit, shelf imperdiet neque egestas. Suspendisse aliquet iaculis fermentum. Talk volutpat, tellus posuere laoreet consequat, mi lacus laoreet massa, sed vehicula mauris velit non lectus. Integer non trust nec neque congue faucibus porttitor sit amet elkhorn.</p>
|
||||
<p><a href="/admin">Visit the moderation console</a></p>
|
||||
|
||||
<div id='coralStreamEmbed'></div>
|
||||
<script type='text/javascript' src='https://pym.nprapps.org/pym.v1.min.js'></script>
|
||||
@@ -17,7 +17,12 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="coralStream"></div>
|
||||
<script src="<%= basePath %>/bundle.js" charset="utf-8"></script>
|
||||
<div id='coralStreamEmbed'></div>
|
||||
<script type='text/javascript' src='https://pym.nprapps.org/pym.v1.min.js'></script>
|
||||
<script>
|
||||
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream', {title: 'Talk Comments'});
|
||||
pymParent.onMessage('height', function(height) {document.querySelector('#coralStreamEmbed iframe').height = height + 'px'})</script>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user