mirror of
https://github.com/wassname/talk.git
synced 2026-08-12 12:30:39 +08:00
Merge branch 'master' into suspend-user
This commit is contained in:
@@ -43,8 +43,15 @@ if (process.env.NODE_ENV === 'production') {
|
||||
app.get('*.js', (req, res, next) => {
|
||||
const accept = accepts(req);
|
||||
if (accept.encoding(['gzip']) === 'gzip') {
|
||||
req.url = `${req.url}.gz`;
|
||||
|
||||
// Adjsut the headers on the request by adding a content type header
|
||||
// because express won't be able to detect the mime-type with the .gz
|
||||
// extension and we need to decalre support for the gzip encoding.
|
||||
res.set('Content-Type', 'application/javascript');
|
||||
res.set('Content-Encoding', 'gzip');
|
||||
|
||||
// Rewrite the url so that the gzip version will be served instead.
|
||||
req.url = `${req.url}.gz`;
|
||||
}
|
||||
|
||||
next();
|
||||
|
||||
@@ -96,24 +96,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
.inlineTextfield {
|
||||
border-color: #ccc;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 1px 0px;
|
||||
text-align: center;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.inlineTextfield:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.charCountTexfield {
|
||||
width: 4em;
|
||||
padding: 0px;
|
||||
border-color: #ccc;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 1px 0px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.charCountTexfieldEnabled {
|
||||
border-color: #00796b;
|
||||
}
|
||||
|
||||
.charCountTexfield:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.changedSave {
|
||||
background-color: #00796B;
|
||||
color: white;
|
||||
|
||||
@@ -27,6 +27,12 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
|
||||
const on = styles.enabledSetting;
|
||||
const off = styles.disabledSetting;
|
||||
|
||||
const onChangeEditCommentWindowLength = (e) => {
|
||||
const value = e.target.value;
|
||||
const valueAsNumber = parseFloat(value);
|
||||
const milliseconds = (!isNaN(valueAsNumber)) && (valueAsNumber * 1000);
|
||||
updateSettings({editCommentWindowLength: milliseconds || value});
|
||||
};
|
||||
return (
|
||||
<div className={styles.Configure}>
|
||||
<Card className={`${styles.configSetting} ${settings.requireEmailConfirmation ? on : off}`}>
|
||||
@@ -72,6 +78,27 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {
|
||||
bannedWords={settings.wordlist.banned}
|
||||
suspectWords={settings.wordlist.suspect}
|
||||
onChangeWordlist={onChangeWordlist} />
|
||||
|
||||
{/* Edit Comment Timeframe */}
|
||||
<Card className={styles.configSetting}>
|
||||
<div className={styles.settingsHeader}>{lang.t('configure.edit-comment-timeframe-heading')}</div>
|
||||
<p>
|
||||
{lang.t('configure.edit-comment-timeframe-text-pre')}
|
||||
|
||||
<input
|
||||
style={{width: '3em'}}
|
||||
className={styles.inlineTextfield}
|
||||
type="number"
|
||||
min="0"
|
||||
onChange={onChangeEditCommentWindowLength}
|
||||
placeholder="30"
|
||||
defaultValue={(settings.editCommentWindowLength / 1000) /* saved as ms, rendered as seconds */}
|
||||
pattern='[0-9]+([\.][0-9]*)?'
|
||||
/>
|
||||
|
||||
{lang.t('configure.edit-comment-timeframe-text-post')}
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -81,7 +81,7 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
|
||||
<p className={settings.charCountEnable ? '' : styles.disabledSettingText}>
|
||||
<span>{lang.t('configure.comment-count-text-pre')}</span>
|
||||
<input type='text'
|
||||
className={`${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
|
||||
className={`${styles.inlineTextfield} ${styles.charCountTexfield} ${settings.charCountEnable && styles.charCountTexfieldEnabled}`}
|
||||
htmlFor='charCount'
|
||||
onChange={updateCharCount(updateSettings, settingsError)}
|
||||
value={settings.charCount}
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
.profileEmail {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
pointer-events: none;
|
||||
font-size: 16px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
@@ -46,7 +46,7 @@ class UserDetail extends React.Component {
|
||||
<Drawer handleClickOutside={hideUserDetail}>
|
||||
<h3>{user.username}</h3>
|
||||
<Button className={styles.copyButton} onClick={this.copyPermalink}>Copy</Button>
|
||||
{profile && <input className={styles.profileEmail} type="text" ref={(ref) => this.profile = ref} value={profile} />}
|
||||
{profile && <input className={styles.profileEmail} readOnly type="text" ref={(ref) => this.profile = ref} value={profile} />}
|
||||
<Slot fill="userProfile" user={user} />
|
||||
<p className={styles.memberSince}><strong>Member since</strong> {new Date(user.created_at).toLocaleString()}</p>
|
||||
<hr/>
|
||||
|
||||
@@ -106,6 +106,9 @@
|
||||
"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.",
|
||||
"edit-comment-timeframe-heading": "Edit Comment Timeframe",
|
||||
"edit-comment-timeframe-text-pre": "Commenters will have",
|
||||
"edit-comment-timeframe-text-post": "seconds to edit their comments.",
|
||||
"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.",
|
||||
@@ -310,6 +313,9 @@
|
||||
"embed-comment-stream": "Colocar Hilo de Comentarios",
|
||||
"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.",
|
||||
"edit-comment-timeframe-heading": "Editar Tiempo de Comentario",
|
||||
"edit-comment-timeframe-text-pre": "Los comentaristas tendrán",
|
||||
"edit-comment-timeframe-text-post": "segundos para editar sus comentarios.",
|
||||
"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.",
|
||||
|
||||
@@ -30,7 +30,7 @@ class Stream extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
root: {asset, asset: {comments}, comment, myIgnoredUsers},
|
||||
root: {asset, asset: {comments}, comment, me},
|
||||
postComment,
|
||||
addNotification,
|
||||
postFlag,
|
||||
@@ -64,8 +64,9 @@ class Stream extends React.Component {
|
||||
const firstCommentDate = asset.comments[0]
|
||||
? asset.comments[0].created_at
|
||||
: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString();
|
||||
const commentIsIgnored = (comment) =>
|
||||
myIgnoredUsers && myIgnoredUsers.includes(comment.user.id);
|
||||
const commentIsIgnored = (comment) => {
|
||||
return me && me.ignoredUsers && me.ignoredUsers.find((u) => u.id === comment.user.id);
|
||||
};
|
||||
return (
|
||||
<div id="stream">
|
||||
{open
|
||||
@@ -162,8 +163,8 @@ class Stream extends React.Component {
|
||||
/>
|
||||
<div className="embed__stream">
|
||||
{comments.map(
|
||||
(comment) =>
|
||||
(commentIsIgnored(comment)
|
||||
(comment) => {
|
||||
return (commentIsIgnored(comment)
|
||||
? <IgnoredCommentTombstone key={comment.id} />
|
||||
: <Comment
|
||||
data={this.props.data}
|
||||
@@ -192,7 +193,9 @@ class Stream extends React.Component {
|
||||
charCountEnable={asset.settings.charCountEnable}
|
||||
maxCharCount={asset.settings.charCount}
|
||||
editComment={this.props.editComment}
|
||||
/>)
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<LoadMore
|
||||
|
||||
@@ -112,7 +112,10 @@ class StreamContainer extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.previousTab) {
|
||||
this.props.data.refetch();
|
||||
this.props.data.refetch()
|
||||
.then(({data: {asset: {commentCount}}}) => {
|
||||
return this.props.setCommentCountCache(commentCount);
|
||||
});
|
||||
}
|
||||
this.countPoll = setInterval(() => {
|
||||
this.getCounts(this.props.data.variables);
|
||||
@@ -205,6 +208,9 @@ const fragments = {
|
||||
}
|
||||
me {
|
||||
status
|
||||
ignoredUsers {
|
||||
id
|
||||
}
|
||||
}
|
||||
settings {
|
||||
organizationName
|
||||
|
||||
@@ -122,19 +122,40 @@ const extension = {
|
||||
`,
|
||||
},
|
||||
mutations: {
|
||||
IgnoreUser: () => ({
|
||||
|
||||
// TODO: don't rely on refetching.
|
||||
refetchQueries: [
|
||||
'EmbedQuery', 'EmbedStreamProfileQuery',
|
||||
],
|
||||
IgnoreUser: ({variables}) => ({
|
||||
updateQueries: {
|
||||
EmbedQuery: (previousData, {mutationResult}) => {
|
||||
const ignoredUserId = variables.id;
|
||||
const response = mutationResult.data.ignoreUser;
|
||||
if (ignoredUserId && !response.errors) {
|
||||
const updated = update(previousData, {me: {ignoredUsers: {$push: [{
|
||||
id: ignoredUserId,
|
||||
__typename: 'User',
|
||||
}]}}});
|
||||
return updated;
|
||||
}
|
||||
return previousData;
|
||||
}
|
||||
}
|
||||
}),
|
||||
StopIgnoringUser: () => ({
|
||||
StopIgnoringUser: ({variables}) => ({
|
||||
updateQueries: {
|
||||
EmbedStreamProfileQuery: (previousData, {mutationResult}) => {
|
||||
const noLongerIgnoredUserId = variables.id;
|
||||
const response = mutationResult.data.stopIgnoringUser;
|
||||
if (noLongerIgnoredUserId && !response.errors) {
|
||||
|
||||
// TODO: don't rely on refetching.
|
||||
refetchQueries: [
|
||||
'EmbedQuery', 'EmbedStreamProfileQuery',
|
||||
],
|
||||
// remove noLongerIgnoredUserId from ignoredUsers
|
||||
const updated = update(previousData, {me: {ignoredUsers: {
|
||||
$apply: (ignoredUsers) => {
|
||||
return ignoredUsers.filter((u) => u.id !== noLongerIgnoredUserId);
|
||||
}
|
||||
}}});
|
||||
return updated;
|
||||
}
|
||||
return previousData;
|
||||
}
|
||||
}
|
||||
}),
|
||||
PostComment: ({
|
||||
variables: {comment: {asset_id, body, parent_id, tags = []}},
|
||||
@@ -200,7 +221,13 @@ const extension = {
|
||||
variables: {id, edit},
|
||||
}) => ({
|
||||
updateQueries: {
|
||||
EmbedQuery: (previousData, {mutationResult: {data: {editComment: {comment: {status}}}}}) => {
|
||||
EmbedQuery: (previousData, {mutationResult: {data: {editComment: {comment, errors}}}}) => {
|
||||
|
||||
// @TODO (kiwi) revisit after streamlining error handling
|
||||
if (errors && errors.length) {
|
||||
return previousData;
|
||||
}
|
||||
const {status} = comment;
|
||||
const updateCommentWithEdit = (comment, edit) => {
|
||||
const {body} = edit;
|
||||
const editedComment = update(comment, {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Map, Set} from 'immutable';
|
||||
import {Map} from 'immutable';
|
||||
import * as authActions from '../constants/auth';
|
||||
import * as actions from '../constants/user';
|
||||
import * as assetActions from '../constants/assets';
|
||||
@@ -9,7 +9,6 @@ const initialState = Map({
|
||||
settings: {},
|
||||
myComments: [],
|
||||
myAssets: [], // the assets from which myComments (above) originated
|
||||
ignoredUsers: Set(),
|
||||
});
|
||||
|
||||
const purge = (user) => {
|
||||
@@ -39,14 +38,6 @@ export default function user (state = initialState, action) {
|
||||
return state.set('myAssets', action.assets);
|
||||
case actions.LOGOUT_SUCCESS:
|
||||
return initialState;
|
||||
case 'APOLLO_MUTATION_RESULT':
|
||||
switch (action.operationName) {
|
||||
case 'ignoreUser':
|
||||
return state.updateIn(['ignoredUsers'], (i) => i.add(action.variables.id));
|
||||
case 'stopIgnoringUser':
|
||||
return state.updateIn(['ignoredUsers'], (i) => i.delete(action.variables.id));
|
||||
}
|
||||
break;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -48,10 +48,12 @@ const Comment = {
|
||||
asset({asset_id}, _, {loaders: {Assets}}) {
|
||||
return Assets.getByID.load(asset_id);
|
||||
},
|
||||
editing(comment) {
|
||||
async editing(comment, _, {loaders: {Settings}}) {
|
||||
const settings = await Settings.load();
|
||||
const editableUntil = new Date(Number(comment.created_at) + settings.editCommentWindowLength);
|
||||
return {
|
||||
edited: comment.edited,
|
||||
editableUntil: comment.editableUntil
|
||||
editableUntil: editableUntil
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,8 +2,6 @@ const mongoose = require('../services/mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const uuid = require('uuid');
|
||||
|
||||
const EDIT_WINDOW_MS = 30 * 1000; // 30 seconds
|
||||
|
||||
const STATUSES = [
|
||||
'ACCEPTED',
|
||||
'REJECTED',
|
||||
@@ -113,12 +111,7 @@ CommentSchema.virtual('edited').get(function() {
|
||||
return this.body_history.length > 1;
|
||||
});
|
||||
|
||||
CommentSchema.virtual('editableUntil').get(function() {
|
||||
return new Date(Number(this.created_at) + EDIT_WINDOW_MS);
|
||||
});
|
||||
|
||||
// Comment model.
|
||||
const Comment = mongoose.model('Comment', CommentSchema);
|
||||
|
||||
module.exports = Comment;
|
||||
module.exports.EDIT_WINDOW_MS = EDIT_WINDOW_MS;
|
||||
|
||||
@@ -88,6 +88,13 @@ const SettingSchema = new Schema({
|
||||
type: Array,
|
||||
default: ['localhost']
|
||||
}
|
||||
},
|
||||
|
||||
// Length of time (in milliseconds) after a comment is posted that it can still be edited by the author
|
||||
editCommentWindowLength: {
|
||||
type: Number,
|
||||
min: [0, 'Edit Comment Window length must be greater than zero'],
|
||||
default: 30 * 1000,
|
||||
}
|
||||
}, {
|
||||
timestamps: {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const CommentModel = require('../models/comment');
|
||||
const EDIT_WINDOW_MS = CommentModel.EDIT_WINDOW_MS;
|
||||
|
||||
const ActionModel = require('../models/action');
|
||||
const ActionsService = require('./actions');
|
||||
const SettingsService = require('./settings');
|
||||
|
||||
const errors = require('../errors');
|
||||
|
||||
@@ -53,8 +53,10 @@ module.exports = class CommentsService {
|
||||
|
||||
// Establish the edit window (if it exists) and add the condition to the
|
||||
// original query.
|
||||
const lastEditableCommentCreatedAt = new Date((new Date()).getTime() - EDIT_WINDOW_MS);
|
||||
let lastEditableCommentCreatedAt;
|
||||
if (!ignoreEditWindow) {
|
||||
const {editCommentWindowLength: editWindowMs} = await SettingsService.retrieve();
|
||||
lastEditableCommentCreatedAt = new Date((new Date()).getTime() - editWindowMs);
|
||||
query.created_at = {
|
||||
$gt: lastEditableCommentCreatedAt,
|
||||
};
|
||||
|
||||
@@ -903,6 +903,5 @@ module.exports = class UsersService {
|
||||
ignoresUsers: usersToStopIgnoring
|
||||
}
|
||||
});
|
||||
console.log('Mongo wrote stopIgnoringUsers', usersToStopIgnoring);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user