mirror of https://gitlab.com/tildes/tildes.git
Browse Source
Use intercooler for comment reply form
Use intercooler for comment reply form
Previously, the comment reply form was being created entirely client-side by cloning and modifying a <template>. This was nice because it meant that a network request wasn't necessary to display the form, but it also had downsides. For example, if a topic was locked after a user had already loaded the page (or their notifications page with a comment from that topic), they would still be able to click Reply and type in a comment, and wouldn't know that replying wasn't possible until they actually tried to submit the comment. By switching to using intercooler for this form, we can do server-side validation to check permissions before showing the form, and it also simplifies some other aspects, such as the warning about replying to an old comment, which previously needed a data-js-old-warning-age attribute in the HTML, but is now just part of generating the reply form template server-side.merge-requests/106/head
Deimos
4 years ago
10 changed files with 113 additions and 139 deletions
-
6tildes/scss/modules/_comment.scss
-
102tildes/static/js/behaviors/comment-reply-button.js
-
46tildes/static/js/behaviors/comment-reply-form.js
-
1tildes/tildes/routes.py
-
35tildes/tildes/templates/intercooler/comment_reply.jinja2
-
42tildes/tildes/templates/macros/comments.jinja2
-
3tildes/tildes/templates/notifications_unread.jinja2
-
3tildes/tildes/templates/topic.jinja2
-
3tildes/tildes/templates/user.jinja2
-
11tildes/tildes/views/api/web/comment.py
@ -1,102 +0,0 @@ |
|||
// Copyright (c) 2018 Tildes contributors <code@tildes.net>
|
|||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
|
|||
$.onmount("[data-js-comment-reply-button]", function() { |
|||
$(this).click(function(event) { |
|||
event.preventDefault(); |
|||
|
|||
// disable click/hover events on the reply button
|
|||
$(this).css("pointer-events", "none"); |
|||
|
|||
var $comment = $(this) |
|||
.parents(".comment") |
|||
.first(); |
|||
|
|||
// get the replies list, or create one if it doesn't already exist
|
|||
var $replies = $comment.children(".comment-tree-replies"); |
|||
if (!$replies.length) { |
|||
var repliesList = document.createElement("ol"); |
|||
repliesList.setAttribute("class", "comment-tree comment-tree-replies"); |
|||
$comment.append(repliesList); |
|||
$replies = $(repliesList); |
|||
} |
|||
|
|||
var $parentComment = $(this).parents("article.comment:first"); |
|||
var parentCommentID = $parentComment.attr("data-comment-id36"); |
|||
var postURL = "/api/web/comments/" + parentCommentID + "/replies"; |
|||
var markdownID = "markdown-reply-" + parentCommentID; |
|||
var previewID = markdownID + "-preview"; |
|||
|
|||
if ($("#" + markdownID).length > 0) { |
|||
$("#" + markdownID).focus(); |
|||
return; |
|||
} |
|||
|
|||
// clone and populate the 'comment-reply' template
|
|||
var template = document.getElementById("comment-reply"); |
|||
var clone = document.importNode(template.content, true); |
|||
|
|||
clone.querySelector("form").setAttribute("data-ic-post-to", postURL); |
|||
var textarea = clone.querySelector("textarea"); |
|||
textarea.setAttribute("id", markdownID); |
|||
|
|||
var preview = clone.querySelector(".form-markdown-preview"); |
|||
preview.setAttribute("id", previewID); |
|||
|
|||
clone |
|||
.querySelector("[data-js-markdown-preview-tab] .btn") |
|||
.setAttribute("data-ic-target", "#" + previewID); |
|||
|
|||
var cancelButton = clone.querySelector("[data-js-cancel-button]"); |
|||
$(cancelButton).on("click", function() { |
|||
// re-enable click/hover events on the reply button
|
|||
$(this) |
|||
.parents(".comment") |
|||
.first() |
|||
.find(".btn-post-action[name=reply]") |
|||
.first() |
|||
.css("pointer-events", "auto"); |
|||
}); |
|||
|
|||
// If the user has text selected inside a comment when they click the reply
|
|||
// button, start the comment form off with that text inside a blockquote
|
|||
if (window.getSelection) { |
|||
var selectedText = ""; |
|||
|
|||
// only capture the selected text if it's all from the same comment
|
|||
var selection = window.getSelection(); |
|||
var $start = $(selection.anchorNode).closest(".comment-text"); |
|||
var $end = $(selection.focusNode).closest(".comment-text"); |
|||
if ($start.is($end)) { |
|||
selectedText = selection.toString(); |
|||
} |
|||
|
|||
if (selectedText.length > 0) { |
|||
selectedText = selectedText.replace(/\s+$/g, ""); |
|||
selectedText = selectedText.replace(/^/gm, "> "); |
|||
textarea.value = selectedText + "\n\n"; |
|||
textarea.scrollTop = textarea.scrollHeight; |
|||
} |
|||
} |
|||
|
|||
// add a warning about the comment's age, if necessary (determined by backend)
|
|||
var warningAge = $(this).attr("data-js-old-warning-age"); |
|||
if (warningAge) { |
|||
var warningDiv = document.createElement("div"); |
|||
warningDiv.classList.add("warning-old-reply"); |
|||
warningDiv.innerHTML = |
|||
'<p class="text-warning text-small">The comment you\'re replying to ' + |
|||
"is " + |
|||
warningAge + |
|||
" old. Replying to old comments is fine as long as you're " + |
|||
"contributing to the discussion.</p>"; |
|||
clone.querySelector("form").prepend(warningDiv); |
|||
} |
|||
|
|||
// update Intercooler so it knows about this new form
|
|||
Intercooler.processNodes(clone); |
|||
|
|||
$replies.prepend(clone); |
|||
$.onmount(); |
|||
}); |
|||
}); |
@ -0,0 +1,46 @@ |
|||
// Copyright (c) 2020 Tildes contributors <code@tildes.net>
|
|||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
|
|||
$.onmount("[data-js-comment-reply-form]", function() { |
|||
var $this = $(this); |
|||
|
|||
// the parent comment's Reply button (that was clicked to create this form)
|
|||
var $replyButton = $this |
|||
.closest(".comment") |
|||
.find(".btn-post-action[name=reply]") |
|||
.first(); |
|||
|
|||
// disable click/hover events on the reply button to prevent opening multiple forms
|
|||
$replyButton.css("pointer-events", "none"); |
|||
|
|||
// have the Cancel button re-enable click/hover events on the reply button
|
|||
$this.find("[data-js-cancel-button]").click(function() { |
|||
$replyButton.css("pointer-events", "auto"); |
|||
}); |
|||
|
|||
var $textarea = $this.find("textarea").first(); |
|||
|
|||
// If the user has text selected inside a comment when the reply form is created,
|
|||
// populate the textbox with that text inside a blockquote
|
|||
if (window.getSelection) { |
|||
var selectedText = ""; |
|||
|
|||
// only capture the selected text if it's all from the same comment
|
|||
var selection = window.getSelection(); |
|||
var $start = $(selection.anchorNode).closest(".comment-text"); |
|||
var $end = $(selection.focusNode).closest(".comment-text"); |
|||
if ($start.is($end)) { |
|||
selectedText = selection.toString(); |
|||
} |
|||
|
|||
if (selectedText.length > 0) { |
|||
selectedText = selectedText.replace(/\s+$/g, ""); |
|||
selectedText = selectedText.replace(/^/gm, "> "); |
|||
|
|||
$textarea.val(selectedText + "\n\n"); |
|||
$textarea.scrollTop($textarea.prop("scrollHeight")); |
|||
} |
|||
} |
|||
|
|||
$textarea.focus(); |
|||
}); |
@ -0,0 +1,35 @@ |
|||
{# Copyright (c) 2020 Tildes contributors <code@tildes.net> #} |
|||
{# SPDX-License-Identifier: AGPL-3.0-or-later #} |
|||
|
|||
{% from 'macros/forms.jinja2' import markdown_textarea %} |
|||
|
|||
<li class="comment-tree-item"> |
|||
|
|||
<form |
|||
method="post" |
|||
autocomplete="off" |
|||
data-ic-post-to="{{ request.route_url( |
|||
"ic_comment_replies", |
|||
comment_id36=parent_comment.comment_id36, |
|||
) }}" |
|||
data-ic-replace-target="true" |
|||
data-js-confirm-cancel="Discard your reply?" |
|||
data-js-prevent-double-submit |
|||
data-js-confirm-leave-page-unsaved |
|||
data-js-comment-reply-form |
|||
> |
|||
{% if parent_comment.age.days >= 7 %} |
|||
<div class="warning-old-reply"> |
|||
<p class="text-warning text-small">The comment you're replying to is {{ parent_comment.age|vague_timedelta_description }} old. Replying to old comments is fine as long as you're contributing to the discussion.</p> |
|||
</div> |
|||
{% endif %} |
|||
|
|||
{{ markdown_textarea(id="markdown-reply-%s" % parent_comment.comment_id36) }} |
|||
|
|||
<div class="form-buttons"> |
|||
<button type="submit" class="btn btn-primary">Post comment</button> |
|||
<button type="button" class="btn btn-link" data-js-cancel-button>Cancel</button> |
|||
</div> |
|||
</form> |
|||
|
|||
</li> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue