This commit is contained in:
Belen Curcio
2017-03-29 16:42:59 -03:00
60 changed files with 746 additions and 497 deletions
-14
View File
@@ -1,14 +0,0 @@
{
"sourceMaps": true,
"presets": [
"es2015"
],
"plugins": [
"add-module-exports",
"transform-class-properties",
"transform-decorators-legacy",
"transform-object-assign",
"transform-object-rest-spread",
"transform-react-jsx"
]
}
+10 -2
View File
@@ -1,6 +1,14 @@
{
"extends": "../.babelrc",
"presets": [
"es2015"
],
"plugins": [
"transform-async-to-generator"
"add-module-exports",
"transform-class-properties",
"transform-decorators-legacy",
"transform-object-assign",
"transform-object-rest-spread",
"transform-async-to-generator",
"transform-react-jsx"
]
}
@@ -74,8 +74,12 @@
background-color: transparent;
transition: background-color 200ms;
&:hover {
background-color: #232323;
}
&.active {
background-color: #232323;
background-color: #232323;
}
}
@@ -1,5 +1,7 @@
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
export const SHOW_SUSPENDUSER_DIALOG = 'SHOW_SUSPENDUSER_DIALOG';
export const HIDE_SUSPENDUSER_DIALOG = 'HIDE_SUSPENDUSER_DIALOG';
export const COMMENTS_MODERATION_QUEUE_FETCH_REQUEST = 'COMMENTS_MODERATION_QUEUE_FETCH_REQUEST';
export const COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS = 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS';
export const COMMENT_CREATE_SUCCESS = 'COMMENT_CREATE_SUCCESS';
@@ -185,6 +185,8 @@
justify-content: space-between;
.author {
font-size: 16px;
font-weight: bold;
min-width: 230px;
display: flex;
align-items: center;
@@ -223,7 +225,6 @@
color: black;
max-width: 500px;
word-wrap: break-word;
font-weight: 300;
}
.flagged {
@@ -301,7 +302,6 @@
}
}
.actionButton {
transform: scale(.8);
margin: 0;
@@ -315,12 +315,19 @@
.flaggedBy {
display: inline;
padding: 3px;
font-size: 16px;
}
.flaggedByLabel {
font-weight: bold;
font-size: 14px;
}
.flaggedReasons {
padding-top: 15px;
margin-left: 24px;
}
.flaggedByReason {
font-size: 1tpx;
}
@@ -7,13 +7,15 @@ const lang = new I18n(translations);
import styles from './Community.css';
import Loading from './Loading';
import EmptyCard from '../../components/EmptyCard';
import EmptyCard from 'coral-admin/src/components/EmptyCard';
import User from './components/User';
const FlaggedAccounts = ({...props}) => {
const {commenters, isFetching} = props;
const hasResults = !isFetching && commenters && !!commenters.length;
// if (commenter.status === 'PENDING' && commenter.actions.length > 0) {
return (
<div className={styles.container}>
<div className={styles.mainFlaggedContent}>
@@ -21,19 +23,16 @@ const FlaggedAccounts = ({...props}) => {
{
hasResults
? commenters.map((commenter, index) => {
if (commenter.status === 'PENDING' && commenter.actions.length > 0) {
return <User
user={commenter}
key={index}
index={index}
modActionButtons={['REJECT', 'APPROVE']}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
approveUser={props.approveUser}
suspendUser={props.suspendUser}
/>;
}
return null;
return <User
user={commenter}
key={index}
index={index}
modActionButtons={['APPROVE', 'REJECT']}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
approveUser={props.approveUser}
suspendUser={props.suspendUser}
/>;
})
: <EmptyCard>{lang.t('community.no-flagged-accounts')}</EmptyCard>
}
@@ -49,7 +49,7 @@ class SuspendUserDialog extends Component {
const {suspendUser, user} = this.props;
const {stage} = this.state;
const cancel = this.props.onClose;
const cancel = this.props.handleClose;
const next = () => this.setState({stage: stage + 1});
const suspend = () => {
suspendUser({userId: user.user.id, message: this.state.email})
@@ -35,7 +35,7 @@ const User = props => {
<div className={styles.itemBody}>
<div className={styles.body}>
<div className={styles.flaggedByCount}>
<i className="material-icons">flag</i><span className={styles.flaggedByLabel}>Flags({ user.actions.length })</span>:
<i className="material-icons">flag</i><span className={styles.flaggedByLabel}>{lang.t('community.flags')}({ user.actions.length })</span>:
{ user.action_summaries.map(
(action, i ) => {
return <span className={styles.flaggedBy} key={i}>
@@ -67,8 +67,8 @@ const User = props => {
}
)}
</div>
</div>
</div>
<div className={styles.sideActions}>
<div className={`actions ${styles.actions}`}>
{modActionButtons.map((action, i) =>
@@ -37,6 +37,10 @@ const updateInfoBoxContent = (updateSettings) => (event) => {
updateSettings({infoBoxContent});
};
const updateAutoClose = (updateSettings, autoCloseStream) => () => {
updateSettings({autoCloseStream});
};
const updateClosedMessage = (updateSettings) => (event) => {
const closedMessage = event.target.value;
updateSettings({closedMessage});
@@ -131,6 +135,11 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
</div>
</Card>
<Card className={`${styles.configSetting} ${styles.configSettingInfoBox}`}>
<div className={styles.action}>
<Checkbox
onChange={updateAutoClose(updateSettings, !settings.autoCloseStream)}
checked={settings.autoCloseStream} />
</div>
<div className={styles.content}>
{lang.t('configure.close-after')}
<br />
@@ -163,6 +163,7 @@ class ModerationContainer extends Component {
activeTab={activeTab}
singleView={moderation.singleView}
selectedIndex={this.state.selectedIndex}
bannedWords={settings.wordlist.banned}
suspectWords={settings.wordlist.suspect}
showBanUserDialog={props.showBanUserDialog}
acceptComment={props.acceptComment}
@@ -24,6 +24,7 @@ const ModerationQueue = ({comments, selectedIndex, commentCount, singleView, loa
commentType={activeTab}
selected={i === selectedIndex}
suspectWords={props.suspectWords}
bannedWords={props.bannedWords}
actions={actionsMap[status]}
showBanUserDialog={props.showBanUserDialog}
acceptComment={props.acceptComment}
@@ -47,6 +48,7 @@ const ModerationQueue = ({comments, selectedIndex, commentCount, singleView, loa
};
ModerationQueue.propTypes = {
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
showBanUserDialog: PropTypes.func.isRequired,
@@ -19,6 +19,7 @@ const lang = new I18n(translations);
const Comment = ({actions = [], ...props}) => {
const links = linkify.getMatches(props.comment.body);
const linkText = links ? links.map(link => link.raw) : [];
const actionSummaries = props.comment.action_summaries;
return (
<li tabIndex={props.index} className={`mdl-card ${props.selected ? 'mdl-shadow--8dp' : 'mdl-shadow--2dp'} ${styles.Comment} ${styles.listItem}`}>
@@ -49,9 +50,9 @@ const Comment = ({actions = [], ...props}) => {
</div>
<div className={styles.itemBody}>
<p className={styles.body}>
<Linkify component='span' properties={{style: linkStyles}}>
<Highlighter searchWords={props.suspectWords} textToHighlight={props.comment.body}/>
</Linkify>
<Highlighter
searchWords={[...props.suspectWords, ...props.bannedWords, ...linkText]}
textToHighlight={props.comment.body} />
</p>
<div className={styles.sideActions}>
{links ? <span className={styles.hasLinks}><Icon name='error_outline'/> Contains Link</span> : null}
@@ -77,6 +78,7 @@ Comment.propTypes = {
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
comment: PropTypes.shape({
body: PropTypes.string.isRequired,
@@ -92,9 +94,4 @@ Comment.propTypes = {
})
};
const linkStyles = {
backgroundColor: 'rgb(255, 219, 135)',
padding: '1px 2px'
};
export default Comment;
@@ -8,28 +8,46 @@ import {Link} from 'react-router';
const lang = new I18n(translations);
const ModerationMenu = ({asset, premodCount, rejectedCount, flaggedCount, selectSort, sort}) => {
const premodPath = asset ? `/admin/moderate/premod/${asset.id}` : '/admin/moderate/premod';
const rejectPath = asset ? `/admin/moderate/rejected/${asset.id}` : '/admin/moderate/rejected';
const flagPath = asset ? `/admin/moderate/flagged/${asset.id}` : '/admin/moderate/flagged';
const ModerationMenu = (
{asset, premodCount, rejectedCount, flaggedCount, selectSort, sort}
) => {
const premodPath = asset
? `/admin/moderate/premod/${asset.id}`
: '/admin/moderate/premod';
const rejectPath = asset
? `/admin/moderate/rejected/${asset.id}`
: '/admin/moderate/rejected';
const flagPath = asset
? `/admin/moderate/flagged/${asset.id}`
: '/admin/moderate/flagged';
return (
<div className='mdl-tabs'>
<div className="mdl-tabs">
<div className={`mdl-tabs__tab-bar ${styles.tabBar}`}>
<div className={styles.tabBarPadding}/>
<div className={styles.tabBarPadding} />
<div>
<Link to={premodPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
<Link
to={premodPath}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.premod')} <CommentCount count={premodCount} />
</Link>
<Link to={rejectPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
{lang.t('modqueue.rejected')} <CommentCount count={rejectedCount} />
</Link>
<Link to={flagPath} className={`mdl-tabs__tab ${styles.tab}`} activeClassName={styles.active}>
<Link
to={flagPath}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.flagged')} <CommentCount count={flaggedCount} />
</Link>
<Link
to={rejectPath}
className={`mdl-tabs__tab ${styles.tab}`}
activeClassName={styles.active}>
{lang.t('modqueue.rejected')} <CommentCount count={rejectedCount} />
</Link>
</div>
<SelectField
className={styles.selectField}
label='Sort'
label="Sort"
value={sort}
onChange={sort => selectSort(sort)}>
<Option value={'REVERSE_CHRONOLOGICAL'}>Newest First</Option>
@@ -170,7 +170,7 @@ span {
border-bottom: 1px solid #e0e0e0;
font-size: 18px;
width: 100%;
max-width: 660px;
max-width: 700px;
min-width: 400px;
margin: 0 auto;
position: relative;
@@ -192,7 +192,7 @@ span {
}
&.selected {
max-width: 670px;
max-width: 720px;
max-height: 410px;
}
@@ -216,10 +216,12 @@ span {
justify-content: space-between;
.author {
font-weight: 600;
font-weight: 300;
min-width: 230px;
display: flex;
align-items: center;
color: #262626;
font-size: 16px;
}
}
@@ -242,10 +244,11 @@ span {
}
.created {
color: #666;
font-size: 13px;
color: #262626;
font-size: 14px;
margin-left: 15px;
line-height: 1px;
font-weight: 300;
}
.actionButton {
@@ -260,6 +263,7 @@ span {
max-width: 500px;
word-wrap: break-word;
font-weight: 300;
font-size: 16px;
}
.flagged {
@@ -67,7 +67,15 @@ export const loadMore = (fetchMore) => ({limit, cursor, sort, tab, asset_id}) =>
});
};
export const modUserFlaggedQuery = graphql(MOD_USER_FLAGGED_QUERY);
export const modUserFlaggedQuery = graphql(MOD_USER_FLAGGED_QUERY, {
options: ({params: {action_type = 'FLAG'}}) => {
return {
variables: {
action_type: action_type
}
};
}
});
export const modQueueResort = (id, fetchMore) => (sort) => {
return fetchMore({
@@ -1,5 +1,5 @@
query Users ($n: ACTION_TYPE) {
users (query:{action_type: $n}){
query Users ($action_type: ACTION_TYPE) {
users (query:{action_type: $action_type}){
id
username
status
-1
View File
@@ -7,7 +7,6 @@ import store from './services/store';
import App from './components/App';
import 'react-mdl/extra/material.css';
import 'react-mdl/extra/material.js';
render(
+42 -41
View File
@@ -18,6 +18,7 @@
"banned": "Banned",
"banned-user": "Banned User",
"loading": "Loading results",
"flags": "Flags",
"flaggedaccounts": "Flagged Usernames",
"people": "People",
"no-flagged-accounts": "The Account Flags queue is currently empty.",
@@ -161,22 +162,23 @@
"es": {
"errors": {
"NOT_AUTHORIZED": "Acción no autorizada.",
"LOGIN_MAXIMUM_EXCEEDED": "Ha realizado demasiados intentos fallidos de contraseña. Por favor espera."
"LOGIN_MAXIMUM_EXCEEDED": "Ha realizado demasiados intentos fallidos de colocar la contraseña. Por favor espere."
},
"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",
"admin": "Administradora",
"moderator": "Moderadora",
"role": "Seleccionar rol...",
"no-results": "No se encontraron usuarios con ese nombre de usuario o correo electronico.",
"no-results": "No se encontraron usuarixs con ese nombre de usuario o e-mail.",
"status": "Estado",
"select-status": "Seleccionar estado...",
"active": "Activa",
"banned": "Suspendido",
"banned-user": "Usuario Suspendido",
"loading": "Cargando resultados",
"flags": "Reporte",
"flaggedaccounts": "Nombres de Usuario Reportados",
"people": "Gente",
"no-flagged-accounts": "No hay ninguna cuenta reportada.",
@@ -185,9 +187,9 @@
"This looks like an ad/marketing": "Spam/Propaganda",
"This username is offensive": "Ofensivo",
"Other": "Otros",
"ban_user": "Quieres suspender el Usuario?",
"ban_user": "Quieres suspender al Usuario?",
"are_you_sure": "Estas segura que quieres suspender a {0}?",
"note": "Nota: Suspender a este usuario no le va a permitir borrar ni editar ni comentar.",
"note": "Nota: Suspender a este usuario no le va a permitir (al usuario) borrar ni editar ni comentar.",
"cancel": "Cancelar",
"yes_ban_user": "Si, Suspendan el usuario"
},
@@ -203,7 +205,8 @@
"username": "nombre de usuario",
"email_subject": "Su cuenta ha sido suspendida temporariamente",
"email": "Otra persona de la comunidad recientemente marcó su nombre de usuario para ser revisado. Por su contenido, el nombre de usuario ha sido rechazado. Esto quiere decir que no puede comentar, gustar o marcar contenido hasta que modifique su nombre de usuario. Por favor, envienos un correo a moderator@newsorg.com si tiene alguna pregunta o preocupación",
"write_message": "Escribir un mensaje"
"write_message": "Escribir un mensaje",
"loading": "Cargando resultados"
},
"modqueue": {
"likes": "gustos",
@@ -222,34 +225,34 @@
"banned_user": "Usuario Suspendido"
},
"user": {
"user_bio": "",
"bio_flags": "",
"username_flags": ""
"user_bio": "marcas para este usuario",
"bio_flags": "marcas para esta biografia",
"username_flags": "marcas para este nombre de usuario"
},
"configure": {
"closed-stream-settings": "Mensaje cuando los comentarios están cerrados en el artículo",
"closed-stream-settings": "Mensaje a enviar cuando los comentarios están cerrados en el artículo",
"stream-settings": "Configuración de Comentarios",
"moderation-settings": "Configuración de Moderación",
"tech-settings": "Configuración Technical",
"tech-settings": "Configuración Técnica",
"custom-css-url": "URL CSS a medida",
"custom-css-url-desc": "URL de una hoja de estilo que va a sobrescribir los estilos por defecto de Embed Stream. Puede ser interna o externa.",
"custom-css-url-desc": "URL de una hoja de estilo que va a sobrescribir los estilos por defecto del hilo de comentarios. Puede ser interna o externa.",
"dashboard": "Panel",
"enable-pre-moderation": "Habilitar pre-moderación",
"enable-pre-moderation-text": "Los moderadores deben aprobar cada comentario antes de que sea publicado.",
"require-email-verification": "Necesita confirmación de correo",
"require-email-verification-text": "Nuevos usuarios deben verificar sus correos antes de comentar",
"require-email-verification": "Necesita confirmación de e-mail",
"require-email-verification-text": "Nuevos usuarios deben verificar sus e-mails antes de comentar",
"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",
"enable-premod-links": "Pre-Moderar Commentarios que contienen Links",
"enable-premod-links-text": "Los y las Moderadoras deben probar cualquier comentario que contengan links antes de su publicación.",
"wordlist": "Palabras Suspendidas y Suspechosas",
"banned-word-text": "Comentarios que contengan estas palabras o frases, no separadas por comas y en mayusculas o minusuculas, serán automaticamente separadas de los comentarios publicados.",
"suspect-word-text": "Comments which contain these words or phrases (not case-sensitive) will be highlighted in the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list.",
"banned-words-title": "Banned words list",
"suspect-words-title": "Suspect words list",
"enable-premod-links": "Pre-Moderar Commentarios que contienen Enlaces",
"enable-premod-links-text": "Los y las Moderadoras deben aprobar cualquier comentario que contengan links antes de su publicación.",
"wordlist": "Palabras Suspendidas y Sospechosas",
"banned-word-text": "Comentarios que contengan estas palabras o frases, no separadas por comas y en mayusculas o minusuculas, serán automaticamente marcadas para separar los comentarios publicados.",
"suspect-word-text": "Comentarios que contengan estas palabras o frases, considerando mayusculas y minusculas, serán automaticamente destacadas en los comentarios publicados. Escribir una palabra y apretar Enter o Tabulador para agergarla. Opcionalmente pegar una lista separada por coma.",
"banned-words-title": "Lista de palabras prohibidas",
"suspect-words-title": "Lista de palabras sospechosas",
"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",
@@ -268,21 +271,21 @@
"comment-count-text-post": " caracteres",
"comment-count-error": "Por favor escribe un número válido.",
"domain-list-title": "Lista de Dominios Permitidos",
"domain-list-text": "Agrega dominios permitidos a Talk, e.g. tu localhost, staging y ambientes de production (ex. localhost:3000, staging.domain.com, domain.com)."
"domain-list-text": "Agrega dominios permitidos a Talk, por ejemplo tu localhost, staging y ambientes de producción (ej. localhost:3000, staging.domain.com, domain.com)."
},
"embedlink": {
"copy": "Copiar"
},
"bandialog": {
"ban_user": "Quieres suspender el Usuario?",
"are_you_sure": "Estas segura que quieres suspender a {0}?",
"note": "Nota: Suspender este usuario también va a colocar este comentario en la cola de Rechazados.",
"ban_user": "¿Quieres suspender el Usuario?",
"are_you_sure": "¿Estás segura que quieres suspender a {0}?",
"note": "Nota: Suspender a este usuario también va a colocar este comentario en la cola de Rechazados.",
"cancel": "Cancelar",
"yes_ban_user": "Si, Suspendan el usuario"
},
"dashbord": {
"next-update": "{0} minutos hasta la siguiente actualización.",
"auto-update": "Los datos se actualizan automaticamente cada 5 minutos o cuando Recargas.",
"auto-update": "Los datos se actualizan automaticamente cada 5 minutos o cuando recargas.",
"no_flags": "¡Nadie ha marcado nada en los últimos 5 minutos! ¡Bravo!",
"no_likes": "A nadie le ha gustado algún comentario en los últimos 5 minutos. Todo tranquilo.",
"flags": "Marcados",
@@ -290,21 +293,19 @@
"comment_count": "comentarios"
},
"streams": {
"empty_result": "No se encuentro articulo con esta busqueda. Tal vez extender la busqueda?",
"search": "",
"filter-streams": "",
"stream-status": "",
"all": "",
"open": "",
"closed": "",
"newest": "",
"oldest": "",
"sort-by": "",
"open": "",
"closed": "",
"empty_result": "No se encuentro articulo con esta busqueda. ¿Tal vez puedas extender la busqueda?",
"search": "buscar",
"filter-streams": "Filtrar Hilos de Comentarios",
"stream-status": "Estado del Hilo de Comentarios",
"all": "todxs",
"open": "abrir",
"closed": "cerrado",
"newest": "más nuevoß",
"oldest": "más viejo",
"sort-by": "ordenar por",
"article": "artículo",
"pubdate": "",
"status": ""
"pubdate": "Fecha de Pblicación",
"status": "Estado"
}
}
}
+3 -3
View File
@@ -9,7 +9,7 @@
"enablePremodLinks": "Pre-Moderate Comments Containing Links",
"enablePremodLinksDescription": "Moderators must approve any comment containing a link before its published.",
"enableQuestionBox": "Ask readers a question",
"enableQuestionBoxDescription": "This question will appear at the top of this comment stram. Ask readers about a certain issue in the article or pose discussion questions, etc.",
"enableQuestionBoxDescription": "This question will appear at the top of this comment stream. Ask readers about a certain issue in the article or pose discussion questions, etc.",
"includeQuestionHere": "Write your question here."
}
},
@@ -20,8 +20,8 @@
"description": "Como Administrador/a puedes modificar las opciones de los comentarios en este artículo",
"enablePremod": "Activar Pre Moderación",
"enablePremodDescription": "Los y las Moderadoras deben aprobar cualquier comentario antes de su publicación",
"enablePremodLinks": "Pre-Moderar Commentarios que contienen Links",
"enablePremodLinksDescription": "Los y las Moderadoras deben probar cualquier comentario que contengan links antes de su publicación.",
"enablePremodLinks": "Pre-Moderar Comentarios que contienen Enlaces",
"enablePremodLinksDescription": "Los y las moderadoras deben aprobar cualquier comentario que contengan enlaces antes de su publicación.",
"enableQuestionBox": "Hacer una pregunta a los y las lectoras.",
"enableQuestionBoxDescription": "Esta pregunta aparecera en la parte de arriba del hilo de comentarios.",
"includeQuestionHere": "Escribir la pregunta aquí."
+2 -1
View File
@@ -1,9 +1,10 @@
.Reply {
position: relative;
margin-bottom: 15px;
}
.Comment {
margin-bottom: 15px;
}
.pendingComment {
+3 -3
View File
@@ -122,8 +122,8 @@ class Embed extends Component {
<div className="commentStream">
<Slot fill="Stream"/>
<TabBar onChange={this.changeTab} activeTab={activeTab}>
<Tab><Count count={asset.commentCount}/></Tab>
<Tab>{lang.t('profile')}</Tab>
<Tab><Count count={asset.totalCommentCount}/></Tab>
<Tab>{lang.t('MY_COMMENTS')}</Tab>
<Tab restricted={!isAdmin}>Configure Stream</Tab>
</TabBar>
{loggedIn && <UserBox user={user} logout={() => this.props.logout().then(refetch)} changeTab={this.changeTab}/>}
@@ -162,7 +162,6 @@ class Embed extends Component {
charCount={asset.settings.charCountEnable && asset.settings.charCount} />
: null
}
<ModerationLink assetId={asset.id} isAdmin={isAdmin} />
</RestrictedContent>
</div>
: <p>{asset.settings.closedMessage}</p>
@@ -172,6 +171,7 @@ class Embed extends Component {
refetch={refetch}
offset={signInOffset}/>}
{loggedIn && user && <ChangeUsernameContainer loggedIn={loggedIn} offset={signInOffset} user={user} />}
{loggedIn && <ModerationLink assetId={asset.id} isAdmin={isAdmin} />}
{
highlightedComment &&
<Comment
+9 -8
View File
@@ -44,14 +44,15 @@ class LoadMore extends React.Component {
render () {
const {assetId, comments, loadMore, moreComments, parentId, replyCount, topLevel} = this.props;
return moreComments
? <Button
className='coral-load-more'
onClick={() => {
this.initialState = false;
loadMoreComments(assetId, comments, loadMore, parentId);
}}>
{topLevel ? lang.t('viewMoreComments') : this.replyCountFormat(replyCount)}
</Button>
? <div className='coral-load-more'>
<Button
onClick={() => {
this.initialState = false;
loadMoreComments(assetId, comments, loadMore, parentId);
}}>
{topLevel ? lang.t('viewMoreComments') : this.replyCountFormat(replyCount)}
</Button>
</div>
: null;
}
}
+8 -3
View File
@@ -212,6 +212,7 @@ hr {
.coral-plugin-commentcontent-text {
margin-bottom: 7px;
font-size: 16px;
}
.coral-plugin-author-name-text {
@@ -417,8 +418,11 @@ button.comment__action-button[disabled],
/* Load More */
button.coral-load-more {
width: 100%;
.coral-load-more {
text-align: center;
}
.coral-load-more button {
text-align: center;
color: #FFF;
background-color: #2376D8;
@@ -427,9 +431,10 @@ button.coral-load-more {
border-radius: 2px;
line-height: 1em;
text-transform: capitalize;
display: inline-block;
}
button.coral-load-more:hover {
.coral-load-more:hover button {
background-color: #4399FF;
}
+1 -1
View File
@@ -2,7 +2,7 @@ import * as actions from '../constants/asset';
import coralApi from '../helpers/response';
import {addNotification} from '../actions/notification';
import I18n from 'coral-framework/modules/i18n/i18n';
import I18n from '../../coral-framework/modules/i18n/i18n';
import translations from './../translations';
const lang = new I18n(translations);
+4 -2
View File
@@ -1,8 +1,9 @@
import I18n from 'coral-framework/modules/i18n/i18n';
import I18n from '../../coral-framework/modules/i18n/i18n';
import translations from './../translations';
const lang = new I18n(translations);
import * as actions from '../constants/auth';
import coralApi, {base} from '../helpers/response';
import {pym} from 'coral-framework';
// Dialog Actions
export const showSignInDialog = (offset = 0) => ({type: actions.SHOW_SIGNIN_DIALOG, offset});
@@ -135,7 +136,8 @@ const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILU
export const fetchForgotPassword = email => (dispatch) => {
dispatch(forgotPassowordRequest(email));
coralApi('/account/password/reset', {method: 'POST', body: {email}})
const redirectUri = pym.parentUrl || location.href;
coralApi('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}})
.then(() => dispatch(forgotPassowordSuccess()))
.catch(error => dispatch(forgotPassowordFailure(error)));
};
@@ -1,4 +1,4 @@
import {pym} from 'coral-framework';
import {pym} from '../../coral-framework';
export const addNotification = (notifType, text) => {
pym.sendMessage('coral-alert', `${notifType}|${text}`);
+8
View File
@@ -1,13 +1,21 @@
import {addNotification} from '../actions/notification';
import coralApi from '../helpers/response';
import * as actions from '../constants/auth';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from './../translations';
const lang = new I18n(translations);
const editUsernameFailure = error => ({type: actions.EDIT_USERNAME_FAILURE, error});
const editUsernameSuccess = () => ({type: actions.EDIT_USERNAME_SUCCESS});
export const editName = (username) => (dispatch) => {
return coralApi('/account/username', {method: 'PUT', body: {username}})
.then(() => {
dispatch(editUsernameSuccess());
dispatch(addNotification('success', lang.t('successNameUpdate')));
})
.catch(error => {
dispatch(editUsernameFailure(lang.t(`error.${error.translation_key}`)));
});
};
+5
View File
@@ -11,6 +11,11 @@ export const CREATE_USERNAME = 'CREATE_USERNAME';
export const SHOW_CREATEUSERNAME_DIALOG = 'SHOW_CREATEUSERNAME_DIALOG';
export const HIDE_CREATEUSERNAME_DIALOG = 'HIDE_CREATEUSERNAME_DIALOG';
export const EDIT_USERNAME_REQUEST = 'CREATE_USERNAME_REQUEST';
export const EDIT_USERNAME_SUCCESS = 'CREATE_USERNAME_SUCCESS';
export const EDIT_USERNAME_FAILURE = 'CREATE_USERNAME_FAILURE';
export const EDIT_USERNAME = 'CREATE_USERNAME';
export const FETCH_SIGNUP_REQUEST = 'FETCH_SIGNUP_REQUEST';
export const FETCH_SIGNUP_FAILURE = 'FETCH_SIGNUP_FAILURE';
export const FETCH_SIGNUP_SUCCESS = 'FETCH_SIGNUP_SUCCESS';
@@ -29,6 +29,7 @@ query AssetQuery($asset_id: ID, $asset_url: String!, $comment_id: ID!, $has_comm
requireEmailConfirmation
}
commentCount
totalCommentCount
comments(limit: 10) {
...commentView
replyCount
+15 -12
View File
@@ -1,5 +1,6 @@
{
"en": {
"MY_COMMENTS": "My Comments",
"profile": "Profile",
"successUpdateSettings": "The changes you have made have been applied to the comment stream on this article",
"successNameUpdate": "Your username has been updated",
@@ -15,7 +16,7 @@
"viewReply": "view reply",
"viewAllRepliesInitial": "view all {0} replies",
"viewAllReplies": "view {0} replies",
"newCount": "View {0} more {1}",
"newCount": "View {0} new {1}",
"comment": "comment",
"comments": "comments",
"error": {
@@ -40,13 +41,15 @@
}
},
"es": {
"profile": "Perfil",
"profile": "Pérfil",
"MY_COMMENTS": "Mis Comentarios",
"profile": "Pérfil",
"successUpdateSettings": "La configuración de este articulo fue actualizada",
"successBioUpdate": "Tu bio fue actualizada",
"successBioUpdate": "Tu biografia fue actualizada",
"contentNotAvailable": "El contenido no se encuentra disponible",
"bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios.",
"bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes gustar, marcar o escribir commentarios.",
"editNameMsg": "",
"viewMoreComments": "Var commentarios más",
"viewMoreComments": "Ver commentarios más",
"viewReply": "ver respuesta",
"viewAllRepliesInitial": "ver todas las {0} respuestas",
"viewAllReplies": "ver {0} respuestas",
@@ -54,22 +57,22 @@
"comment": "commentario",
"comments": "commentarios",
"error": {
"emailNotVerified": "Dirección de correo electrónico {0} no verificada.",
"email": "No es un email válido",
"emailNotVerified": "E-mail {0} no verificado.",
"email": "No es un e-mail válido",
"password": "La contraseña debe tener por lo menos 8 caracteres",
"username": "Los nombres pueden contener letras, números y _",
"organizationName": "El nombre de la organización debe contener letras y/o números.",
"confirmPassword": "Las contraseñas no coinciden",
"emailPasswordError": "Email y/o contraseña incorrecta.",
"EMAIL_REQUIRED": "Se requiere una dirección de correo electrónico",
"emailPasswordError": "E-mail y/o contraseña incorrecta.",
"EMAIL_REQUIRED": "Se requiere un e-mail",
"PASSWORD_REQUIRED": "Debe ingresar una contraseña",
"PASSWORD_LENGTH": "La contraseña es muy corta",
"EMAIL_IN_USE": "La dirección de correo electrónico se encuentra en uso",
"EMAIL_USERNAME_IN_USE": "Correo o Nombre en uso.",
"EMAIL_IN_USE": "El e-mail se encuentra en uso",
"EMAIL_USERNAME_IN_USE": "E-mail o Nombre en uso.",
"USERNAME_IN_USE": "Nombre en uso.",
"USERNAME_REQUIRED": "Debe ingresar un nombre",
"NO_SPECIAL_CHARACTERS": "Los nombres pueden contener letras, números y _",
"PROFANITY_ERROR": "Los nombres no pueden contener blasfemias. Por favor contacte al administrador si cree que esto es un error",
"PROFANITY_ERROR": "Los nombres no pueden contener blasfemias. Por favor contacte al o la administradora si cree que esto es un error",
"NOT_AUTHORIZED": "Acción no autorizada.",
"EDIT_USERNAME_NOT_AUTHORIZED": "No tiene permiso para editar el nombre de usuario."
}
+2 -2
View File
@@ -5,8 +5,8 @@
"commentIsBest": "This comment is one of the best"
},
"es": {
"like": "Establecer como mejor",
"liked": "Desarmado como mejor",
"setBest": "Etiquetar como el mejor",
"unsetBest": "Desetiquetar como el mejor",
"commentIsBest": "Este comentario es uno de los mejores"
}
}
@@ -13,12 +13,12 @@
"es": {
"post": "Publicar",
"cancel": "Cancelar",
"reply": "Respuesta",
"comment": "Escribe un Comentario",
"reply": "Responder",
"comment": "Publicar un Comentario",
"name": "Nombre",
"comment-post-notif": "Tu comentario ha sido publicado.",
"comment-post-notif-premod": "Gracias por comentar. Nuestro equipo de moderación va a revisarlo muy pronto.",
"comment-post-banned-word": "Tu comentario contiene una o más palabras que no estan permitidasen nuestro espacio, por lo que no será publicado. Si crees que es un error, por favor contacta a nuestro equipo de moderación.",
"comment-post-notif-premod": "Gracias por el comentario. Nuestro equipo de moderación va a revisarlo muy pronto.",
"comment-post-banned-word": "Tu comentario contiene una o más palabras que no estan permitidas en nuestro espacio, por lo que no será publicado. Si crees que es un error, por favor contacta a nuestro equipo de moderación.",
"characters-remaining": "carácteres restantes"
}
}
+14 -14
View File
@@ -25,28 +25,28 @@
"other": "Other"
},
"es": {
"report": "Informe",
"reported": "Informado",
"report-notif": "Gracias por marcar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar.",
"report-notif-remove": "Tu marca ha sido eliminada.",
"report": "Reportar",
"reported": "Reportado",
"report-notif": "Gracias por reportar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar.",
"report-notif-remove": "Tu reporte ha sido eliminada.",
"step-1-header": "Reportar un problema",
"step-2-header": "Ayudanos a entender",
"step-2-header": "Ayudanos a comprender",
"step-3-header": "Gracias por tu participación",
"flag-username": "Marcar el nombre de usuario",
"flag-comment": "Marcar el comentario",
"flag-username": "Reportar el nombre de usuario",
"flag-comment": "Reportar el comentario",
"continue": "Continuar",
"done": "hecho",
"no-agree-comment": "No estoy de acuerdo con este comentario",
"comment-offensive": "Este comentario es ofensivo",
"personal-info": "Este comentario muestra información personal",
"username-offensive": "Este nombre de usuario es ofensivo",
"no-like-username": "No me gusta ese nombre de usuario",
"bio-offensive": "Esta bio es ofensiva",
"no-like-bio": "No me gusta esta bio",
"user-impersonating": "Este usario suplanta a alguien",
"marketing": "Esto parece una publicidad/marketing",
"thank-you": "Nos interesa tu protección y comentarios. Un moderador va a mirar tu marca.",
"flag-reason": "Razón por la que marcar (Opcional)",
"no-like-username": "No me gusta este nombre de usuario",
"bio-offensive": "Esta biografia es ofensiva",
"no-like-bio": "No me gusta esta biografia",
"user-impersonating": "Este usuario suplanta a alguien",
"marketing": "Esto parece una propaganda",
"thank-you": "Valoramos tanto tu seguridad en este espacio como tus comentarios. Un o una moderadora van a leer tu reporte.",
"flag-reason": "Razón por la que hacer este reporte (Opcional)",
"other": "Otro"
}
}
+59 -1
View File
@@ -1,16 +1,74 @@
@custom-media --big-viewport (min-width: 780px);
.myComment {
border-bottom: 1px solid lightgrey;
display: flex;
align-items: baseline;
justify-content: space-between;
}
.myComment:last-child {
border-bottom: none;
border-bottom: solid 1px #EBEBEB;
}
.assetURL {
font-size: 16px;
color: black;
text-decoration: none;
font-weight: bold;
}
.commentBody {
}
.sidebar {
ul {
min-width: 136px;
}
li {
margin-bottom: 10px;
&:nth-child(1) {
color: #5394D7;
}
&:nth-child(2) {
color: #909090;
}
i {
margin-right: 5px;
font-size: 15px;
vertical-align: bottom;
}
a:hover {
cursor: pointer;
}
}
}
@custom-media --mobile-viewport (max-width: 480px);
@media (--mobile-viewport) {
.myComment {
flex-direction: column;
}
.sidebar ul {
display: flex;
li {
margin-right: 20px;
}
}
}
.pubdate {
display: inline-block;
font-size: inherit;
margin: inherit;
color: inherit;
}
+33 -4
View File
@@ -1,13 +1,42 @@
import React, {PropTypes} from 'react';
import {Icon} from '../coral-ui';
import styles from './Comment.css';
import PubDate from '../coral-plugin-pubdate/PubDate';
import Content from '../coral-plugin-commentcontent/CommentContent';
const Comment = props => {
return (
<div className={styles.myComment}>
<p className="myCommentAsset">
<a className={`${styles.assetURL} myCommentAnchor`} href='#' onClick={props.link(`${props.asset.url}#${props.comment.id}`)}>{props.asset.title ? props.asset.title : props.asset.url}</a>
</p>
<p className={`${styles.commentBody} myCommentBody`}>{props.comment.body}</p>
<div>
<Content
className={`${styles.commentBody} myCommentBody`}
body={props.comment.body}
/>
<p className="myCommentAsset">
<a
className={`${styles.assetURL} myCommentAnchor`}
href="#"
onClick={props.link(`${props.asset.url}`)}>
Story: {props.asset.title ? props.asset.title : props.asset.url}
</a>
</p>
</div>
<div className={styles.sidebar}>
<ul>
<li>
<a onClick={props.link(`${props.asset.url}#${props.comment.id}`)}>
<Icon name="open_in_new" />View Conversation
</a>
</li>
<li>
<Icon name="schedule" />
<PubDate
className={styles.pubdate}
created_at={props.comment.created_at}
/>
</li>
</ul>
</div>
</div>
);
};
@@ -3,6 +3,6 @@
"MODERATE_THIS_STREAM": "Moderate this stream"
},
"es": {
"MODERATE_THIS_STREAM": "Modera este stream"
"MODERATE_THIS_STREAM": "Modera este hilo de comentarios"
}
}
+2 -2
View File
@@ -11,12 +11,12 @@
},
"es":{
"profile": "Perfil",
"userNoComment": "No has dejado áun ningún comentario. ¡Unete a la conversación!",
"userNoComment": "No has dejado n ningún comentario. ¡Únete a la conversación!",
"allComments": "Todos los comentarios",
"profileSettings": "Configuración del perfil",
"myCommentHistory": "Mi historial de comentarios",
"signIn": "Entrar",
"toAccess": "para acceder a al perfil",
"fromSettingsPage": "Desde la peagina de configuración puede ver su historia de comentarios."
"fromSettingsPage": "Desde la página de configuración puedes ver tu historia de comentarios."
}
}
-102
View File
@@ -1,102 +0,0 @@
export default {
en: {
'signIn': {
emailVerifyCTA: 'Please verify your email address.',
requestNewVerifyEmail: 'Request another email:',
verifyEmail: 'Thank you for creating an account! We sent an email to the address you provided to verify your account.',
verifyEmail2: 'You must verify your account before engaging with the community.',
notYou: 'Not you?',
loggedInAs: 'Logged in as',
facebookSignIn: 'Sign in with Facebook',
facebookSignUp: 'Sign up with Facebook',
logout: 'Logout',
signIn: 'Sign in to join the conversation',
or: 'Or',
email: 'E-mail Address',
password: 'Password',
forgotYourPass: 'Forgot your password?',
needAnAccount: 'Need an account?',
register: 'Register',
signUp: 'Sign Up',
confirmPassword: 'Confirm Password',
username: 'Username',
alreadyHaveAnAccount: 'Already have an account?',
recoverPassword: 'Recover password',
emailInUse: 'Email address already in use',
emailORusernameInUse: 'Email address or Username already in use',
requiredField: 'This field is required',
passwordsDontMatch: 'Passwords don\'t match.',
specialCharacters: 'Usernames can contain letters, numbers and _ only',
checkTheForm: 'Invalid Form. Please, check the fields'
},
'createdisplay': {
writeyourusername: 'Edit your username',
yourusername: 'Your username appears on every comment you post.',
ifyoudontchangeyourname: 'If you don\'t change your username at this step, your Facebook display name will appear alongside of all your comments.',
username: 'Username',
continue: 'Continue with the same Facebook username',
save: 'Save',
fakecommentdate: '1 minute ago',
fakecommentbody: 'This is an example comment. Readers can share their thoughts and opinions with newsrooms in the comments section.',
requiredField: 'Required field',
errorCreate: 'Error when changing username',
checkTheForm: 'Invalid Form. Please, check the fields',
specialCharacters: 'Usernames can contain letters, numbers and _ only'
},
'permalink': {
permalink: 'Link'
},
'report': 'Report',
'like': 'Like',
},
es: {
'signIn': {
emailVerifyCTA: 'Por favor verifique su correo electronico.',
requestNewVerifyEmail: 'Enviar otro correo:',
verifyEmail: '¡Gracias por crear una cuenta! Le enviamos un correo a la dirección que dio para verificar su cuenta.',
verifyEmail2: 'Debe verificarla antes de poder involucrarse en la comunidad.',
notYou: 'No eres tu?',
loggedInAs: 'Entraste como',
facebookSignIn: 'Entrar con Facebook',
facebookSignUp: 'Regístrate con Facebook',
logout: 'Salir',
signIn: 'Entrar para Unirte a la Conversación',
or: 'o',
email: 'E-mail',
password: 'Contraseña',
forgotYourPass: 'Has olvidado tu contraseña?',
needAnAccount: 'Necesitas una cuenta?',
register: 'Regístrate',
signUp: 'Registro',
confirmPassword: 'Confirmar Contraseña',
username: 'Nombre',
alreadyHaveAnAccount: 'Ya tienes una cuenta?',
recoverPassword: 'Recuperar contraseña',
emailInUse: 'Este email se encuentra en uso',
emailORusernameInUse: 'Este email ó nombre se encuentran en uso',
requiredField: 'Este campo es requerido',
passwordsDontMatch: 'Las contraseñas no coinciden',
specialCharacters: 'Los nombres pueden contener letras, números y _',
checkTheForm: 'Formulario Inválido. Por favor, completa los campos'
},
'createdisplay': {
writeyourusername: 'Edita tu nombre',
yourusername: 'Tu nombre aparece en cada comentario que publiques.',
ifyoudontchangeyourname: 'Si no modificas tu nombre de usuario en este paso, tu nombre de Facebook aparecera al lado de cada comentario que publiques.',
username: 'Nombre',
continue: 'Continuar con nombre de Facebook',
save: 'Guardar',
fakecommentdate: 'hace un minuto',
fakecommentbody: 'Este es un comentario de ejemplo. Las lectoras pueden compartir sus ideas y opiniones con los periodistas en la sección de comentarios.',
requiredField: 'Campo necesario',
errorCreate: 'Hubo un error al cambiar el nombre de usuario',
checkTheForm: 'Formulario Invalido. Por favor, verifica los campos',
specialCharacters: 'Sólo pueden contener letras, números y _'
},
'permalink': {
permalink: 'Enlace'
},
'report': 'Informe',
'like': 'Me gusta',
}
};
+102
View File
@@ -0,0 +1,102 @@
{
"en": {
"signIn": {
"emailVerifyCTA": "Please verify your email address.",
"requestNewVerifyEmail": "Request another email:",
"verifyEmail": "Thank you for creating an account! We sent an email to the address you provided to verify your account.",
"verifyEmail2": "You must verify your account before engaging with the community.",
"notYou": "Not you?",
"loggedInAs": "Logged in as",
"facebookSignIn": "Sign in with Facebook",
"facebookSignUp": "Sign up with Facebook",
"logout": "Logout",
"signIn": "Sign in to join the conversation",
"or": "Or",
"email": "E-mail Address",
"password": "Password",
"forgotYourPass": "Forgot your password?",
"needAnAccount": "Need an account?",
"register": "Register",
"signUp": "Sign Up",
"confirmPassword": "Confirm Password",
"username": "Username",
"alreadyHaveAnAccount": "Already have an account?",
"recoverPassword": "Recover password",
"emailInUse": "Email address already in use",
"emailORusernameInUse": "Email address or Username already in use",
"requiredField": "This field is required",
"passwordsDontMatch": "Passwords don\"t match.",
"specialCharacters": "Usernames can contain letters, numbers and _ only",
"checkTheForm": "Invalid Form. Please, check the fields"
},
"createdisplay": {
"writeyourusername": "Edit your username",
"yourusername": "Your username appears on every comment you post.",
"ifyoudontchangeyourname": "If you don\"t change your username at this step, your Facebook display name will appear alongside of all your comments.",
"username": "Username",
"continue": "Continue with the same Facebook username",
"save": "Save",
"fakecommentdate": "1 minute ago",
"fakecommentbody": "This is an example comment. Readers can share their thoughts and opinions with newsrooms in the comments section.",
"requiredField": "Required field",
"errorCreate": "Error when changing username",
"checkTheForm": "Invalid Form. Please, check the fields",
"specialCharacters": "Usernames can contain letters, numbers and _ only"
},
"permalink": {
"permalink": "Link"
},
"report": "Report",
"like": "Like"
},
"es": {
"signIn": {
"emailVerifyCTA": "Por favor verifique su e-mail.",
"requestNewVerifyEmail": "Enviar otro correo:",
"verifyEmail": "¡Gracias por crear una cuenta! Le enviamos un correo a la dirección que dio para verificar su cuenta.",
"verifyEmail2": "Debe verificarla antes de poder involucrarse en la comunidad.",
"notYou": "¿No eres tu?",
"loggedInAs": "Entraste como",
"facebookSignIn": "Entrar con Facebook",
"facebookSignUp": "Regístrate con Facebook",
"logout": "Salir",
"signIn": "Entrar para Unirte a la Conversación",
"or": "o",
"email": "E-mail",
"password": "Contraseña",
"forgotYourPass": "¿Has olvidado tu contraseña?",
"needAnAccount": "¿Necesitas una cuenta?",
"register": "Regístrate",
"signUp": "Registro",
"confirmPassword": "Confirmar Contraseña",
"username": "Nombre",
"alreadyHaveAnAccount": "¿Ya tienes una cuenta?",
"recoverPassword": "Recuperar contraseña",
"emailInUse": "Este e-mail se encuentra en uso",
"emailORusernameInUse": "Este e-mail ó nombre de usuario se encuentran en uso",
"requiredField": "Este campo es requerido",
"passwordsDontMatch": "Las contraseñas no coinciden",
"specialCharacters": "Los nombres pueden contener letras, números y _",
"checkTheForm": "Formulario Inválido. Por favor, completa los campos"
},
"createdisplay": {
"writeyourusername": "Edita tu nombre",
"yourusername": "Tu nombre aparece en cada comentario que publiques.",
"ifyoudontchangeyourname": "Si no modificas tu nombre de usuario en este paso, tu nombre de Facebook aparecera al lado de cada comentario que publiques.",
"username": "Nombre",
"continue": "Continuar con nombre de Facebook",
"save": "Guardar",
"fakecommentdate": "hace un minuto",
"fakecommentbody": "Este es un comentario de ejemplo. Las lectoras pueden compartir sus ideas y opiniones con los periodistas en la sección de comentarios.",
"requiredField": "Campo necesario",
"errorCreate": "Hubo un error al cambiar el nombre de usuario",
"checkTheForm": "Formulario Inválido. Por favor, verifica los campos",
"specialCharacters": "Sólo pueden contener letras, números y _"
},
"permalink": {
"permalink": "Enlace"
},
"report": "Marcar",
"like": "Me gusta"
}
}
+1 -1
View File
@@ -19,7 +19,7 @@
}
.base li:hover {
background: #f3f3f3;
background: #d5d5d5;
cursor: pointer;
}
+1 -1
View File
@@ -103,7 +103,7 @@ const RootQuery = {
.then((ids) => {
// Perform the query using the available resolver.
return Users.getByQuery({ids, limit, cursor, sort});
return Users.getByQuery({ids, limit, cursor, sort}).find({status: 'PENDING'});
});
}
+4
View File
@@ -47,6 +47,10 @@ const SettingSchema = new Schema({
organizationName: {
type: String
},
autoCloseStream: {
type: Boolean,
default: false
},
closedTimeout: {
type: Number,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "talk",
"version": "1.1.0",
"version": "1.4.0",
"description": "A commenting platform from The Coral Project. https://coralproject.net",
"main": "app.js",
"scripts": {
+1 -1
View File
@@ -12,7 +12,7 @@ router.get('/password-reset', (req, res) => {
// TODO: store the redirect uri in the token or something fancy.
// admins and regular users should probably be redirected to different places.
res.render('admin/password-reset', {redirectUri: process.env.TALK_ROOT_URL});
res.render('admin/password-reset');
});
router.get('*', (req, res) => {
+6 -6
View File
@@ -41,14 +41,14 @@ router.post('/email/verify', (req, res, next) => {
* if it does, create a JWT and send an email
*/
router.post('/password/reset', (req, res, next) => {
const {email} = req.body;
const {email, loc} = req.body;
if (!email) {
return next('you must submit an email when requesting a password.');
}
UsersService
.createPasswordResetToken(email)
.createPasswordResetToken(email, loc)
.then((token) => {
// Check to see if the token isn't defined.
@@ -101,11 +101,11 @@ router.put('/password/reset', (req, res, next) => {
UsersService
.verifyPasswordResetToken(token)
.then((user) => {
return UsersService.changePassword(user.id, password);
.then(([user, loc]) => {
return Promise.all([UsersService.changePassword(user.id, password), loc]);
})
.then(() => {
res.status(204).end();
.then(([ , loc]) => {
res.json({redirect: loc});
})
.catch(() => {
next(authorization.ErrNotAuthorized);
+12 -2
View File
@@ -57,11 +57,21 @@ module.exports = class AssetsService {
static findOrCreateByUrl(url) {
// Check the URL to confirm that is in the domain whitelist
return domainlist.urlCheck(url).then((whitelisted) => {
return Promise.all([
domainlist.urlCheck(url),
SettingsService.retrieve()
]).then(([whitelisted, settings]) => {
const update = {$setOnInsert: {url}};
if (settings.autoCloseStream) {
update.$setOnInsert.closedAt = new Date(Date.now() + settings.closedTimeout * 1000);
}
if (!whitelisted) {
return Promise.reject(errors.ErrInvalidAssetURL);
} else {
return AssetModel.findOneAndUpdate({url}, {url}, {
return AssetModel.findOneAndUpdate({url}, update, {
// Ensure that if it's new, we return the new object created.
new: true,
+35 -10
View File
@@ -17,16 +17,41 @@ if (smtpRequiredProps.some(prop => !process.env[prop])) {
}
// load all the templates as strings
const templateStrings = {};
fs.readdir(path.join(__dirname, 'email'), (err, files) => {
if (err) {
throw err;
const templates = {
data: {}
};
// load the temlates per request during development
templates.render = (name, format = 'txt', context) => new Promise((resolve, reject) => {
// If we are in production mode, check the view cache.
if (process.env.NODE_ENV === 'production') {
if (name in templates.data && format in templates.data[name]) {
let view = templates.data[name][format];
return resolve(view(context));
}
}
files.forEach(file => {
fs.readFile(path.join(__dirname, 'email', file), 'utf8', (err, data) => {
templateStrings[file] = _.template(data);
});
const filename = path.join(__dirname, 'email', [name, format, 'ejs'].join('.'));
fs.readFile(filename, (err, file) => {
if (err) {
return reject(err);
}
let view = _.template(file);
// If we are in production mode, fill the view cache.
if (process.env.NODE_ENV === 'production') {
if (!(name in templates.data)) {
templates.data[name] = {};
}
templates.data[name][format] = view;
}
return resolve(view(context));
});
});
@@ -70,10 +95,10 @@ const mailer = module.exports = {
return Promise.all([
// Render the HTML version of the email.
templateStrings[`${template}.ejs`](locals),
templates.render(template, 'html', locals),
// Render the TEXT version of the email.
templateStrings[`${template}.txt.ejs`](locals)
templates.render(template, 'txt', locals)
])
.then(([html, text]) => {
+42 -27
View File
@@ -1,4 +1,5 @@
const bcrypt = require('bcrypt');
const url = require('url');
const jwt = require('jsonwebtoken');
const Wordlist = require('./wordlist');
@@ -16,6 +17,7 @@ const USER_ROLES = require('../models/user').USER_ROLES;
const RECAPTCHA_WINDOW_SECONDS = 60 * 10; // 10 minutes.
const RECAPTCHA_INCORRECT_TRIGGER = 5; // after 3 incorrect attempts, recaptcha will be required.
const SettingsService = require('./settings');
const ActionsService = require('./actions');
const MailerService = require('./mailer');
@@ -456,7 +458,7 @@ module.exports = class UsersService {
* @param {Function} done callback after the operation is complete
*/
static suspendUser(id, message) {
return UserModel.update({
return UserModel.findOneAndUpdate({
id
}, {
$set: {
@@ -464,31 +466,27 @@ module.exports = class UsersService {
canEditName: true
}
})
.then(() => {
return UsersService.findById(id)
.then((user) => {
if (message) {
let localProfile = user.profiles.find((profile) => profile.provider === 'local');
.then((user) => {
if (message) {
let localProfile = user.profiles.find((profile) => profile.provider === 'local');
if (localProfile) {
const options =
{
template: 'suspension', // needed to know which template to render!
locals: { // specifies the template locals.
body: message
},
subject: 'Email Suspension',
to: localProfile.id // This only works if the user has registered via e-mail.
// We may want a standard way to access a user's e-mail address in the future
};
if (localProfile) {
const options =
{
template: 'suspension', // needed to know which template to render!
locals: { // specifies the template locals.
body: message
},
subject: 'Email Suspension',
to: localProfile.id // This only works if the user has registered via e-mail.
// We may want a standard way to access a user's e-mail address in the future
};
return MailerService.sendSimple(options);
} else {
return Promise.reject(errors.ErrMissingEmail);
}
return MailerService.sendSimple(options);
} else {
return Promise.reject(errors.ErrMissingEmail);
}
});
}
});
}
@@ -524,15 +522,18 @@ module.exports = class UsersService {
* Creates a JWT from a user email. Only works for local accounts.
* @param {String} email of the local user
*/
static createPasswordResetToken(email) {
static createPasswordResetToken(email, loc) {
if (!email || typeof email !== 'string') {
return Promise.reject('email is required when creating a JWT for resetting passord');
}
email = email.toLowerCase();
return UserModel.findOne({profiles: {$elemMatch: {id: email}}})
.then((user) => {
return Promise.all([
UserModel.findOne({profiles: {$elemMatch: {id: email}}}),
SettingsService.retrieve()
])
.then(([user, settings]) => {
if (!user) {
// Since we don't want to reveal that the email does/doesn't exist
@@ -541,9 +542,21 @@ module.exports = class UsersService {
return;
}
let redirectDomain;
try {
redirectDomain = url.parse(loc).hostname;
} catch (e) {
return Promise.reject('redirect location is invalid');
}
if (settings.domains.whitelist.indexOf(redirectDomain) === -1) {
return Promise.reject('redirect location is not on the list of acceptable domains');
}
const payload = {
jti: uuid.v4(),
email,
loc,
userId: user.id,
version: user.__v
};
@@ -588,7 +601,9 @@ module.exports = class UsersService {
})
// TODO: add search by __v as well
.then((decoded) => UsersService.findById(decoded.userId));
.then((decoded) => {
return Promise.all([UsersService.findById(decoded.userId), decoded.loc]);
});
}
/**
+36 -16
View File
@@ -2,9 +2,13 @@ const debug = require('debug')('talk:services:wordlist');
const _ = require('lodash');
const natural = require('natural');
const tokenizer = new natural.WordTokenizer();
const nameTokenizer = new natural.RegexpTokenizer({pattern: /\_/});
const SettingsService = require('./settings');
const Errors = require('../errors');
// REGEX to prevent emoji's from entering the wordlist.
const EMOJI_REGEX = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*/;
/**
* The root wordlist object.
* @type {Object}
@@ -58,7 +62,27 @@ class Wordlist {
* @return {Array} the parsed list
*/
static parseList(list) {
return _.uniq(list.map((word) => tokenizer.tokenize(word.toLowerCase())));
return _.uniq(list.filter((word) => {
if (EMOJI_REGEX.test(word)) {
return false;
}
return true;
})
.map((word) => {
if (word.length === 1) {
return [word];
}
return tokenizer.tokenize(word.toLowerCase());
})
.filter((tokens) => {
if (tokens.length === 0) {
return false;
}
return true;
}));
}
/**
@@ -66,11 +90,11 @@ class Wordlist {
* @param {String} phrase value to check for blockwords.
* @return {Boolean} true if a blockword is found, false otherwise.
*/
match(list, phrase) {
match(list, phrase, tk = tokenizer) {
// Lowercase the word to ensure that we don't miss a match due to
// capitalization.
let lowerPhraseWords = tokenizer.tokenize(phrase.toLowerCase());
let lowerPhraseWords = tk.tokenize(phrase.toLowerCase());
// This will return true in the event that at least one blockword is found
// in the phrase.
@@ -199,28 +223,24 @@ class Wordlist {
}
/**
* check potential username for banned words, special characters
* check potential username for banned words
*/
static usernameCheck(username) {
const wl = new Wordlist();
return wl.load()
return wl
.load()
.then(() => {
username = username.replace(/_/g, '');
// test each word, and fail if we find a match
const hasBadWords = wl.lists.banned.some(phrase => {
return username.indexOf(phrase.join('')) !== -1;
});
if (hasBadWords) {
throw Errors.ErrContainsProfanity;
} else {
return Promise.resolve(username);
if (!wl.checkName(wl.lists.banned, username)) {
return Errors.ErrContainsProfanity;
}
});
}
checkName(list, name) {
return !this.match(list, name, nameTokenizer);
}
/**
* Connect middleware for scanning request bodies for wordlisted words and
* attaching a ErrContainsProfanity to the req.wordlisted parameter, otherwise
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "../../client/.babelrc"
}
@@ -1,30 +0,0 @@
import React from 'react';
import {shallow, mount} from 'enzyme';
import {expect} from 'chai';
import Comment from '../../../client/coral-plugin-history/Comment';
describe('coral-plugin-history/Comment', () => {
let render;
const comment = {body: 'this is a comment', id: '123'};
const asset = {url: 'https://google.com'};
beforeEach(() => {
render = shallow(<Comment asset={asset} comment={comment} link={()=>{}}/>);
});
it('should render the provided comment body', () => {
const wrapper = mount(<Comment asset={asset} comment={comment} link={()=>{}}/>);
expect(wrapper.find('.myCommentBody')).to.have.length(1);
expect(wrapper.find('.myCommentBody').text()).to.equal('this is a comment');
});
it('should render the asset url as a link', () => {
const wrapper = mount(<Comment asset={asset} comment={comment} link={()=>{}}/>);
expect(wrapper.find('.myCommentAnchor')).to.have.length(1);
expect(wrapper.find('.myCommentAnchor').text()).to.equal('https://google.com');
});
it('should render the comment with styles', () => {
expect(render.props().style).to.be.defined;
});
});
@@ -1,39 +0,0 @@
import React from 'react';
import {shallow, mount} from 'enzyme';
import {expect} from 'chai';
import CommentHistory from '../../../client/coral-plugin-history/CommentHistory';
describe('coral-plugin-history/CommentHistory', () => {
let render;
const comments = [{body: 'a comment or something', 'status_history':[{'type':'premod', 'created_at':'2016-12-09T01:40:53.327Z', 'assigned_by':null}, {'created_at':'2016-12-09T22:52:44.888Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-09T01:40:53.360Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-09T22:52:44.893Z', 'id':'3962c2ea-4ec4-42e4-b9bd-c571ff30f56b'}, {'body':'another comment', 'status_history':[{'type':'premod', 'created_at':'2016-12-09T22:53:43.148Z', 'assigned_by':null}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-09T22:53:43.158Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'premod', '__v':0, 'updated_at':'2016-12-09T22:53:43.158Z', 'id':'b51e27af-bcfd-4932-91be-e3f01a4802e6'}, {'body':'can I comment?', 'status_history':[{'type':'premod', 'created_at':'2016-12-13T23:23:47.123Z', 'assigned_by':null}, {'created_at':'2016-12-13T23:23:58.487Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'cef81015-1b53-4d70-b9af-6eca680f22fc', 'created_at':'2016-12-13T23:23:47.131Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-13T23:23:58.493Z', 'id':'dc9d7be1-b911-4dc3-8e1e-400e8b8d110e'}, {'body':'pre-mod comment', 'status_history':[{'type':'premod', 'created_at':'2016-12-08T21:34:56.994Z', 'assigned_by':null}, {'created_at':'2016-12-08T21:38:04.961Z', 'type':'rejected', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T21:34:56.997Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'rejected', '__v':0, 'updated_at':'2016-12-08T21:38:04.965Z', 'id':'6f02af16-a8f8-4ead-80ea-0d48824eb74d'}, {'body':'a flagged commetn', 'status_history':[{'type':'premod', 'created_at':'2016-12-08T21:38:26.342Z', 'assigned_by':null}, {'created_at':'2016-12-09T23:47:27.009Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T21:38:26.344Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-09T23:47:27.018Z', 'id':'784c5f91-36b9-4bda-b4ca-a114cef2c9f0'}, {'body':'a post mod comment', 'status_history':[{'type':'premod', 'created_at':'2016-12-08T22:19:05.870Z', 'assigned_by':null}, {'created_at':'2016-12-09T23:26:41.427Z', 'type':'accepted', 'assigned_by':'92256159-1164-4f66-9970-c7f23de7e461'}], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T22:19:05.874Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':'accepted', '__v':0, 'updated_at':'2016-12-09T23:26:41.450Z', 'id':'e8b86039-f850-4e53-bd9d-f8c9186a9637'}, {'body':'an actual post-mod comment here', 'status_history':[], 'asset_id':'96fddf96-7c83-4008-80ad-50091997d006', 'created_at':'2016-12-08T22:20:11.147Z', 'author_id':'92256159-1164-4f66-9970-c7f23de7e461', 'status':null, '__v':0, 'updated_at':'2016-12-08T22:20:11.147Z', 'id':'cff1a318-50c6-431e-9a63-de7a7b7136bf'}];
const asset = {
'settings': null,
'created_at':'2016-12-06T21:36:09.302Z',
'url':'localhost:3000/',
'scraped':null,
'status':'open',
'updated_at':'2016-12-08T02:11:15.943Z',
'_id':'58472f499e775a38f23d5da0',
'type':'article',
'closedMessage':null,
'id':'7302e637-f884-47c0-9723-02cc10a18617',
'closedAt':null
};
comments.forEach((comment) => {
comment.asset = asset;
});
beforeEach(() => {
render = shallow(<CommentHistory comments={comments} asset={asset} link={()=>{}}/>);
});
it('should render Comments as children when given comments and assets', () => {
const wrapper = mount(<CommentHistory comments={comments} asset={asset} link={()=>{}}/>);
expect(wrapper.find('.commentHistory__list').children()).to.have.length(7);
});
it('should render with styles', () => {
expect(render.props().style).to.be.defined;
});
});
+11
View File
@@ -249,6 +249,17 @@ describe('services.UsersService', () => {
done();
});
});
it('should not allow non-alphanumeric characters in usernames', () => {
return UsersService
.isValidUsername('hi🖕')
.then(() => {
expect(false).to.be.true;
})
.catch((err) => {
expect(err).to.be.truthy;
});
});
});
});
+37 -3
View File
@@ -9,7 +9,8 @@ describe('services.Wordlist', () => {
banned: [
'cookies',
'how to do bad things',
'how to do really bad things'
'how to do really bad things',
's h i t'
],
suspect: [
'do bad things'
@@ -32,9 +33,22 @@ describe('services.Wordlist', () => {
});
describe('#match', () => {
describe('#parseList', () => {
it('does not include emojis in the wordlist', () => {
let list = Wordlist.parseList([
'🖕',
'🖕 asdf',
'asd🖕asdf',
'asd🖕',
]);
const bannedList = Wordlist.parseList(wordlists.banned);
expect(list).to.have.length(0);
});
});
const bannedList = Wordlist.parseList(wordlists.banned);
describe('#match', () => {
it('does match on a bad word', () => {
[
@@ -62,6 +76,26 @@ describe('services.Wordlist', () => {
});
describe('#checkName', () => {
[
'flowers',
'joy',
'lots_of_candy'
].forEach((username) => {
it(`does not match on list=banned name=${username}`, () => {
expect(wordlist.checkName(bannedList, username)).to.be.true;
});
});
[
'cookies'
].forEach((username) => {
it(`does match on list=banned name=${username}`, () => {
expect(wordlist.checkName(bannedList, username)).to.be.false;
});
});
});
describe('#filter', () => {
before(() => wordlist.upsert(wordlists));
+1 -1
View File
@@ -21,7 +21,7 @@
<meta name="msapplication-TileColor" content="#ffffff">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.indigo-pink.min.css">
<link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.min.css">
<style media="screen">
body, #root {
width: 100%;
+1 -1
View File
@@ -126,7 +126,7 @@
},
data: JSON.stringify({password: password, token: location.hash.replace('#', '')})
}).then(function (success) {
location.href = '<%= redirectUri %>';
location.href = success.redirect;
}).catch(function (error) {
showError(error.responseText);
});
+126 -88
View File
@@ -49,7 +49,7 @@
"@types/express-serve-static-core" "*"
"@types/mime" "*"
abab@^1.0.0:
abab@^1.0.0, abab@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d"
@@ -76,7 +76,7 @@ acorn-globals@^1.0.4:
dependencies:
acorn "^2.1.0"
acorn-globals@^3.0.0:
acorn-globals@^3.0.0, acorn-globals@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf"
dependencies:
@@ -329,10 +329,6 @@ async@2.1.4, async@^2.1.2, async@^2.1.4:
dependencies:
lodash "^4.14.0"
async@~0.2.6:
version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
async@~0.9.0:
version "0.9.2"
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
@@ -401,7 +397,19 @@ babel-eslint@^7.2.1:
babel-types "^6.23.0"
babylon "^6.16.1"
babel-generator@^6.18.0, babel-generator@^6.24.0:
babel-generator@^6.18.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.22.0.tgz#d642bf4961911a8adc7c692b0c9297f325cda805"
dependencies:
babel-messages "^6.22.0"
babel-runtime "^6.22.0"
babel-types "^6.22.0"
detect-indent "^4.0.0"
jsesc "^1.3.0"
lodash "^4.2.0"
source-map "^0.5.0"
babel-generator@^6.24.0:
version "6.24.0"
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.24.0.tgz#eba270a8cc4ce6e09a61be43465d7c62c1f87c56"
dependencies:
@@ -568,7 +576,13 @@ babel-loader@^6.4.1:
mkdirp "^0.5.1"
object-assign "^4.0.1"
babel-messages@^6.22.0, babel-messages@^6.23.0:
babel-messages@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.22.0.tgz#36066a214f1217e4ed4164867669ecb39e3ea575"
dependencies:
babel-runtime "^6.22.0"
babel-messages@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
dependencies:
@@ -911,7 +925,14 @@ babel-plugin-transform-object-assign@^6.8.0:
dependencies:
babel-runtime "^6.22.0"
babel-plugin-transform-object-rest-spread@^6.22.0, babel-plugin-transform-object-rest-spread@^6.23.0:
babel-plugin-transform-object-rest-spread@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.22.0.tgz#1d419b55e68d2e4f64a5ff3373bd67d73c8e83bc"
dependencies:
babel-plugin-syntax-object-rest-spread "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-transform-object-rest-spread@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz#875d6bc9be761c58a2ae3feee5dc4895d8c7f921"
dependencies:
@@ -1036,7 +1057,17 @@ babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtim
core-js "^2.4.0"
regenerator-runtime "^0.10.0"
babel-template@^6.16.0, babel-template@^6.22.0, babel-template@^6.23.0, babel-template@^6.3.0:
babel-template@^6.16.0, babel-template@^6.22.0, babel-template@^6.3.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.22.0.tgz#403d110905a4626b317a2a1fcb8f3b73204b2edb"
dependencies:
babel-runtime "^6.22.0"
babel-traverse "^6.22.0"
babel-types "^6.22.0"
babylon "^6.11.0"
lodash "^4.2.0"
babel-template@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.23.0.tgz#04d4f270adbb3aa704a8143ae26faa529238e638"
dependencies:
@@ -1046,7 +1077,21 @@ babel-template@^6.16.0, babel-template@^6.22.0, babel-template@^6.23.0, babel-te
babylon "^6.11.0"
lodash "^4.2.0"
babel-traverse@^6.18.0, babel-traverse@^6.22.0, babel-traverse@^6.23.0, babel-traverse@^6.23.1:
babel-traverse@^6.18.0, babel-traverse@^6.22.0:
version "6.22.1"
resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.22.1.tgz#3b95cd6b7427d6f1f757704908f2fc9748a5f59f"
dependencies:
babel-code-frame "^6.22.0"
babel-messages "^6.22.0"
babel-runtime "^6.22.0"
babel-types "^6.22.0"
babylon "^6.15.0"
debug "^2.2.0"
globals "^9.0.0"
invariant "^2.2.0"
lodash "^4.2.0"
babel-traverse@^6.23.0, babel-traverse@^6.23.1:
version "6.23.1"
resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.23.1.tgz#d3cb59010ecd06a97d81310065f966b699e14f48"
dependencies:
@@ -1060,7 +1105,16 @@ babel-traverse@^6.18.0, babel-traverse@^6.22.0, babel-traverse@^6.23.0, babel-tr
invariant "^2.2.0"
lodash "^4.2.0"
babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23.0:
babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.22.0.tgz#2a447e8d0ea25d2512409e4175479fd78cc8b1db"
dependencies:
babel-runtime "^6.22.0"
esutils "^2.0.2"
lodash "^4.2.0"
to-fast-properties "^1.0.1"
babel-types@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.23.0.tgz#bb17179d7538bad38cd0c9e115d340f77e7e9acf"
dependencies:
@@ -2067,11 +2121,11 @@ csso@~2.3.1:
clap "^1.0.9"
source-map "^0.5.3"
cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0":
cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0", "cssom@>= 0.3.2 < 0.4.0":
version "0.3.2"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b"
"cssstyle@>= 0.2.29 < 0.3.0", "cssstyle@>= 0.2.36 < 0.3.0":
"cssstyle@>= 0.2.29 < 0.3.0", "cssstyle@>= 0.2.37 < 0.3.0":
version "0.2.37"
resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54"
dependencies:
@@ -2804,17 +2858,17 @@ exports-loader@^0.6.4:
source-map "0.5.x"
express-session@^1.15.1:
version "1.15.2"
resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.15.2.tgz#d98516443a4ccb8688e1725ae584c02daa4093d4"
version "1.15.1"
resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.15.1.tgz#9abba15971beea7ad98da5a4d25ed92ba4a2984e"
dependencies:
cookie "0.3.1"
cookie-signature "1.0.6"
crc "3.4.4"
debug "2.6.3"
debug "2.6.1"
depd "~1.1.0"
on-headers "~1.0.1"
parseurl "~1.3.1"
uid-safe "~2.1.4"
uid-safe "~2.1.3"
utils-merge "1.0.0"
express@^4.12.2:
@@ -2914,8 +2968,8 @@ fastparse@^1.1.1:
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
fbjs@^0.8.4:
version "0.8.11"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.11.tgz#340b590b8a2278a01ef7467c07a16da9b753db24"
version "0.8.8"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.8.tgz#02f1b6e0ea0d46c24e0b51a2d24df069563a5ad6"
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
@@ -3626,8 +3680,8 @@ highlight-words-core@^1.0.2:
babel-runtime "^6.11.6"
history@^3.0.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/history/-/history-3.3.0.tgz#fcedcce8f12975371545d735461033579a6dae9c"
version "3.2.1"
resolved "https://registry.yarnpkg.com/history/-/history-3.2.1.tgz#71c7497f4e6090363d19a6713bb52a1bfcdd99aa"
dependencies:
invariant "^2.2.1"
loose-envify "^1.2.0"
@@ -3762,7 +3816,7 @@ iconv-lite@0.4.13:
version "0.4.13"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
iconv-lite@0.4.15, iconv-lite@^0.4.13, iconv-lite@^0.4.5, iconv-lite@~0.4.13:
iconv-lite@0.4.15, iconv-lite@^0.4.5, iconv-lite@~0.4.13:
version "0.4.15"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
@@ -4209,10 +4263,6 @@ istanbul-lib-coverage@^1.0.0, istanbul-lib-coverage@^1.0.0-alpha, istanbul-lib-c
version "1.0.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.1.tgz#f263efb519c051c5f1f3343034fc40e7b43ff212"
istanbul-lib-coverage@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.2.tgz#87a0c015b6910651cb3b184814dfb339337e25e1"
istanbul-lib-hook@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.0.tgz#fc5367ee27f59268e8f060b0c7aaf051d9c425c5"
@@ -4232,15 +4282,15 @@ istanbul-lib-instrument@^1.3.0:
semver "^5.3.0"
istanbul-lib-instrument@^1.6.2:
version "1.7.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.0.tgz#b8e0dc25709bb44e17336ab47b7bb5c97c23f659"
version "1.6.2"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.6.2.tgz#dac644f358f51efd6113536d7070959a0111f73b"
dependencies:
babel-generator "^6.18.0"
babel-template "^6.16.0"
babel-traverse "^6.18.0"
babel-types "^6.18.0"
babylon "^6.13.0"
istanbul-lib-coverage "^1.0.2"
istanbul-lib-coverage "^1.0.0"
semver "^5.3.0"
istanbul-lib-report@^1.0.0-alpha.3:
@@ -4352,29 +4402,28 @@ jsdom@^7.0.2:
xml-name-validator ">= 2.0.1 < 3.0.0"
jsdom@^9.8.3:
version "9.9.1"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.9.1.tgz#84f3972ad394ab963233af8725211bce4d01bfd5"
version "9.12.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4"
dependencies:
abab "^1.0.0"
acorn "^2.4.0"
acorn-globals "^1.0.4"
abab "^1.0.3"
acorn "^4.0.4"
acorn-globals "^3.1.0"
array-equal "^1.0.0"
content-type-parser "^1.0.1"
cssom ">= 0.3.0 < 0.4.0"
cssstyle ">= 0.2.36 < 0.3.0"
cssom ">= 0.3.2 < 0.4.0"
cssstyle ">= 0.2.37 < 0.3.0"
escodegen "^1.6.1"
html-encoding-sniffer "^1.0.1"
iconv-lite "^0.4.13"
nwmatcher ">= 1.3.9 < 2.0.0"
parse5 "^1.5.1"
request "^2.55.0"
sax "^1.1.4"
symbol-tree ">= 3.1.0 < 4.0.0"
tough-cookie "^2.3.1"
webidl-conversions "^3.0.1"
request "^2.79.0"
sax "^1.2.1"
symbol-tree "^3.2.1"
tough-cookie "^2.3.2"
webidl-conversions "^4.0.0"
whatwg-encoding "^1.0.1"
whatwg-url "^4.1.0"
xml-name-validator ">= 2.0.1 < 3.0.0"
whatwg-url "^4.3.0"
xml-name-validator "^2.0.1"
jsesc@^1.3.0:
version "1.3.0"
@@ -5114,8 +5163,8 @@ mongodb@2.2.25:
readable-stream "2.1.5"
mongoose@^4.9.1:
version "4.9.2"
resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-4.9.2.tgz#df137675eed76a14dc1e6952ede54497c7547926"
version "4.9.1"
resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-4.9.1.tgz#e621d9e7356f46d1e39980a71063857405fa9099"
dependencies:
async "2.1.4"
bson "~1.0.4"
@@ -7081,7 +7130,7 @@ sax@0.5.x:
version "0.5.8"
resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
sax@^1.1.4, sax@~1.2.1:
sax@^1.1.4, sax@^1.2.1, sax@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
@@ -7306,9 +7355,9 @@ source-list-map@^0.1.7:
version "0.1.8"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106"
source-list-map@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.1.tgz#1a33ac210ca144d1e561f906ebccab5669ff4cb4"
source-list-map@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.0.1.tgz#cc1fc17122ae0a51978024c2cc0f8c35659026b8"
source-map-support@^0.4.2:
version "0.4.11"
@@ -7510,8 +7559,8 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
style-loader@^0.16.0:
version "0.16.1"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.16.1.tgz#50e325258d4e78421dd9680636b41e8661595d10"
version "0.16.0"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.16.0.tgz#5f001a9bf58fff9fd40f8aa3b9738ab99d4000c7"
dependencies:
loader-utils "^1.0.2"
@@ -7558,7 +7607,7 @@ supports-color@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e"
supports-color@3.1.2:
supports-color@3.1.2, supports-color@^3.1.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
dependencies:
@@ -7572,7 +7621,7 @@ supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3:
supports-color@^3.1.2, supports-color@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
dependencies:
@@ -7598,7 +7647,7 @@ symbol-observable@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
"symbol-tree@>= 3.1.0 < 4.0.0":
"symbol-tree@>= 3.1.0 < 4.0.0", symbol-tree@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.1.tgz#8549dd1d01fa9f893c18cc9ab0b106b4d9b168cb"
@@ -7762,7 +7811,7 @@ touch@1.0.0:
dependencies:
nopt "~1.0.10"
tough-cookie@^2.0.0, tough-cookie@^2.2.0, tough-cookie@^2.3.1, tough-cookie@~2.3.0:
tough-cookie@^2.0.0, tough-cookie@^2.2.0, tough-cookie@^2.3.2, tough-cookie@~2.3.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
dependencies:
@@ -7833,18 +7882,9 @@ uc.micro@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192"
uglify-js@^2.6, uglify-js@^2.6.1:
version "2.7.5"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8"
dependencies:
async "~0.2.6"
source-map "~0.5.1"
uglify-to-browserify "~1.0.0"
yargs "~3.10.0"
uglify-js@^2.8.5:
version "2.8.17"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.17.tgz#b68ea00a1cef853960bc99b8dec7740d2553f20b"
uglify-js@^2.6, uglify-js@^2.6.1, uglify-js@^2.8.5:
version "2.8.15"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.15.tgz#835dd4cd5872554756e6874508d0d0561704d94d"
dependencies:
source-map "~0.5.1"
yargs "~3.10.0"
@@ -7859,19 +7899,13 @@ uid-number@~0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
uid-safe@2.1.3:
uid-safe@2.1.3, uid-safe@~2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.3.tgz#077e264a00b3187936b270bb7376a26473631071"
dependencies:
base64-url "1.3.3"
random-bytes "~1.0.0"
uid-safe@~2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.4.tgz#3ad6f38368c6d4c8c75ec17623fb79aa1d071d81"
dependencies:
random-bytes "~1.0.0"
uid2@0.0.x:
version "0.0.3"
resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82"
@@ -8017,20 +8051,24 @@ webidl-conversions@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-2.0.1.tgz#3bf8258f7d318c7443c36f2e169402a1a6703506"
webidl-conversions@^3.0.0, webidl-conversions@^3.0.1:
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
webpack-sources@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.3.tgz#17c62bfaf13c707f9d02c479e0dcdde8380697fb"
webidl-conversions@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.1.tgz#8015a17ab83e7e1b311638486ace81da6ce206a0"
webpack-sources@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.0.tgz#fea93ba840f16cdd3f246f0ee95f88a9492c69fb"
dependencies:
source-list-map "^1.1.1"
source-list-map "^1.0.1"
source-map "~0.5.3"
webpack@^2.3.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.3.2.tgz#7d521e6f0777a3a58985c69425263fdfe977b458"
version "2.3.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.3.1.tgz#55bce8baffe7c1f9dc3029adc048643b448318a8"
dependencies:
acorn "^4.0.4"
acorn-dynamic-import "^2.0.0"
@@ -8050,7 +8088,7 @@ webpack@^2.3.1:
tapable "~0.2.5"
uglify-js "^2.8.5"
watchpack "^1.3.1"
webpack-sources "^0.2.3"
webpack-sources "^0.2.0"
yargs "^6.0.0"
whatwg-encoding@^1.0.1:
@@ -8069,9 +8107,9 @@ whatwg-url-compat@~0.6.5:
dependencies:
tr46 "~0.0.1"
whatwg-url@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.3.0.tgz#92aaee21f4f2a642074357d70ef8500a7cbb171a"
whatwg-url@^4.3.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.6.0.tgz#ef98da442273be04cf9632e176f257d2395a1ae4"
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
@@ -8172,7 +8210,7 @@ xdg-basedir@^2.0.0:
dependencies:
os-homedir "^1.0.0"
"xml-name-validator@>= 2.0.1 < 3.0.0":
"xml-name-validator@>= 2.0.1 < 3.0.0", xml-name-validator@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"