Vue Comments: Comments ported to Vue + DnD fileupload
* Drag and drop files to comment editor to add a file attachment * Using Vue to render comments Since comments now has attachments we need to update the schemas ./manage.py maintenance replace_pillar_node_type_schemas
This commit is contained in:
@@ -30,6 +30,8 @@ html(lang="en")
|
||||
| {% endblock %}
|
||||
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js') }}")
|
||||
script.
|
||||
pillar.utils.initCurrentUser({{ current_user | json | safe }});
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/js.cookie-2.0.3.min.js')}}")
|
||||
| {% if current_user.is_authenticated %}
|
||||
|
@@ -56,8 +56,7 @@ script(type="text/javascript").
|
||||
}
|
||||
|
||||
if (ProjectUtils.nodeType() == 'asset' || ProjectUtils.nodeType() == 'post') {
|
||||
var commentsUrl = "{{ url_for('nodes.comments_for_node', node_id=node._id) }}";
|
||||
loadComments(commentsUrl);
|
||||
new Vue({el:'#comments-embed'});
|
||||
}
|
||||
|
||||
{% if node.has_method('PUT') %}
|
||||
|
@@ -42,7 +42,9 @@
|
||||
a(href="{{ node.short_link }}") {{ node.short_link }}
|
||||
| {% endif %}
|
||||
|
||||
#comments-embed
|
||||
comments-tree#comments-embed(
|
||||
parent-id="{{ node._id }}"
|
||||
)
|
||||
|
||||
include ../_scripts
|
||||
|
||||
|
@@ -1,51 +0,0 @@
|
||||
| {%- macro render_comment(comment, is_reply) -%}
|
||||
.comment-container(
|
||||
id="{{ comment._id }}",
|
||||
data-node-id="{{ comment._id }}",
|
||||
class="{% if is_reply %}is-reply{% else %}is-first{% endif %}")
|
||||
|
||||
.comment-avatar
|
||||
img(src="{{ comment._user.email | gravatar }}", alt="{{ comment._user.full_name }}")
|
||||
.comment-badges
|
||||
| {{ comment._user.badges_html|safe }}
|
||||
.comment-content
|
||||
.comment-body
|
||||
p.comment-author {{ comment._user.full_name }}
|
||||
span {{comment.properties | markdowned('content') }}
|
||||
|
||||
| {# TODO(Pablo): Markdown preview when editing #}
|
||||
|
||||
.comment-meta
|
||||
.comment-rating(
|
||||
class="{% if comment._current_user_rating is not none %}rated{% if comment._current_user_rating %} positive{% endif %}{% endif %}")
|
||||
.comment-rating-value(title="{{ _('Number of likes') }}") {{ comment._rating }}
|
||||
|
||||
| {% if not comment._is_own %}
|
||||
.comment-action-rating.up(title="{{ _('Like comment') }}")
|
||||
| {% endif %}
|
||||
|
||||
.comment-action-reply(title="{{ _('Reply to this comment') }}")
|
||||
span {{ _("Reply") }}
|
||||
|
||||
| {% if comment._is_own %}
|
||||
.comment-action-edit
|
||||
span.edit_mode(title="{{ _('Edit comment') }}")
|
||||
| {{ _("Edit") }}
|
||||
span.edit_save(title="{{ _('Save comment') }}")
|
||||
i.pi-check
|
||||
| {{ _("Save Changes") }}
|
||||
span.edit_cancel(title="{{ _('Cancel changes') }}")
|
||||
i.pi-cancel
|
||||
| {{ _("Cancel") }}
|
||||
| {% endif %}
|
||||
|
||||
.comment-time(title='{{ comment._created }}')
|
||||
| {{ comment._created | pretty_date_time }}
|
||||
| {% if comment._created != comment._updated %}
|
||||
span(title="{{ _('edited') }} {{ comment._updated | pretty_date_time }}") *
|
||||
| {% endif %}
|
||||
|
||||
| {% for reply in comment['_replies']['_items'] %}
|
||||
| {{ render_comment(reply, True) }}
|
||||
| {% endfor %}
|
||||
| {%- endmacro -%}
|
@@ -1 +0,0 @@
|
||||
| {% extends "nodes/custom/comment/list_embed_base.html" %}
|
@@ -1,258 +0,0 @@
|
||||
| {% import 'nodes/custom/comment/_macros.html' as macros %}
|
||||
#comments.comments-container
|
||||
section#comments-list.comments-list
|
||||
.comment-reply-container
|
||||
| {% if can_post_comments %}
|
||||
| {% block can_post_comment %}
|
||||
.comment-reply-avatar
|
||||
img(
|
||||
title="{{ _('Commenting as') }} {{ current_user.full_name }}",
|
||||
src="{{ current_user.gravatar }}")
|
||||
|
||||
.comment-reply-form
|
||||
|
||||
.comment-reply-field
|
||||
textarea(
|
||||
id="comment_field",
|
||||
data-parent-id="{{ node_id }}",
|
||||
placeholder="{{ _('Join the conversation...') }}")
|
||||
|
||||
.comment-reply-meta
|
||||
|
||||
button.comment-action-submit(
|
||||
id="comment_submit",
|
||||
type="button",
|
||||
title="Post Comment (Ctrl+Enter)")
|
||||
span
|
||||
i.pi-paper-plane
|
||||
| {{ _('Send') }}
|
||||
span.hotkey Ctrl + Enter
|
||||
|
||||
.comment-reply-preview
|
||||
.comment-reply-preview-md
|
||||
.comment-reply-info
|
||||
.comment-action-cancel(
|
||||
title="{{ _('cancel') }}")
|
||||
span {{ _('cancel') }}
|
||||
|
||||
a(
|
||||
title="{{ _('Handy guide of Markdown syntax') }}",
|
||||
target="_blank",
|
||||
href="http://commonmark.org/help/")
|
||||
span {{ _('markdown cheatsheet') }}
|
||||
| {% endblock can_post_comment %}
|
||||
|
||||
| {% else %}
|
||||
| {% block can_not_post_comment %}
|
||||
| {% if current_user.is_authenticated %}
|
||||
|
||||
| {# * User is authenticated, but has no subscription or 'POST' permission #}
|
||||
.comment-reply-form
|
||||
.comment-reply-field.sign-in
|
||||
| {% if current_user.has_cap('subscriber') %}
|
||||
i.pi-lock
|
||||
| Only project members can comment.
|
||||
| {% elif current_user.has_cap('can-renew-subscription') %}
|
||||
i.pi-heart
|
||||
| Join the conversation! #[a(href='/renew', target='_blank') Renew your subscription] to comment.
|
||||
| {% else %}
|
||||
| Join the conversation! #[a(href="https://store.blender.org/product/membership/") Subscribe to Blender Cloud] to comment.
|
||||
| {% endif %}
|
||||
|
||||
| {% else %}
|
||||
| {# * User is not autenticated #}
|
||||
.comment-reply-form
|
||||
.comment-reply-field.sign-in
|
||||
a(href="{{ url_for('users.login') }}") {{ _('Log in to comment') }}
|
||||
| {% endif %}
|
||||
| {% endblock can_not_post_comment %}
|
||||
| {% endif %}
|
||||
|
||||
| {% if show_comments and (nr_of_comments > 0) %}
|
||||
section.comments-list-header
|
||||
.comments-list-title
|
||||
| {{ nr_of_comments }} {{ _('comment') }}{{ nr_of_comments|pluralize }}
|
||||
.comments-list-items
|
||||
| {% for comment in comments['_items'] %}
|
||||
| {{ macros.render_comment(comment, False) }}
|
||||
| {% endfor %}
|
||||
| {% endif %}
|
||||
|
||||
| {% block comment_scripts %}
|
||||
script.
|
||||
|
||||
{% if show_comments %}
|
||||
$('body')
|
||||
.off('pillar:comment-posted')
|
||||
.on('pillar:comment-posted', function(e, comment_node_id) {
|
||||
|
||||
var commentsUrl = "{{ url_for('nodes.comments_for_node', node_id=node_id) }}";
|
||||
|
||||
loadComments(commentsUrl)
|
||||
.done(function() {
|
||||
$('#' + comment_node_id).scrollHere();
|
||||
});
|
||||
});
|
||||
|
||||
// If there's a comment link in the URL, scroll there
|
||||
function scrollToLinkedComment() {
|
||||
var scrollToId = location.hash;
|
||||
|
||||
// Check that it's a valid ObjectID before passing it to jQuery.
|
||||
if (!/^[a-fA-F0-9]{24}$/.test(scrollToId.replace('#',''))) return;
|
||||
|
||||
$(scrollToId)
|
||||
.addClass('comment-linked')
|
||||
.scrollHere();
|
||||
}
|
||||
|
||||
$(scrollToLinkedComment);
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if can_post_comments %}
|
||||
|
||||
// If we can actually comment, load the tools to do it
|
||||
|
||||
// Submit new comment
|
||||
$(document)
|
||||
.off('click','body .comment-action-submit')
|
||||
.on( 'click','body .comment-action-submit', function(e){
|
||||
post_comment($(this));
|
||||
});
|
||||
|
||||
// Writing comment
|
||||
var $commentField = $("#comment_field");
|
||||
var $commentContainer = $commentField.parent();
|
||||
var $commentPreview = $commentField.parent().parent().find('.comment-reply-preview-md');
|
||||
|
||||
function parseCommentContent(content) {
|
||||
|
||||
$.ajax({
|
||||
url: "{{ url_for('nodes.preview_markdown')}}",
|
||||
type: 'post',
|
||||
data: {content: content},
|
||||
headers: {"X-CSRFToken": csrf_token},
|
||||
headers: {},
|
||||
dataType: 'json'
|
||||
})
|
||||
.done(function (data) {
|
||||
$commentPreview.html(data.content);
|
||||
})
|
||||
.fail(function (err) {
|
||||
toastr.error(xhrErrorResponseMessage(err), 'Parsing failed');
|
||||
});
|
||||
}
|
||||
|
||||
var options = {
|
||||
callback: parseCommentContent,
|
||||
wait: 750,
|
||||
highlight: false,
|
||||
allowSubmit: false,
|
||||
captureLength: 2
|
||||
}
|
||||
|
||||
$commentField.typeWatch(options);
|
||||
|
||||
$(document)
|
||||
.off('keyup','body .comment-reply-field textarea')
|
||||
.on( 'keyup','body .comment-reply-field textarea',function(e){
|
||||
|
||||
// While we are at it, style if empty
|
||||
if ($commentField.val()) {
|
||||
$commentContainer.addClass('filled');
|
||||
} else {
|
||||
$commentContainer.removeClass('filled');
|
||||
}
|
||||
|
||||
// Send on ctrl+enter
|
||||
if ($commentField.is(":focus")) {
|
||||
if ((e.keyCode == 10 || e.keyCode == 13) && e.ctrlKey){
|
||||
post_comment($commentContainer.find('.comment-action-submit'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Autoresize the textarea as we type
|
||||
$('#comment_field').autoResize();
|
||||
|
||||
|
||||
// Edit comment
|
||||
// Enter edit mode
|
||||
$(document)
|
||||
.off('click','body .comment-action-edit span.edit_mode')
|
||||
.on( 'click','body .comment-action-edit span.edit_mode',function(){
|
||||
|
||||
comment_mode(this, 'edit');
|
||||
|
||||
var $parent_div = $(this).closest('.comment-container');
|
||||
var comment_id = $parent_div.data('node-id');
|
||||
|
||||
var comment_content = $parent_div.find('.comment-body span');
|
||||
var height = comment_content.height();
|
||||
|
||||
loadComment(comment_id, {'properties.content': 1})
|
||||
|
||||
.done(function(data) {
|
||||
var comment_raw = data['properties']['content'];
|
||||
comment_content.html($('<textarea>').text(comment_raw));
|
||||
comment_content
|
||||
.addClass('editing')
|
||||
.find('textarea')
|
||||
.autoResize()
|
||||
.focus()
|
||||
.trigger('keyup');
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
if (console) console.log('Error fetching comment: ', xhr);
|
||||
toastr.error('Error ' + xhr.status + ' entering edit mode.');
|
||||
});
|
||||
});
|
||||
|
||||
// Abort, abort
|
||||
$(document)
|
||||
.off('click','body .comment-action-edit span.edit_cancel')
|
||||
.on( 'click','body .comment-action-edit span.edit_cancel',function(e){
|
||||
commentEditCancel(this, true);
|
||||
});
|
||||
|
||||
// Save edited comment
|
||||
$(document)
|
||||
.off('click','body .comment-action-edit span.edit_save')
|
||||
.on( 'click','body .comment-action-edit span.edit_save',function(e){
|
||||
var $button = $(this);
|
||||
var $container = $button.closest('.comment-container');
|
||||
|
||||
save_comment(false, $container)
|
||||
.progress(function() {
|
||||
$button
|
||||
.addClass('submitting')
|
||||
.html('<i class="pi-spin spin"></i> Posting...');
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
if (typeof xhr === 'string') {
|
||||
show_comment_edit_button_error($button, xhr);
|
||||
} else {
|
||||
// If it's not a string, we assume it's a jQuery XHR object.
|
||||
if (console) console.log('Error saving comment:', xhr.responseText);
|
||||
show_comment_edit_button_error($button, "Houston! Try again?");
|
||||
}
|
||||
})
|
||||
.done(function(comment_id, comment_html) {
|
||||
commentEditCancel($button, false)
|
||||
|
||||
$container.find('.comment-body span')
|
||||
.html(comment_html)
|
||||
.removeClass('editing')
|
||||
.flashOnce();
|
||||
|
||||
$button
|
||||
.html('<i class="pi-check"></i> save changes')
|
||||
.removeClass('saving');
|
||||
});
|
||||
});
|
||||
|
||||
{% endif %}
|
||||
|
||||
| {% endblock %}
|
@@ -122,9 +122,9 @@ section.node-details-meta.pl-4.pr-2.py-2.border-bottom
|
||||
.row
|
||||
| {% block node_comments %}
|
||||
.col-md-8.col-sm-12
|
||||
#comments-embed.p-2
|
||||
.comments-list-loading
|
||||
i.pi-spin
|
||||
comments-tree#comments-embed(
|
||||
parent-id="{{ node._id }}"
|
||||
)
|
||||
| {% endblock node_comments %}
|
||||
|
||||
| {# Check if tags is defined and there is _actually_ a tag at least #}
|
||||
|
Reference in New Issue
Block a user