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