Merge branch 'master' into notification-warning
@@ -34,6 +34,7 @@ app.use(helmet({
|
||||
}));
|
||||
app.use(bodyParser.json());
|
||||
app.use('/client', express.static(path.join(__dirname, 'dist')));
|
||||
app.use('/public', express.static(path.join(__dirname, 'public')));
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
|
||||
@@ -24,10 +24,30 @@
|
||||
margin-bottom: 20px;
|
||||
align-items: flex-start;
|
||||
min-height: 100px;
|
||||
max-width: 600px;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
.copiedText {
|
||||
display: inline-block;
|
||||
color: #00796b;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.copyButton {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settingsError {
|
||||
@@ -41,7 +61,9 @@
|
||||
|
||||
.settingsHeader {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 7px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.disabledSettingText {
|
||||
@@ -58,11 +80,6 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.configSettingInfoBox p {
|
||||
font-size: 12px;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.configSettingEmbed {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
@@ -81,6 +98,8 @@
|
||||
border-color: #ccc;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 1px 0px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.charCountTexfieldEnabled {
|
||||
@@ -96,29 +115,18 @@
|
||||
color: white;
|
||||
}
|
||||
|
||||
.copiedText {
|
||||
color: #00796b;
|
||||
float: right;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.copyButton {
|
||||
float: right;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.embedInput {
|
||||
border-radius: 3px;
|
||||
border: 1px solid #ccc;
|
||||
width: 100%;
|
||||
display: block;
|
||||
width: 90%;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 10px;
|
||||
color: #555;
|
||||
padding: 14px;
|
||||
outline: none;
|
||||
border: 1px solid rgba(0,0,0,.12);
|
||||
padding: 6px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
margin: 5px auto;
|
||||
min-height: 175px;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.03em;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
#bannedWordlist, #suspectWordlist {
|
||||
@@ -170,3 +178,24 @@
|
||||
padding-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.Configure {
|
||||
|
||||
p {
|
||||
line-height: 1.2;
|
||||
max-width: 550px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 550px;
|
||||
}
|
||||
|
||||
.descriptionBox {
|
||||
margin-top: 15px;
|
||||
max-width: 550px;
|
||||
|
||||
input {
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,19 @@ const lang = new I18n(translations);
|
||||
const Domainlist = ({domains, onChangeDomainlist}) => {
|
||||
return (
|
||||
<Card id={styles.domainlist} className={styles.configSetting}>
|
||||
<h3>{lang.t('configure.domain-list-title')}</h3>
|
||||
<p className={styles.domainlistDesc}>{lang.t('configure.domain-list-text')}</p>
|
||||
<TagsInput
|
||||
value={domains}
|
||||
inputProps={{placeholder: 'URL'}}
|
||||
addOnPaste={true}
|
||||
pasteSplit={data => data.split(',').map(d => d.trim())}
|
||||
onChange={tags => onChangeDomainlist('whitelist', tags)}
|
||||
/>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.domain-list-title')}</div>
|
||||
<p className={styles.domainlistDesc}>{lang.t('configure.domain-list-text')}</p>
|
||||
<div className={styles.wrapper}>
|
||||
<TagsInput
|
||||
value={domains}
|
||||
inputProps={{placeholder: 'URL'}}
|
||||
addOnPaste={true}
|
||||
pasteSplit={data => data.split(',').map(d => d.trim())}
|
||||
onChange={tags => onChangeDomainlist('whitelist', tags)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -45,13 +45,19 @@ class EmbedLink extends Component {
|
||||
`.trim();
|
||||
return (
|
||||
<Card shadow="2" className={styles.configSetting}>
|
||||
<h3>Embed Comment Stream</h3>
|
||||
<p>{lang.t('configure.copy-and-paste')}</p>
|
||||
<textarea rows={5} type='text' className={styles.embedInput} value={embedText} readOnly={true}/>
|
||||
<Button raised className={styles.copyButton} onClick={this.copyToClipBoard} cStyle="black">
|
||||
{lang.t('embedlink.copy')}
|
||||
</Button>
|
||||
<div className={styles.copiedText}>{this.state.copied && 'Copied!'}</div>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.settingsHeader}>Embed Comment Stream</div>
|
||||
<p>{lang.t('configure.copy-and-paste')}</p>
|
||||
<textarea rows={5} type='text' className={styles.embedInput} value={embedText} readOnly={true}/>
|
||||
<div className={styles.actions}>
|
||||
<Button raised className={styles.copyButton} onClick={this.copyToClipBoard} cStyle="black">
|
||||
{lang.t('embedlink.copy')}
|
||||
</Button>
|
||||
<div className={styles.copiedText}>
|
||||
{this.state.copied && 'Copied!'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
|
||||
const off = styles.disabledSetting;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.Configure}>
|
||||
<Card className={`${styles.configSetting} ${settings.requireEmailConfirmation ? on : off}`}>
|
||||
<div className={styles.action}>
|
||||
<Checkbox
|
||||
|
||||
@@ -3,8 +3,8 @@ import {SelectField, Option} from 'react-mdl-selectfield';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../../translations.json';
|
||||
import styles from './Configure.css';
|
||||
import {Textfield, Checkbox} from 'react-mdl';
|
||||
import {Card, Icon} from 'coral-ui';
|
||||
import {Checkbox, Textfield} from 'react-mdl';
|
||||
import {Card, Icon, TextArea} from 'coral-ui';
|
||||
|
||||
const TIMESTAMPS = {
|
||||
weeks: 60 * 60 * 24 * 7,
|
||||
@@ -32,6 +32,11 @@ const updateInfoBoxEnable = (updateSettings, infoBox) => () => {
|
||||
updateSettings({infoBoxEnable});
|
||||
};
|
||||
|
||||
const updatePremodLinksEnable = (updateSettings, premodLinks) => () => {
|
||||
const premodLinksEnable = !premodLinks;
|
||||
updateSettings({premodLinksEnable});
|
||||
};
|
||||
|
||||
const updateInfoBoxContent = (updateSettings) => (event) => {
|
||||
const infoBoxContent = event.target.value;
|
||||
updateSettings({infoBoxContent});
|
||||
@@ -64,7 +69,7 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
|
||||
const off = styles.disabledSetting;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.Configure}>
|
||||
<Card className={`${styles.configSetting} ${settings.charCountEnable ? on : off}`}>
|
||||
<div className={styles.action}>
|
||||
<Checkbox
|
||||
@@ -79,7 +84,9 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
|
||||
className={`${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
|
||||
htmlFor='charCount'
|
||||
onChange={updateCharCount(updateSettings, settingsError)}
|
||||
value={settings.charCount}/>
|
||||
value={settings.charCount}
|
||||
disabled={settings.charCountEnable ? '' : 'disabled'}
|
||||
/>
|
||||
<span>{lang.t('configure.comment-count-text-post')}</span>
|
||||
{
|
||||
errors.charCount &&
|
||||
@@ -92,41 +99,56 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? on : off}`}>
|
||||
<Card className={`${styles.configSetting} ${settings.premodLinksEnable ? on : off}`}>
|
||||
<div className={styles.action}>
|
||||
<Checkbox
|
||||
onChange={updatePremodLinksEnable(updateSettings, settings.premodLinksEnable)}
|
||||
checked={settings.premodLinksEnable} />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.enable-premod-links')}</div>
|
||||
<p>
|
||||
{lang.t('configure.enable-premod-links-text')}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className={`${styles.configSetting} ${styles.configSettingInfoBox} ${settings.infoBoxEnable ? on : off}`}>
|
||||
<div className={styles.action}>
|
||||
<Checkbox
|
||||
onChange={updateInfoBoxEnable(updateSettings, settings.infoBoxEnable)}
|
||||
checked={settings.infoBoxEnable} />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
{lang.t('configure.include-comment-stream')}
|
||||
<p>
|
||||
<div className={styles.settingsHeader}>
|
||||
{lang.t('configure.include-comment-stream')}
|
||||
</div>
|
||||
<p className={settings.infoBoxEnable ? '' : styles.disabledSettingText}>
|
||||
{lang.t('configure.include-comment-stream-desc')}
|
||||
</p>
|
||||
<div className={`${styles.configSettingInfoBox} ${settings.infoBoxEnable ? null : styles.hidden}`} >
|
||||
<div className={styles.content}>
|
||||
<Textfield
|
||||
<div>
|
||||
<TextArea
|
||||
className={styles.descriptionBox}
|
||||
onChange={updateInfoBoxContent(updateSettings)}
|
||||
value={settings.infoBoxContent}
|
||||
label={lang.t('configure.include-text')}
|
||||
rows={3}/>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className={styles.configSettingInfoBox}>
|
||||
<div className={styles.content}>
|
||||
{lang.t('configure.closed-comments-desc')}
|
||||
<Card className={`${styles.configSetting} ${styles.configSettingInfoBox}`}>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.closed-stream-settings')}</div>
|
||||
<div className={styles.wrapper}>
|
||||
<p>{lang.t('configure.closed-comments-desc')}</p>
|
||||
<div>
|
||||
<Textfield
|
||||
onChange={updateClosedMessage(updateSettings)}
|
||||
value={settings.closedMessage}
|
||||
label={lang.t('configure.closed-comments-label')}
|
||||
rows={3}/>
|
||||
<TextArea className={styles.descriptionBox}
|
||||
onChange={updateClosedMessage(updateSettings)}
|
||||
value={settings.closedMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className={styles.configSettingInfoBox}>
|
||||
<Card className={`${styles.configSetting} ${styles.configSettingInfoBox}`}>
|
||||
<div className={styles.content}>
|
||||
{lang.t('configure.close-after')}
|
||||
<br />
|
||||
|
||||
@@ -14,19 +14,20 @@ const updateCustomCssUrl = (updateSettings) => (event) => {
|
||||
|
||||
const TechSettings = ({settings, onChangeDomainlist, updateSettings}) => {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.Configure}>
|
||||
<Domainlist
|
||||
domains={settings.domains.whitelist}
|
||||
onChangeDomainlist={onChangeDomainlist} />
|
||||
<EmbedLink />
|
||||
<Card className={styles.configSetting}>
|
||||
<h3>{lang.t('configure.custom-css-url')}</h3>
|
||||
<p>{lang.t('configure.custom-css-url-desc')}</p>
|
||||
<br />
|
||||
<input
|
||||
className={styles.customCSSInput}
|
||||
value={settings.customCssUrl}
|
||||
onChange={updateCustomCssUrl(updateSettings)} />
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.custom-css-url')}</div>
|
||||
<p>{lang.t('configure.custom-css-url-desc')}</p>
|
||||
<input
|
||||
className={styles.customCSSInput}
|
||||
value={settings.customCssUrl}
|
||||
onChange={updateCustomCssUrl(updateSettings)} />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,25 +8,29 @@ import {Card} from 'coral-ui';
|
||||
const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
|
||||
<div>
|
||||
<Card id={styles.bannedWordlist} className={styles.configSetting}>
|
||||
<h3>{lang.t('configure.banned-words-title')}</h3>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.banned-words-title')}</div>
|
||||
<p className={styles.wordlistDesc}>{lang.t('configure.banned-word-text')}</p>
|
||||
<TagsInput
|
||||
value={bannedWords}
|
||||
inputProps={{placeholder: 'word or phrase'}}
|
||||
addOnPaste={true}
|
||||
pasteSplit={data => data.split(',').map(d => d.trim())}
|
||||
onChange={tags => onChangeWordlist('banned', tags)}
|
||||
/>
|
||||
<div className={styles.wrapper}>
|
||||
<TagsInput
|
||||
value={bannedWords}
|
||||
inputProps={{placeholder: 'word or phrase'}}
|
||||
addOnPaste={true}
|
||||
pasteSplit={data => data.split(',').map(d => d.trim())}
|
||||
onChange={tags => onChangeWordlist('banned', tags)}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card id={styles.suspectWordlist} className={styles.configSetting}>
|
||||
<h3>{lang.t('configure.suspect-words-title')}</h3>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.suspect-words-title')}</div>
|
||||
<p className={styles.wordlistDesc}>{lang.t('configure.suspect-word-text')}</p>
|
||||
<TagsInput
|
||||
value={suspectWords}
|
||||
inputProps={{placeholder: 'word or phrase'}}
|
||||
addOnPaste={true}
|
||||
pasteSplit={data => data.split(',').map(d => d.trim())}
|
||||
onChange={tags => onChangeWordlist('suspect', tags)} />
|
||||
<div className={styles.wrapper}>
|
||||
<TagsInput
|
||||
value={suspectWords}
|
||||
inputProps={{placeholder: 'word or phrase'}}
|
||||
addOnPaste={true}
|
||||
pasteSplit={data => data.split(',').map(d => d.trim())}
|
||||
onChange={tags => onChangeWordlist('suspect', tags)} />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"copy": "Copy to Clipboard"
|
||||
},
|
||||
"configure": {
|
||||
"closed-stream-settings": "Closed Stream Message",
|
||||
"stream-settings": "Stream Settings",
|
||||
"moderation-settings": "Moderation Settings",
|
||||
"tech-settings": "Tech Settings",
|
||||
@@ -63,9 +64,11 @@
|
||||
"enable-pre-moderation-text": "Moderators must approve any comment before it is published.",
|
||||
"require-email-verification": "Require Email Verification",
|
||||
"require-email-verification-text": "New Users must verify their email before commenting",
|
||||
"include-comment-stream": "Include Comment Stream Description for Readers.",
|
||||
"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.",
|
||||
"enable-premod-links": "Pre-Moderate Comments Containing Links",
|
||||
"enable-premod-links-text": "Moderators must approve any comment containing a link before its published.",
|
||||
"comment-settings": "Settings",
|
||||
"embed-comment-stream": "Embed Stream",
|
||||
"banned-word-text": "Comments which contain these words or phrases (not case-sensitive) will be automatically removed from the comment stream. Type a word and press Enter or Tab to add. Optionally paste a comma-separated list.",
|
||||
@@ -79,7 +82,7 @@
|
||||
"configure": "Configure",
|
||||
"community": "Community",
|
||||
"streams": "Streams",
|
||||
"closed-comments-desc": "Write a message for closed threads",
|
||||
"closed-comments-desc": "Write a message to be displayed when when your comment stream is closed and no longer accepting comments.",
|
||||
"closed-comments-label": "Write a message...",
|
||||
"hours": "Hours",
|
||||
"days": "Days",
|
||||
@@ -87,7 +90,7 @@
|
||||
"close-after": "Close comments after",
|
||||
"comment-count-header": "Limit Comment Length",
|
||||
"comment-count-text-pre": "Comments will be limited to ",
|
||||
"comment-count-text-post": " characters.",
|
||||
"comment-count-text-post": " characters",
|
||||
"comment-count-error": "Please enter a valid number.",
|
||||
"domain-list-title": "Permitted Domains",
|
||||
"domain-list-text": "Enter the domains you would like to permit for Talk, e.g. your local, staging and production environments (ex. localhost:3000, staging.domain.com, domain.com)."
|
||||
@@ -178,6 +181,7 @@
|
||||
"username_flags": ""
|
||||
},
|
||||
"configure": {
|
||||
"closed-stream-settings": "Mensaje 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",
|
||||
@@ -193,6 +197,8 @@
|
||||
"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.",
|
||||
@@ -204,7 +210,7 @@
|
||||
"configure": "Configurar",
|
||||
"community": "Comunidad",
|
||||
"streams": "Streams",
|
||||
"closed-comments-desc": "Escribe un mensaje para cuando los comentarios se encuentran cerrados",
|
||||
"closed-comments-desc": "Escribe un mensaje que será mostrado cuando los comentarios estén cerrados y no se acepten más comentarios.",
|
||||
"closed-comments-label": "Escribe un mensaje...",
|
||||
"never": "Nunca",
|
||||
"hours": "Horas",
|
||||
|
||||
@@ -34,6 +34,18 @@ export default ({handleChange, handleApply, changed, updateQuestionBoxContent, .
|
||||
description: lang.t('configureCommentStream.enablePremodDescription')
|
||||
}} />
|
||||
</li>
|
||||
<li>
|
||||
<Checkbox
|
||||
className={styles.checkbox}
|
||||
cStyle={changed ? 'green' : 'darkGrey'}
|
||||
name="premodLinks"
|
||||
onChange={handleChange}
|
||||
defaultChecked={props.premodLinks}
|
||||
info={{
|
||||
title: lang.t('configureCommentStream.enablePremodLinks'),
|
||||
description: lang.t('configureCommentStream.enablePremodLinksDescription')
|
||||
}} />
|
||||
</li>
|
||||
<li>
|
||||
<Checkbox
|
||||
className={styles.checkbox}
|
||||
|
||||
@@ -31,13 +31,14 @@ class ConfigureStreamContainer extends Component {
|
||||
const questionBoxEnable = elements.qboxenable.checked;
|
||||
const questionBoxContent = elements.qboxcontent.value;
|
||||
|
||||
// const premodLinks = elements.premodLinks.checked;
|
||||
const premodLinksEnable = elements.premodLinks.checked;
|
||||
const {changed} = this.state;
|
||||
|
||||
const newConfig = {
|
||||
moderation: premod ? 'PRE' : 'POST',
|
||||
questionBoxEnable,
|
||||
questionBoxContent
|
||||
questionBoxContent,
|
||||
premodLinksEnable
|
||||
};
|
||||
|
||||
if (changed) {
|
||||
@@ -77,10 +78,9 @@ class ConfigureStreamContainer extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const status = this.props.asset.closedAt === null ? 'open' : 'closed';
|
||||
const premod = this.props.asset.settings.moderation === 'PRE';
|
||||
const questionBoxEnable = this.props.asset.settings.questionBoxEnable;
|
||||
const questionBoxContent = this.props.asset.settings.questionBoxContent;
|
||||
const {settings, closedAt} = this.props.asset;
|
||||
const status = closedAt === null ? 'open' : 'closed';
|
||||
const premod = settings.moderation === 'PRE';
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -88,11 +88,11 @@ class ConfigureStreamContainer extends Component {
|
||||
handleChange={this.handleChange}
|
||||
handleApply={this.handleApply}
|
||||
changed={this.state.changed}
|
||||
premodLinks={false}
|
||||
premodLinks={settings.premodLinks}
|
||||
premod={premod}
|
||||
updateQuestionBoxContent={this.updateQuestionBoxContent}
|
||||
questionBoxEnable={questionBoxEnable}
|
||||
questionBoxContent={questionBoxContent}
|
||||
questionBoxEnable={settings.questionBoxEnable}
|
||||
questionBoxContent={settings.questionBoxContent}
|
||||
/>
|
||||
<hr />
|
||||
<h3>{status === 'open' ? 'Close' : 'Open'} Comment Stream</h3>
|
||||
|
||||
@@ -80,6 +80,7 @@ hr {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Question Box Styles */
|
||||
@@ -220,7 +221,7 @@ hr {
|
||||
|
||||
.comment__action-container .material-icons {
|
||||
font-size: 12px;
|
||||
margin-left: 3px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
button.comment__action-button,
|
||||
|
||||
@@ -42,7 +42,7 @@ export const postComment = graphql(POST_COMMENT, {
|
||||
updateQueries: {
|
||||
AssetQuery: (oldData, {mutationResult:{data:{createComment:{comment}}}}) => {
|
||||
|
||||
if (oldData.asset.settings.moderation === 'PRE') {
|
||||
if (oldData.asset.settings.moderation === 'PRE' || comment.status === 'PREMOD' || comment.status === 'REJECTED') {
|
||||
return oldData;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
.textArea {
|
||||
textarea {
|
||||
width: 100%;
|
||||
display: block;
|
||||
outline: none;
|
||||
border: 1px solid rgba(0,0,0,.12);
|
||||
padding: 6px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
margin: 5px auto;
|
||||
min-height: 175px;
|
||||
font-size: 14px;
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import styles from './TextArea.css';
|
||||
|
||||
const TextArea = ({className, value = '', ...props}) => (
|
||||
<div className={`${styles.textArea} ${className ? className : ''}`}>
|
||||
<textarea value={value} {...props}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
TextArea.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export default TextArea;
|
||||
@@ -22,3 +22,4 @@ export {default as WizardNav} from './components/WizardNav';
|
||||
export {default as Select} from './components/Select';
|
||||
export {default as Option} from './components/Option';
|
||||
export {default as SnackBar} from './components/SnackBar';
|
||||
export {default as TextArea} from './components/TextArea';
|
||||
|
||||
@@ -3,6 +3,7 @@ const errors = require('../../errors');
|
||||
const AssetsService = require('../../services/assets');
|
||||
const ActionsService = require('../../services/actions');
|
||||
const CommentsService = require('../../services/comments');
|
||||
const linkify = require('linkify-it')();
|
||||
|
||||
const Wordlist = require('../../services/wordlist');
|
||||
|
||||
@@ -54,13 +55,16 @@ const createComment = ({user, loaders: {Comments}}, {body, asset_id, parent_id =
|
||||
* @param {String} body body of a comment
|
||||
* @return {Object} resolves to the wordlist results
|
||||
*/
|
||||
const filterNewComment = (context, {body}) => {
|
||||
const filterNewComment = (context, {body, asset_id}) => {
|
||||
|
||||
// Create a new instance of the Wordlist.
|
||||
const wl = new Wordlist();
|
||||
|
||||
// Load the wordlist and filter the comment content.
|
||||
return wl.load().then(() => wl.scan('body', body));
|
||||
return Promise.all([
|
||||
wl.load().then(() => wl.scan('body', body)),
|
||||
AssetsService.rectifySettings(AssetsService.findById(asset_id))
|
||||
]);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -72,7 +76,7 @@ const filterNewComment = (context, {body}) => {
|
||||
* @param {Object} [wordlist={}] the results of the wordlist scan
|
||||
* @return {Promise} resolves to the comment's status
|
||||
*/
|
||||
const resolveNewCommentStatus = (context, {asset_id, body}, wordlist = {}) => {
|
||||
const resolveNewCommentStatus = (context, {asset_id, body}, wordlist = {}, settings) => {
|
||||
|
||||
// Decide the status based on whether or not the current asset/settings
|
||||
// has pre-mod enabled or not. If the comment was rejected based on the
|
||||
@@ -82,6 +86,8 @@ const resolveNewCommentStatus = (context, {asset_id, body}, wordlist = {}) => {
|
||||
|
||||
if (wordlist.banned) {
|
||||
status = Promise.resolve('REJECTED');
|
||||
} else if (settings.premodLinksEnable && linkify.test(body)) {
|
||||
status = Promise.resolve('PREMOD');
|
||||
} else {
|
||||
status = AssetsService
|
||||
.rectifySettings(AssetsService.findById(asset_id).then((asset) => {
|
||||
@@ -131,13 +137,13 @@ const createPublicComment = (context, commentInput) => {
|
||||
// We then take the wordlist and the comment into consideration when
|
||||
// considering what status to assign the new comment, and resolve the new
|
||||
// status to set the comment to.
|
||||
.then((wordlist) => resolveNewCommentStatus(context, commentInput, wordlist)
|
||||
.then(([wordlist, settings]) => resolveNewCommentStatus(context, commentInput, wordlist, settings)
|
||||
|
||||
// Then we actually create the comment with the new status.
|
||||
.then((status) => createComment(context, commentInput, status))
|
||||
.then((comment) => {
|
||||
|
||||
// If the comment was flagged as being suspect, we need to add a
|
||||
// If the comment has a suspect word or a link, we need to add a
|
||||
// flag to it to indicate that it needs to be looked at.
|
||||
// Otherwise just return the new comment.
|
||||
|
||||
|
||||
@@ -370,6 +370,7 @@ type Settings {
|
||||
|
||||
infoBoxEnable: Boolean
|
||||
infoBoxContent: String
|
||||
premodLinksEnable: Boolean
|
||||
questionBoxEnable: Boolean
|
||||
questionBoxContent: String
|
||||
closeTimeout: Int
|
||||
|
||||
@@ -40,6 +40,10 @@ const SettingSchema = new Schema({
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
premodLinksEnable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
organizationName: {
|
||||
type: String
|
||||
},
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"inquirer": "^3.0.1",
|
||||
"jsonwebtoken": "^7.1.9",
|
||||
"kue": "^0.11.5",
|
||||
"linkify-it": "^2.0.3",
|
||||
"lodash": "^4.16.6",
|
||||
"metascraper": "^1.0.6",
|
||||
"minimist": "^1.2.0",
|
||||
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 872 B |
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/img\/android-icon-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "\/img\/android-icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "\/img\/android-icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "\/img\/android-icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "\/img\/android-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "\/img\/android-icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,6 +5,20 @@
|
||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
|
||||
<meta property="csrf" content="<%= csrfToken %>">
|
||||
<title>Talk - Coral Admin</title>
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/public/img/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/public/img/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/public/img/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/public/img/apple-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/public/img/apple-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/public/img/apple-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/public/img/apple-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/public/img/apple-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/public/img/apple-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/public/img/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/public/img/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/public/img/favicon-16x16.png">
|
||||
<link rel="manifest" href="/public/manifest.json">
|
||||
<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">
|
||||
|
||||