mirror of
https://github.com/wassname/talk.git
synced 2026-07-13 17:45:56 +08:00
Merge pull request #363 from coralproject/move-config-around
re-organize settings so they make a bit more sense
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
h3 {
|
||||
color: black;
|
||||
font-size: 1.76em;
|
||||
font-size: 1.26em;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,10 @@
|
||||
margin-bottom: 20px;
|
||||
align-items: flex-start;
|
||||
min-height: 100px;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.settingsError {
|
||||
@@ -118,18 +122,19 @@
|
||||
}
|
||||
|
||||
#bannedWordlist, #suspectWordlist {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
|
||||
display: block;
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.wordlistHeader {
|
||||
font-weight: bold;
|
||||
font-size:18px;
|
||||
margin-bottom:3px;
|
||||
.customCSSInput {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
padding: 14px;
|
||||
letter-spacing: 0.03em;
|
||||
color: #555;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.enabledSetting {
|
||||
@@ -150,7 +155,7 @@
|
||||
margin-top: 38px;
|
||||
}
|
||||
|
||||
.commentSettingsSection {
|
||||
.settingsSection {
|
||||
padding-bottom: 200px;
|
||||
.action {
|
||||
display: inline-block;
|
||||
|
||||
@@ -8,22 +8,20 @@ import {
|
||||
updateDomainlist
|
||||
} from '../../actions/settings';
|
||||
|
||||
import {Button, List, Item} from 'coral-ui';
|
||||
import {Button, List, Item, Card, Spinner} from 'coral-ui';
|
||||
import styles from './Configure.css';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../../translations.json';
|
||||
import EmbedLink from './EmbedLink';
|
||||
import CommentSettings from './CommentSettings';
|
||||
import Wordlist from './Wordlist';
|
||||
import Domainlist from './Domainlist';
|
||||
import has from 'lodash/has';
|
||||
import translations from 'coral-admin/src/translations.json';
|
||||
import StreamSettings from './StreamSettings';
|
||||
import ModerationSettings from './ModerationSettings';
|
||||
import TechSettings from './TechSettings';
|
||||
|
||||
class Configure extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
activeSection: 'comments',
|
||||
activeSection: 'stream',
|
||||
changed: false,
|
||||
errors: {}
|
||||
};
|
||||
@@ -70,40 +68,52 @@ class Configure extends Component {
|
||||
|
||||
getSection (section) {
|
||||
const pageTitle = this.getPageTitle(section);
|
||||
let sectionComponent;
|
||||
switch(section){
|
||||
case 'comments':
|
||||
return <CommentSettings
|
||||
title={pageTitle}
|
||||
fetchingSettings={this.props.fetchingSettings}
|
||||
case 'stream':
|
||||
sectionComponent = <StreamSettings
|
||||
settings={this.props.settings}
|
||||
updateSettings={this.onSettingUpdate}
|
||||
errors={this.state.errors}
|
||||
settingsError={this.onSettingError}/>;
|
||||
case 'embed':
|
||||
return has(this, 'props.settings.domains.whitelist')
|
||||
? <div>
|
||||
<Domainlist
|
||||
domains={this.props.settings.domains.whitelist}
|
||||
onChangeDomainlist={this.onChangeDomainlist}/>
|
||||
<EmbedLink title={pageTitle} />
|
||||
</div>
|
||||
: <EmbedLink title={pageTitle} />;
|
||||
case 'wordlist':
|
||||
return has(this, 'props.settings.wordlist')
|
||||
? <Wordlist
|
||||
bannedWords={this.props.settings.wordlist.banned}
|
||||
suspectWords={this.props.settings.wordlist.suspect}
|
||||
onChangeWordlist={this.onChangeWordlist} />
|
||||
: <p>loading wordlists</p>;
|
||||
break;
|
||||
case 'moderation':
|
||||
sectionComponent = <ModerationSettings
|
||||
title={pageTitle}
|
||||
onChangeWordlist={this.onChangeWordlist}
|
||||
fetchingSettings={this.props.settings.fetchingSettings}
|
||||
settings={this.props.settings}
|
||||
updateSettings={this.onSettingUpdate} />;
|
||||
break;
|
||||
case 'tech':
|
||||
sectionComponent = <TechSettings
|
||||
title={pageTitle}
|
||||
fetchingSettings={this.props.settings.fetchingSettings}
|
||||
onChangeDomainlist={this.onChangeDomainlist}
|
||||
settings={this.props.settings}
|
||||
updateSettings={this.onSettingUpdate} />;
|
||||
}
|
||||
|
||||
if (this.props.settings.fetchingSettings) {
|
||||
return <Card shadow="4"><Spinner/>Loading settings...</Card>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.settingsSection}>
|
||||
<h3>{pageTitle}</h3>
|
||||
{sectionComponent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getPageTitle (section) {
|
||||
switch(section) {
|
||||
case 'comments':
|
||||
return lang.t('configure.comment-settings');
|
||||
case 'embed':
|
||||
return lang.t('configure.embed-comment-stream');
|
||||
case 'stream':
|
||||
return lang.t('configure.stream-settings');
|
||||
case 'moderation':
|
||||
return lang.t('configure.moderation-settings');
|
||||
case 'tech':
|
||||
return lang.t('configure.tech-settings');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@@ -120,14 +130,14 @@ class Configure extends Component {
|
||||
<div className={styles.container}>
|
||||
<div className={styles.leftColumn}>
|
||||
<List onChange={this.changeSection} activeItem={activeSection}>
|
||||
<Item itemId='comments' icon="settings">
|
||||
{lang.t('configure.comment-settings')}
|
||||
<Item itemId='stream' icon='speaker_notes'>
|
||||
{lang.t('configure.stream-settings')}
|
||||
</Item>
|
||||
<Item itemId='embed' icon='code'>
|
||||
{lang.t('configure.embed-comment-stream')}
|
||||
<Item itemId='moderation' icon='thumbs_up_down'>
|
||||
{lang.t('configure.moderation-settings')}
|
||||
</Item>
|
||||
<Item itemId='wordlist' icon='settings'>
|
||||
{lang.t('configure.wordlist')}
|
||||
<Item itemId='tech' icon='code'>
|
||||
{lang.t('configure.tech-settings')}
|
||||
</Item>
|
||||
</List>
|
||||
<div className={styles.saveBox}>
|
||||
|
||||
@@ -9,19 +9,17 @@ const lang = new I18n(translations);
|
||||
|
||||
const Domainlist = ({domains, onChangeDomainlist}) => {
|
||||
return (
|
||||
<div>
|
||||
<Card id={styles.domainlist} className={styles.configSetting}>
|
||||
<h3>{lang.t('configure.domain-list-title')}</h3>
|
||||
<Card id={styles.domainlist}>
|
||||
<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)}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
<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)}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -44,19 +44,15 @@ class EmbedLink extends Component {
|
||||
"></script>
|
||||
`.trim();
|
||||
return (
|
||||
<div>
|
||||
<h3>{this.props.title}</h3>
|
||||
<div>
|
||||
<Card shadow="2">
|
||||
<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>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import styles from './Configure.css';
|
||||
import {Card} from 'coral-ui';
|
||||
import {Checkbox} from 'react-mdl';
|
||||
import Wordlist from './Wordlist';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../../translations.json';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const updateModeration = (updateSettings, mod) => () => {
|
||||
const moderation = mod === 'PRE' ? 'POST' : 'PRE';
|
||||
updateSettings({moderation});
|
||||
};
|
||||
|
||||
const updateEmailConfirmation = (updateSettings, verify) => () => {
|
||||
updateSettings({requireEmailConfirmation: !verify});
|
||||
};
|
||||
|
||||
const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
|
||||
|
||||
// just putting this here for shorthand below
|
||||
const on = styles.enabledSetting;
|
||||
const off = styles.disabledSetting;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card className={`${styles.configSetting} ${settings.requireEmailConfirmation ? on : off}`}>
|
||||
<div className={styles.action}>
|
||||
<Checkbox
|
||||
onChange={updateEmailConfirmation(updateSettings, settings.requireEmailConfirmation)}
|
||||
checked={settings.requireEmailConfirmation} />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.require-email-verification')}</div>
|
||||
<p className={settings.requireEmailConfirmation ? '' : styles.disabledSettingText}>
|
||||
{lang.t('configure.require-email-verification-text')}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className={`${styles.configSetting} ${settings.moderation === 'PRE' ? on : off}`}>
|
||||
<div className={styles.action}>
|
||||
<Checkbox
|
||||
onChange={updateModeration(updateSettings, settings.moderation)}
|
||||
checked={settings.moderation === 'PRE'} />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.enable-pre-moderation')}</div>
|
||||
<p className={settings.moderation === 'PRE' ? '' : styles.disabledSettingText}>
|
||||
{lang.t('configure.enable-pre-moderation-text')}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
<Wordlist
|
||||
bannedWords={settings.wordlist.banned}
|
||||
suspectWords={settings.wordlist.suspect}
|
||||
onChangeWordlist={onChangeWordlist} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ModerationSettings.propTypes = {
|
||||
onChangeWordlist: PropTypes.func.isRequired,
|
||||
settings: PropTypes.shape({
|
||||
moderation: PropTypes.string.isRequired,
|
||||
wordlist: PropTypes.shape({
|
||||
banned: PropTypes.array.isRequired,
|
||||
suspect: PropTypes.array.isRequired
|
||||
})
|
||||
}).isRequired,
|
||||
updateSettings: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ModerationSettings;
|
||||
+4
-60
@@ -4,7 +4,7 @@ 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, Spinner} from 'coral-ui';
|
||||
import {Card, Icon} from 'coral-ui';
|
||||
|
||||
const TIMESTAMPS = {
|
||||
weeks: 60 * 60 * 24 * 7,
|
||||
@@ -27,15 +27,6 @@ const updateCharCount = (updateSettings, settingsError) => (event) => {
|
||||
updateSettings({charCount: charCount});
|
||||
};
|
||||
|
||||
const updateModeration = (updateSettings, mod) => () => {
|
||||
const moderation = mod === 'PRE' ? 'POST' : 'PRE';
|
||||
updateSettings({moderation});
|
||||
};
|
||||
|
||||
const updateEmailConfirmation = (updateSettings, verify) => () => {
|
||||
updateSettings({requireEmailConfirmation: !verify});
|
||||
};
|
||||
|
||||
const updateInfoBoxEnable = (updateSettings, infoBox) => () => {
|
||||
const infoBoxEnable = !infoBox;
|
||||
updateSettings({infoBoxEnable});
|
||||
@@ -51,11 +42,6 @@ const updateClosedMessage = (updateSettings) => (event) => {
|
||||
updateSettings({closedMessage});
|
||||
};
|
||||
|
||||
const updateCustomCssUrl = (updateSettings) => (event) => {
|
||||
const customCssUrl = event.target.value;
|
||||
updateSettings({customCssUrl});
|
||||
};
|
||||
|
||||
// If we are changing the measure we need to recalculate using the old amount
|
||||
// Same thing if we are just changing the amount
|
||||
const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => {
|
||||
@@ -71,44 +57,14 @@ const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => {
|
||||
}
|
||||
};
|
||||
|
||||
const CommentSettings = ({fetchingSettings, title, updateSettings, settingsError, settings, errors}) => {
|
||||
if (fetchingSettings) {
|
||||
return <Card shadow="4"><Spinner/>Loading settings...</Card>;
|
||||
}
|
||||
const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
|
||||
|
||||
// just putting this here for shorthand below
|
||||
const on = styles.enabledSetting;
|
||||
const off = styles.disabledSetting;
|
||||
|
||||
return (
|
||||
<div className={styles.commentSettingsSection}>
|
||||
<h3>{title}</h3>
|
||||
<Card className={`${styles.configSetting} ${settings.moderation === 'PRE' ? on : off}`}>
|
||||
<div className={styles.action}>
|
||||
<Checkbox
|
||||
onChange={updateModeration(updateSettings, settings.moderation)}
|
||||
checked={settings.moderation === 'PRE'} />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.enable-pre-moderation')}</div>
|
||||
<p className={settings.moderation === 'PRE' ? '' : styles.disabledSettingText}>
|
||||
{lang.t('configure.enable-pre-moderation-text')}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className={`${styles.configSetting} ${settings.requireEmailConfirmation ? on : off}`}>
|
||||
<div className={styles.action}>
|
||||
<Checkbox
|
||||
onChange={updateEmailConfirmation(updateSettings, settings.requireEmailConfirmation)}
|
||||
checked={settings.requireEmailConfirmation} />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.require-email-verification')}</div>
|
||||
<p className={settings.requireEmailConfirmation ? '' : styles.disabledSettingText}>
|
||||
{lang.t('configure.require-email-verification-text')}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Card className={`${styles.configSetting} ${settings.charCountEnable ? on : off}`}>
|
||||
<div className={styles.action}>
|
||||
<Checkbox
|
||||
@@ -193,23 +149,11 @@ const CommentSettings = ({fetchingSettings, title, updateSettings, settingsError
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className={styles.configSettingInfoBox}>
|
||||
<div className={styles.content}>
|
||||
{lang.t('configure.custom-css-url')}
|
||||
<p>{lang.t('configure.custom-css-url-desc')}</p>
|
||||
<br />
|
||||
<Textfield
|
||||
style={{width: '100%'}}
|
||||
label={lang.t('configure.custom-css-url')}
|
||||
value={settings.customCssUrl}
|
||||
onChange={updateCustomCssUrl(updateSettings)} />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommentSettings;
|
||||
export default StreamSettings;
|
||||
|
||||
// To see if we are talking about weeks, days or hours
|
||||
// We talk the remainder of the division and see if it's 0
|
||||
@@ -0,0 +1,44 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import {Card} from 'coral-ui';
|
||||
import Domainlist from './Domainlist';
|
||||
import EmbedLink from './EmbedLink';
|
||||
import styles from './Configure.css';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../../translations.json';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const updateCustomCssUrl = (updateSettings) => (event) => {
|
||||
const customCssUrl = event.target.value;
|
||||
updateSettings({customCssUrl});
|
||||
};
|
||||
|
||||
const TechSettings = ({settings, onChangeDomainlist, updateSettings}) => {
|
||||
return (
|
||||
<div>
|
||||
<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)} />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
TechSettings.propTypes = {
|
||||
settings: PropTypes.shape({
|
||||
domains: PropTypes.shape({
|
||||
whitelist: PropTypes.array.isRequired
|
||||
})
|
||||
}).isRequired,
|
||||
updateSettings: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default TechSettings;
|
||||
@@ -7,9 +7,8 @@ import {Card} from 'coral-ui';
|
||||
|
||||
const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
|
||||
<div>
|
||||
<h3>{lang.t('configure.banned-words-title')}</h3>
|
||||
<Card id={styles.bannedWordlist}>
|
||||
<p className={styles.wordlistHeader}>{lang.t('configure.banned-word-header')}</p>
|
||||
<Card id={styles.bannedWordlist} className={styles.configSetting}>
|
||||
<h3>{lang.t('configure.banned-words-title')}</h3>
|
||||
<p className={styles.wordlistDesc}>{lang.t('configure.banned-word-text')}</p>
|
||||
<TagsInput
|
||||
value={bannedWords}
|
||||
@@ -19,9 +18,8 @@ const Wordlist = ({suspectWords, bannedWords, onChangeWordlist}) => (
|
||||
onChange={tags => onChangeWordlist('banned', tags)}
|
||||
/>
|
||||
</Card>
|
||||
<h3>{lang.t('configure.suspect-words-title')}</h3>
|
||||
<Card id={styles.suspectWordlist}>
|
||||
<p className={styles.wordlistHeader}>{lang.t('configure.suspect-word-header')}</p>
|
||||
<Card id={styles.suspectWordlist} className={styles.configSetting}>
|
||||
<h3>{lang.t('configure.suspect-words-title')}</h3>
|
||||
<p className={styles.wordlistDesc}>{lang.t('configure.suspect-word-text')}</p>
|
||||
<TagsInput
|
||||
value={suspectWords}
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function settings (state = initialState, action) {
|
||||
.set('fetchSettingsError', null);
|
||||
case actions.SETTINGS_RECEIVED:
|
||||
return state.merge({
|
||||
fetchingSettings: null,
|
||||
fetchingSettings: false,
|
||||
fetchSettingsError: null,
|
||||
...action.settings
|
||||
});
|
||||
@@ -43,7 +43,7 @@ export default function settings (state = initialState, action) {
|
||||
.set('fetchSettingsError', action.error);
|
||||
case actions.SETTINGS_UPDATED:
|
||||
return state.merge({
|
||||
fetchingSettings: null,
|
||||
fetchingSettings: false,
|
||||
fetchSettingsError: null,
|
||||
...action.settings
|
||||
});
|
||||
|
||||
@@ -65,8 +65,6 @@
|
||||
"include-text": "Include your text here.",
|
||||
"comment-settings": "Settings",
|
||||
"embed-comment-stream": "Embed Stream",
|
||||
"banned-word-header": "Write the banned words list",
|
||||
"suspect-word-header": "Write the suspect words list",
|
||||
"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.",
|
||||
"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.",
|
||||
"wordlist": "Banned Words",
|
||||
@@ -88,7 +86,7 @@
|
||||
"comment-count-text-pre": "Comments will be limited to ",
|
||||
"comment-count-text-post": " characters.",
|
||||
"comment-count-error": "Please enter a valid number.",
|
||||
"domain-list-title": "Domain Whitelist",
|
||||
"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)."
|
||||
},
|
||||
"bandialog": {
|
||||
@@ -190,8 +188,6 @@
|
||||
"comment-settings": "Configuración de Comentarios",
|
||||
"embed-comment-stream": "Colocar Hilo de Comentarios",
|
||||
"wordlist": "Palabras Suspendidas y Suspechosas",
|
||||
"banned-word-header": "Escribir las palabras no permitidas",
|
||||
"suspect-word-header": "Write the suspect words list",
|
||||
"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",
|
||||
|
||||
Reference in New Issue
Block a user