mirror of
https://gitlab.com/SIGBUS/nyaa.git
synced 2024-12-22 08:49:59 +00:00
[Schema+config change] Comment editing (#396)
* Comment editing * Optional time limit for comment editing
This commit is contained in:
parent
b4c0ad9e84
commit
72c997173c
|
@ -127,3 +127,11 @@ ENABLE_ELASTIC_SEARCH_HIGHLIGHT = False
|
|||
ES_MAX_SEARCH_RESULT = 1000
|
||||
# ES index name generally (nyaa or sukebei)
|
||||
ES_INDEX_NAME = SITE_FLAVOR
|
||||
|
||||
################
|
||||
## Commenting ##
|
||||
################
|
||||
|
||||
# Time limit for editing a comment after it has been posted (seconds)
|
||||
# Set to 0 to disable
|
||||
EDITING_TIME_LIMIT = 0
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
"""Add edited_time to Comments
|
||||
|
||||
Revision ID: cf7bf6d0e6bd
|
||||
Revises: 500117641608
|
||||
Create Date: 2017-10-28 15:32:12.687378
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'cf7bf6d0e6bd'
|
||||
down_revision = '500117641608'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('nyaa_comments', sa.Column('edited_time', sa.DateTime(), nullable=True))
|
||||
op.add_column('sukebei_comments', sa.Column('edited_time', sa.DateTime(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('sukebei_comments', 'edited_time')
|
||||
op.drop_column('nyaa_comments', 'edited_time')
|
||||
# ### end Alembic commands ###
|
|
@ -184,7 +184,7 @@ class CommentForm(FlaskForm):
|
|||
comment = TextAreaField('Make a comment', [
|
||||
Length(min=3, max=1024, message='Comment must be at least %(min)d characters '
|
||||
'long and %(max)d at most.'),
|
||||
DataRequired()
|
||||
DataRequired(message='Comment must not be empty.')
|
||||
])
|
||||
|
||||
|
||||
|
|
|
@ -433,6 +433,7 @@ class CommentBase(DeclarativeHelperBase):
|
|||
return db.Column(db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'))
|
||||
|
||||
created_time = db.Column(db.DateTime(timezone=False), default=datetime.utcnow)
|
||||
edited_time = db.Column(db.DateTime(timezone=False), onupdate=datetime.utcnow)
|
||||
text = db.Column(TextType(collation=COL_UTF8MB4_BIN), nullable=False)
|
||||
|
||||
@declarative.declared_attr
|
||||
|
@ -448,6 +449,15 @@ class CommentBase(DeclarativeHelperBase):
|
|||
''' Returns a UTC POSIX timestamp, as seconds '''
|
||||
return (self.created_time - UTC_EPOCH).total_seconds()
|
||||
|
||||
@property
|
||||
def editable_until(self):
|
||||
return self.created_utc_timestamp + config['EDITING_TIME_LIMIT']
|
||||
|
||||
@property
|
||||
def editing_limit_exceeded(self):
|
||||
limit = config['EDITING_TIME_LIMIT']
|
||||
return bool(limit and (datetime.utcnow() - self.created_time).total_seconds() >= limit)
|
||||
|
||||
|
||||
class UserLevelType(IntEnum):
|
||||
REGULAR = 0
|
||||
|
|
|
@ -271,6 +271,10 @@ a.text-purple:hover, a.text-purple:active, a.text-purple:focus {
|
|||
color: #a760e0;
|
||||
}
|
||||
|
||||
.comment-details {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
@ -286,11 +290,39 @@ a.text-purple:hover, a.text-purple:active, a.text-purple:focus {
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.delete-comment-form {
|
||||
.comment-actions {
|
||||
position: relative;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.delete-comment-form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.edit-comment-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.is-editing .edit-comment-box {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.is-editing .comment-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.edit-waiting {
|
||||
float: right;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid;
|
||||
border-color: gray transparent;
|
||||
border-radius: 100%;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
animation: fa-spin 1s infinite linear;
|
||||
}
|
||||
|
||||
#comment {
|
||||
height: 8em;
|
||||
}
|
||||
|
|
|
@ -74,6 +74,52 @@ $(document).ready(function() {
|
|||
$(this).blur().children('i').toggleClass('fa-folder-open fa-folder');
|
||||
$(this).next().stop().slideToggle(250);
|
||||
});
|
||||
|
||||
$('.edit-comment').click(function(e) {
|
||||
e.preventDefault();
|
||||
$(this).closest('.comment').toggleClass('is-editing');
|
||||
});
|
||||
|
||||
$('[data-until]').each(function() {
|
||||
var $this = $(this),
|
||||
text = $(this).text(),
|
||||
until = $this.data('until');
|
||||
|
||||
var displayTimeRemaining = function() {
|
||||
var diff = Math.max(0, until - (Date.now() / 1000) | 0),
|
||||
min = Math.floor(diff / 60),
|
||||
sec = diff % 60;
|
||||
$this.text(text + ' (' + min + ':' + ('00' + sec).slice(-2) + ')');
|
||||
};
|
||||
|
||||
displayTimeRemaining();
|
||||
setInterval(displayTimeRemaining, 1000);
|
||||
});
|
||||
|
||||
$('.edit-comment-box').submit(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $this = $(this),
|
||||
$submitButton = $this.find('[type=submit]').attr('disabled', 'disabled'),
|
||||
$waitIndicator = $this.find('.edit-waiting').show()
|
||||
$errorStatus = $this.find('.edit-error').empty();
|
||||
|
||||
$.ajax({
|
||||
type: $this.attr('method'),
|
||||
url: $this.attr('action'),
|
||||
data: $this.serialize()
|
||||
}).done(function(data) {
|
||||
var $comment = $this.closest('.comment');
|
||||
$comment.find('.comment-content').html(markdown.render(data.comment));
|
||||
$comment.toggleClass('is-editing');
|
||||
}).fail(function(xhr) {
|
||||
var error = xhr.responseJSON && xhr.responseJSON.error || 'An unknown error occurred.';
|
||||
$errorStatus.text(error);
|
||||
}).always(function() {
|
||||
$submitButton.removeAttr('disabled');
|
||||
$waitIndicator.hide();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
function _format_time_difference(seconds) {
|
||||
|
|
|
@ -152,18 +152,38 @@
|
|||
</p>
|
||||
<img class="avatar" src="{{ comment.user.gravatar_url() }}" alt="{{ comment.user.userlevel_str }}">
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<div class="row">
|
||||
<div class="col-md-10 comment">
|
||||
<div class="row comment-details">
|
||||
<a href="#com-{{ loop.index }}"><small data-timestamp-swap data-timestamp="{{ comment.created_utc_timestamp|int }}">{{ comment.created_time.strftime('%Y-%m-%d %H:%M UTC') }}</small></a>
|
||||
{% if comment.edited_time %}
|
||||
<small title="{{ comment.edited_time }}">(edited)</small>
|
||||
{% endif %}
|
||||
<div class="comment-actions">
|
||||
{% if g.user.id == comment.user_id and not comment.editing_limit_exceeded %}
|
||||
<button class="btn btn-xs edit-comment" title="Edit"{% if config.EDITING_TIME_LIMIT %} data-until="{{ comment.editable_until|int }}"{% endif %}>Edit</button>
|
||||
{% endif %}
|
||||
{% if g.user.is_moderator or g.user.id == comment.user_id %}
|
||||
<form class="delete-comment-form" action="{{ url_for('torrents.delete_comment', torrent_id=torrent.id, comment_id=comment.id) }}" method="POST">
|
||||
<button name="submit" type="submit" class="btn btn-danger btn-xs" title="Delete">Delete</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{# Escape newlines into html entities because CF strips blank newlines #}
|
||||
<div markdown-text class="comment-content" id="torrent-comment{{ comment.id }}">{{ comment.text }}</div>
|
||||
{% if g.user.id == comment.user_id %}
|
||||
<form class="edit-comment-box" action="{{ url_for('torrents.edit_comment', torrent_id=torrent.id, comment_id=comment.id) }}" method="POST">
|
||||
{{ comment_form.csrf_token }}
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" name="comment" autofocus>{{ comment.text }}</textarea>
|
||||
</div>
|
||||
<input type="submit" value="Submit" class="btn btn-success btn-sm">
|
||||
<button class="btn btn-sm edit-comment" title="Cancel">Cancel</button>
|
||||
<span class="edit-error text-danger"></span>
|
||||
<div class="edit-waiting" style="display:none"></div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -330,6 +330,37 @@ def download_torrent(torrent_id):
|
|||
return resp
|
||||
|
||||
|
||||
@bp.route('/view/<int:torrent_id>/comment/<int:comment_id>/edit', methods=['POST'])
|
||||
def edit_comment(torrent_id, comment_id):
|
||||
if not flask.g.user:
|
||||
flask.abort(403)
|
||||
torrent = models.Torrent.by_id(torrent_id)
|
||||
if not torrent:
|
||||
flask.abort(404)
|
||||
|
||||
comment = models.Comment.query.get(comment_id)
|
||||
if not comment:
|
||||
flask.abort(404)
|
||||
|
||||
if not comment.user.id == flask.g.user.id:
|
||||
flask.abort(403)
|
||||
|
||||
if comment.editing_limit_exceeded:
|
||||
flask.abort(flask.make_response(flask.jsonify(
|
||||
{'error': 'Editing time limit exceeded.'}), 400))
|
||||
|
||||
form = forms.CommentForm(flask.request.form)
|
||||
|
||||
if not form.validate():
|
||||
error_str = ' '.join(form.errors['comment'])
|
||||
flask.abort(flask.make_response(flask.jsonify({'error': error_str}), 400))
|
||||
|
||||
comment.text = form.comment.data
|
||||
db.session.commit()
|
||||
|
||||
return flask.jsonify({'comment': comment.text})
|
||||
|
||||
|
||||
@bp.route('/view/<int:torrent_id>/comment/<int:comment_id>/delete', methods=['POST'])
|
||||
def delete_comment(torrent_id, comment_id):
|
||||
if not flask.g.user:
|
||||
|
|
Loading…
Reference in a new issue