Comments UI update (#11283)

* tooltips 1.0.1

* tooltips 1.0.1

* init

* .

* drop not needed files

* .

* init

* init

* .

* comments

* border

* missing bits

* build comment JS

* .

* .

* specs

* sloan avatar fix

* bunch of fixes

* specs and fixes

* specs

* .

* swap order of links

* contrast

* js for dropdown

* icons

* op-author

* three dots icon

* reverts

* test: capybara- make sure that we click the dropdown before clicking on edit

* permalink --> link

* tiny fixes

* aria-label on form

* proper alt on user's avatar

* aria has popup

* use UL > LI instead of DIV

* add aria-labels to dropdown items

* another aria-label

* better links: nav

* contrast

Co-authored-by: Ridhwana <ridhwana.khan16@gmail.com>
This commit is contained in:
ludwiczakpawel 2020-11-19 00:21:19 +01:00 committed by GitHub
parent fd8f83dc7d
commit 70706d0a62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 779 additions and 1322 deletions

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 10.677L8 6.935 9 6l3 2.807L15 6l1 .935-4 3.742zm0 4.517L9 18l-1-.935 4-3.742 4 3.742-1 .934-3-2.805z"/>
</svg>

After

Width:  |  Height:  |  Size: 212 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 18l-4-3.771 1-.943 3 2.829 3-2.829 1 .943L12 18zm0-10.115l-3 2.829-1-.943L12 6l4 3.771-1 .942-3-2.828z"/>
</svg>

After

Width:  |  Height:  |  Size: 214 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 8.25a6 6 0 110 12 6 6 0 010-12zm0 2.625l-.992 2.01-2.218.322 1.605 1.564-.379 2.21L12 15.937l1.984 1.043-.379-2.209 1.605-1.564-2.218-.323L12 10.875zm.75-6.376l3.75.001v2.25l-1.022.854a7.45 7.45 0 00-2.728-.817V4.5zm-1.5 0v2.288a7.451 7.451 0 00-2.727.816L7.5 6.75V4.5h3.75z"/>
</svg>

After

Width:  |  Height:  |  Size: 386 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.25 12a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zm5.25 0a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zm3.75 1.5a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"/>
</svg>

After

Width:  |  Height:  |  Size: 273 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M17 13l-5 6-5-6h3.125c0-3.314 2.798-6 6.25-6 .17 0 .34.006.506.02-1.787.904-3.006 2.705-3.006 4.78V13H17z"/>
</svg>

After

Width:  |  Height:  |  Size: 213 B

View file

@ -131,7 +131,7 @@ function initializeCommentDropdown() {
return;
}
reportAbuseLink.innerHTML = `<a href="${reportAbuseLink.dataset.path}" class="crayons-link crayons-link--block">Report Abuse</a>`;
reportAbuseLink.innerHTML = `<a href="${reportAbuseLink.dataset.path}" class="crayons-link crayons-link--block">Report abuse</a>`;
}
function addDropdownListener(dropdown) {

View file

@ -1,3 +1,5 @@
var iconSmallThread = `<svg width="24" height="24" class="crayons-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17 13l-5 6-5-6h3.125c0-3.314 2.798-6 6.25-6 .17 0 .34.006.506.02-1.787.904-3.006 2.705-3.006 4.78V13H17z" /></svg>`;
function initializeCommentsPage() {
if (document.getElementById('comments-container')) {
toggleCodeOfConduct();
@ -28,12 +30,22 @@ function initializeCommentsPage() {
for (var i = 0; i < publicReactionCounts.length; i++) {
var buttForComment = document.getElementById('button-for-comment-' + publicReactionCounts[i].id);
if (buttForComment) {
var reactionsCountWrapper = buttForComment.querySelector('.reactions-count');
var reactionsLabelWrapper = buttForComment.querySelector('.reactions-label');
if (publicReactionCounts[i].count > 0) {
if (!document.getElementById('reactions-count-' + publicReactionCounts[i].id)) {
buttForComment.innerHTML = buttForComment.innerHTML + "<span class='reactions-count' id='reactions-count-" + publicReactionCounts[i].id + "'>" + publicReactionCounts[i].count + '</span>';
if (publicReactionCounts[i].count > 1) {
reactionsLabelWrapper.innerHTML = "&nbsp;likes";
} else {
document.getElementById('reactions-count-' + publicReactionCounts[i].id).innerHTML = publicReactionCounts[i].count;
reactionsLabelWrapper.innerHTML = "&nbsp;like";
}
reactionsCountWrapper.id = 'reactions-count-' + publicReactionCounts[i].id;
reactionsCountWrapper.innerHTML = publicReactionCounts[i].count;
reactionsCountWrapper.classList.remove("hidden");
} else {
reactionsCountWrapper.classList.add("hidden");
reactionsCountWrapper.innerHTML = '0';
}
}
}
@ -41,14 +53,13 @@ function initializeCommentsPage() {
for (var i = 0; i < allNodes.length; i++) {
if (allNodes[i].dataset.commentAuthorId == responseObj.current_user.id) {
allNodes[i].dataset.currentUserComment = "true";
var userActionsEl = allNodes[i].children[0].children[2].children[0];
var path = allNodes[i].dataset.path;
var userActionsEl = allNodes[i].querySelector('.current-user-actions');
var buttEl = document.getElementById('button-for-comment-' + allNodes[i].dataset.commentId);
if (userActionsEl && buttEl) {
userActionsEl.className = 'current-user-actions';
userActionsEl.innerHTML = '<a data-no-instant href="' + userActionsEl.parentNode.dataset.path + '/delete_confirm" class="edit-butt">Delete</a>\
<a href="' + userActionsEl.parentNode.dataset.path + '/edit">Edit</a>'
userActionsEl.style.display = 'inline-block';
document.getElementById('button-for-comment-' + allNodes[i].dataset.commentId).classList.add('reacted');
userActionsEl.innerHTML = `<li><a href="${ path }/edit" class="crayons-link crayons-link--block" aria-label="Edit this comment">Edit</a></li>
<li><a data-no-instant href="${ path }/delete_confirm" class="edit-butt crayons-link crayons-link--block" aria-label="Delete this comment">Delete</a></li>`
userActionsEl.classList.remove('hidden');
}
}
}
@ -78,16 +89,27 @@ function initializeCommentsPage() {
thisButt.disabled = true;
function successCb(response) {
var reactionCountSpan = thisButt.children[2];
var reactionCount = thisButt.querySelector('.reactions-count');
var reactionLabel = thisButt.querySelector('.reactions-label');
if (response.result === 'create') {
thisButt.classList.add('reacted');
if (reactionCountSpan) {
reactionCountSpan.innerHTML = parseInt(reactionCountSpan.innerHTML) + 1;
if (reactionCount) {
reactionCount.innerHTML = parseInt(reactionCount.innerHTML) + 1;
reactionCount.classList.remove("hidden");
if(parseInt(reactionCount.innerHTML) == 1) {
reactionLabel.innerHTML = "&nbsp;like"
} else if(parseInt(reactionCount.innerHTML) > 1) {
reactionLabel.innerHTML = "&nbsp;likes"
}
}
} else {
thisButt.classList.remove('reacted');
if (reactionCountSpan) {
reactionCountSpan.innerHTML = parseInt(reactionCountSpan.innerHTML) - 1;
if (reactionCount) {
reactionCount.innerHTML = parseInt(reactionCount.innerHTML) - 1;
if(parseInt(reactionCount.innerHTML) == 0) {
reactionCount.classList.add("hidden");
reactionLabel.innerHTML = "Like"
}
}
}
}
@ -117,13 +139,12 @@ function initializeCommentsPage() {
showModal('reply-to-comment');
return;
}
var actionNode = event.target.parentNode;
var parentId = actionNode.dataset.commentId;
var parentId = event.target.closest('.comment').dataset.commentId;
var waitingOnCSRF = setInterval(function () {
var metaTag = document.querySelector("meta[name='csrf-token']");
if (metaTag) {
clearInterval(waitingOnCSRF);
commentWrapper = event.target.closest('.inner-comment');
commentWrapper = event.target.closest('.comment__details');
commentWrapper.classList.add("replying");
commentWrapper.innerHTML += buildCommentFormHTML(commentableId, commentableType, parentId);
initializeCommentsPage();
@ -137,14 +158,7 @@ function initializeCommentsPage() {
return false;
}
}
var editButts = document.getElementsByClassName('edit-butt');
for (var i = 0; i < editButts.length; i++) {
var butt = editButts[i];
butt.onclick = function () {
// event.preventDefault();
// alert("edit clicked!")
};
}
if (document.getElementById('new_comment')) {
document.getElementById('new_comment').addEventListener('submit', handleCommentSubmit);
}
@ -165,16 +179,6 @@ function toggleCodeOfConduct() {
}
}
function replaceActionButts(el) {
var loggedInActionButts = "";
var wrapper = el.getElementsByClassName("actions")[0]
if (el.dataset.currentUserComment == "true") {
loggedInActionButts = '<a data-no-instant href="' + el.parentNode.parentNode.dataset.path + '/delete_confirm" class="edit-butt">Delete</a>\
<a href="' + el.parentNode.parentNode.dataset.path + '/edit">Edit</a>'
}
wrapper.innerHTML = '<span class="current-user-actions">' + loggedInActionButts + '</span><a href="#" class="toggle-reply-form">Reply</a>';
}
function handleCommentSubmit(event) {
event.preventDefault();
var form = event.target;
@ -219,10 +223,13 @@ function handleCommentSubmit(event) {
var mainCommentsForm = document.getElementById("new_comment");
if (parentComment) {
handleFormClose(event);
if (newComment.depth > 2) {
parentComment.getElementsByClassName("toggle-reply-form")[0].innerHTML = ""
if (newComment.depth > 3) {
var replyTrigger = parentComment.getElementsByClassName("toggle-reply-form")[0];
var threadIndicator = `<span class="fs-s inline-flex items-center fs-italic color-base-50 pl-1">${ iconSmallThread }Thread</span>`;
replyTrigger.classList.replace("inline-flex", "hidden");
replyTrigger.parentNode.innerHTML += threadIndicator;
}
var actionsNode = parentComment.getElementsByClassName("inner-comment")[0];
var actionsNode = parentComment.getElementsByClassName("comment__inner")[0];
actionsNode.parentNode.insertBefore(newNode, actionsNode.nextSibling);
}
@ -496,19 +503,19 @@ function handleImageUpload(event, randomIdNumber) {
}
function listenForDetailsToggle() {
var detailItems = document.getElementsByTagName("DETAILS");
var detailItems = document.querySelectorAll(".js-comment-wrapper");
for (var i = 0; i < detailItems.length; i++) {
detailItems[i].addEventListener("toggle", event => {
var item = event.target
var itemSummaryContent = item.getElementsByTagName("SPAN")[0]
var usernames = item.getElementsByClassName("comment-username")
var item = event.target;
var itemSummaryContent = item.getElementsByClassName("js-collapse-comment-content")[0];
var usernames = item.getElementsByClassName("js-comment-username");
var number = "";
if (usernames.length > 1) {
number = " + " + (usernames.length - 1) + " replies"
}
var itemUsername = usernames[0].textContent + number
if (item.open) {
itemSummaryContent.innerHTML = "&nbsp;"
itemSummaryContent.innerHTML = "";
} else {
itemSummaryContent.innerHTML = itemUsername;
}

View file

@ -18,7 +18,7 @@ function buildCommentFormHTML(commentableId, commentableType, parentId) {
<input value="${parentId}" type="hidden" name="comment[parent_id]" id="comment_parent_id" />
<div class="comment-form__inner">
<div class="comment-form__field">
<textarea id="textarea-for-${parentId}" class="crayons-textfield crayons-textfield--ghost comment-textarea" name="comment[body_markdown]" placeholder="Reply..." required="required" onkeydown="handleKeyDown(event)" onfocus="handleFocus(event)" oninput="handleChange(event)" onkeykup="handleKeyUp(event)"></textarea>
<textarea id="textarea-for-${parentId}" class="crayons-textfield crayons-textfield--ghost comment-textarea" name="comment[body_markdown]" placeholder="Reply..." aria-label="Reply to a comment..." required="required" onkeydown="handleKeyDown(event)" onfocus="handleFocus(event)" oninput="handleChange(event)" onkeykup="handleKeyUp(event)"></textarea>
<div class="comment-form__toolbar">
<div class="editor-image-upload">
<input type="file" id="image-upload-${randomIdNumber}" name="file" accept="image/*" style="display:none">

View file

@ -1,116 +1,138 @@
function buildCommentHTML(comment) {
var iconSmallOverflowHorizontal = `<svg width="24" height="24" viewBox="0 0 24 24" class="crayons-icon pointer-events-none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.25 12a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zm5.25 0a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zm3.75 1.5a1.5 1.5 0 100-3 1.5 1.5 0 000 3z" /></svg>`;
var iconCollaspe = `<svg width="24" height="24" class="crayons-icon expanded" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 10.6771L8 6.93529L8.99982 6L12 8.80653L15.0002 6L16 6.93529L12 10.6771ZM12 15.1935L8.99982 18L8 17.0647L12 13.3229L16 17.0647L15.0002 17.9993L12 15.1935Z" /></svg>`;
var iconExpand = `<svg width="24" height="24" class="crayons-icon collapsed" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 18L8 14.2287L8.99982 13.286L12 16.1147L15.0002 13.286L16 14.2287L12 18ZM12 7.88533L8.99982 10.714L8 9.77133L12 6L16 9.77133L15.0002 10.7133L12 7.88533Z" /></svg>`;
var depthClass = "";
var githubIcon = "";
var twitterIcon = "";
var customClass = "";
var detailsStartHTML = "";
var detailsFinishHTML = "";
var body = "";
if (!comment.newly_created) {
depthClass="child flat-node"
} else if (comment.depth == 0 ){
depthClass = "root";
} else if (comment.depth < 3) {
depthClass="child"
var detailsStartHTML = "";
var detailsEndHTML = "";
var commentHeader = "";
var commentFooter = "";
var commentAvatar = "";
var commentBody = "";
if ( comment.depth == 0 ) {
depthClass += "root ";
} else {
depthClass="child flat-node"
depthClass += "child "
}
if (comment.user.twitter_username && comment.user.twitter_username.length > 0) {
twitterIcon = '<a href="http://twitter.com/'+comment.user.twitter_username+'" rel="noopener noreferrer" target="_blank"><%= image_tag("twitter-logo.svg", class:"icon-img", alt: "twitter logo") %></a>'
}
if (comment.user.github_username && comment.user.github_username.length > 0) {
githubIcon = '<a href="http://github.com/'+comment.user.github_username+'" rel="noopener noreferrer" target="_blank"><%= image_tag("github-logo.svg", class:"icon-img", alt: "github logo") %></a>'
if ( comment.depth > 3 ) {
depthClass += "comment--too-deep ";
}
if (comment.newly_created) {
customClass = "comment-created-via-fetch"
}
if (comment.depth < 3) {
detailsStartHTML = '<details open><summary><span>&nbsp;</span></summary>'
detailsFinishHTML = '</details>'
detailsStartHTML = `
<details class="comment-wrapper comment-wrapper--deep-${ comment.depth } js-comment-wrapper" open>
<summary aria-label="Toggle this comment (and replies)">
<span class="inline-block align-middle ${ comment.depth > 0 ? 'mx-0' : 'm:mx-1'}">
${ iconCollaspe }
${ iconExpand }
</span>
<span class="js-collapse-comment-content inline-block align-middle"></span>
</summary>
`;
detailsEndHTML = `</details>`;
}
body = '<style>'+comment.css+'</style>\
'+detailsStartHTML+'\
<div class="comment-hash-marker" id="'+comment.id_code+'"></div>\
<div id="comment-node-'+comment.id+'" class="single-comment-node '+depthClass+' comment-deep-'+comment.depth+'" "\
data-comment-id="'+comment.id+'" data-comment-author-id="'+comment.user.id+'" data-current-user-comment="'+comment.newly_created+'" data-content-user-id="'+comment.user.id+'">\
<div class="inner-comment '+customClass+'">\
<div class="details">\
<a href="/'+comment.user.username+'">\
<img class="profile-pic" src="'+comment.user.profile_pic+'" alt="'+comment.user.username+'">\
<span class="comment-username"><span class="comment-username-inner">'+comment.user.name+'</span></span>\
</a>\
'+twitterIcon+'\
'+githubIcon+'\
<div class="comment-date">\
<a href="'+comment.url+'">\
<time datetime="'+comment.published_timestamp+'">\
'+comment.readable_publish_date+'\
</time>\
</a>\
</div>\
<button class="dropbtn">\
<%= image_tag("overflow-horizontal.svg", class: "dropdown-icon", alt: "Toggle dropdown menu") %>\
</button>\
<div class="dropdown">\
<div class="crayons-dropdown p-1 z-30 right-1 left-1 s:right-0 s:left-auto fs-base">\
<a href="'+comment.url+'" class="crayons-link crayons-link--block">\
Permalink\
</a>\
<a href="'+comment.url+'" class="crayons-link crayons-link--block permalink-copybtn" data-no-instant>\
Copy Permalink\
</a>\
<a href="'+comment.url+'/settings" class="crayons-link crayons-link--block">\
Settings\
</a>\
<a href="/report-abuse?url='+comment.url+'" class="crayons-link crayons-link--block">Report Abuse</a>\
</div>\
</div>\
</div>\
<div class="body">\
'+comment.body_html+'\
'+reactions(comment)+'\
</div>\
'+actions(comment)+'\
</div>\
</div>\
'+detailsFinishHTML;
commentAvatar = `<a href="/${ comment.user.username }" class="shrink-0 crayons-avatar ${ comment.depth == 0 ? 'm:crayons-avatar--l mt-4 m:mt-3' : 'mt-4' }">
<img class="crayons-avatar__image" width="32" height="32" src="${ comment.user.profile_pic }" alt="${ comment.user.username } profile" />
</a>`;
return body;
commentHeader = `<header class="comment__header">
<a href="/${ comment.user.username }" class="crayons-link crayons-link--secondary flex items-center fw-medium">
<span class="js-comment-username">${ comment.user.name }</span>
</a>
<span class="color-base-30 px-2" role="presentation">&bull;</span>
<a href="${ comment.url }" class="comment-date crayons-link crayons-link--secondary fs-s">
<time datetime="${ comment.published_timestamp }">
${ comment.readable_publish_date }
</time>
</a>
<div class="comment__dropdown">
<button class="dropbtn comment__dropdown-trigger crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon" aria-label="Toggle dropdown menu" aria-haspopup="true">
${ iconSmallOverflowHorizontal }
</button>
<ul class="crayons-dropdown p-1 right-1 s:right-0 s:left-auto fs-base dropdown">
<li><a href="${ comment.url }" class="crayons-link crayons-link--block permalink-copybtn" aria-label="Copy link to ${ comment.user.name }'s comment" data-no-instant>Copy link</a></li>
<li><a href="${ comment.url }/settings" class="crayons-link crayons-link--block" aria-label="Go to ${ comment.user.name }'s comment settings">Settings</a></li>
<li><a href="/report-abuse?url=${ comment.url }" class="crayons-link crayons-link--block" aria-label="Report ${ comment.user.name }'s comment as abusive or violating our code of conduct and/or terms and conditions">Report abuse</a></li>
<li class="${ comment.newly_created ? '' : 'hidden' }"><a href="${ comment.url }/edit" class="crayons-link crayons-link--block" rel="nofollow" aria-label="Delete this comment">Edit</a></li>
<li class="${ comment.newly_created ? '' : 'hidden' }"><a data-no-instant="" href="${ comment.url }/delete_confirm" class="crayons-link crayons-link--block" rel="nofollow" aria-label="Delete this comment">Delete</a></li>
</ul>
</div>
</header>`;
commentFooter = `<footer class="comment__footer">
${ react(comment) }
${ reply(comment) }
</footer>`;
commentBody = `${ detailsStartHTML }
<div class="comment single-comment-node ${ depthClass } comment--deep-${ comment.depth }" id="comment-node-${ comment.id }" data-comment-id="${ comment.id }" data-path="${ comment.url }" data-comment-author-id="${ comment.user.id }" data-current-user-comment="${ comment.newly_created }" data-content-user-id="${ comment.user.id }">
<div class="comment__inner">
${ commentAvatar }
<div class="inner-comment comment__details">
<div class="comment__content crayons-card">
${ commentHeader }
<div class="comment__body text-styles text-styles--secondary body">
${ comment.body_html }
</div>
</div>
${ commentFooter }
</div>
</div>
</div>
${ detailsEndHTML }`;
return commentBody;
}
function actions(comment) {
function reply(comment) {
var iconSmallComment = `<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" class="crayons-icon reaction-icon not-reacted"><path d="M10.5 5h3a6 6 0 110 12v2.625c-3.75-1.5-9-3.75-9-8.625a6 6 0 016-6zM12 15.5h1.5a4.501 4.501 0 001.722-8.657A4.5 4.5 0 0013.5 6.5h-3A4.5 4.5 0 006 11c0 2.707 1.846 4.475 6 6.36V15.5z"/></svg>`;
var replyButton = `<a class="js actions crayons-btn crayons-btn--ghost crayons-btn--s crayons-btn--icon-left toggle-reply-form mr-1 inline-flex"
href="#${ comment.url }"
data-comment-id="${ comment.id }"
data-path="${ comment.url }"
rel="nofollow">
${ iconSmallComment }
<span class="hidden m:inline-block">Reply</span>
</a>`;
if (comment.newly_created) {
return '<div class="actions" data-comment-id="'+comment.id+'" data-path="'+comment.url+'">\
<span class="current-user-actions" style="display: '+ (comment.newly_created ? 'inline-block' : 'none') +';">\
<a data-no-instant="" href="'+comment.url+'/delete_confirm" class="edit-butt" rel="nofollow">DELETE</a>\
<a href="'+comment.url+'/edit" class="edit-butt" rel="nofollow">EDIT</a>\
</span>\
<a href="#" class="toggle-reply-form" rel="nofollow">Reply</a>\
</div>';
} else {
return '<div class="actions" data-comment-id="'+comment.id+'" data-path="'+comment.url+'" data-commentable-id="'+comment.commentable.id+'">\
<a href="'+comment.url+'" rel="nofollow">VIEW/REPLY</a>\
</div>';
return replyButton;
}
}
function reactions(comment) {
if (comment.newly_created) {
return '<button class="reaction-button reacted" id="button-for-comment-'+comment.id+'" data-comment-id="'+comment.id+'">\
<img src="<%= asset_path("favorite-heart-outline-button.svg") %>" alt="Favorite heart outline button">\
<img class="voted-heart" src="<%= asset_path("emoji/emoji-one-heart.png") %>" alt="Favorite heart button">\
<span class="reactions-count" id="reactions-count-'+comment.id+'">1</span></button>';
} else {
if (comment.heart_ids.indexOf(userData().id) > -1) {
var reactedClass = "reacted";
} else {
var reactedClass = ""
}
return '<button style="background:white" class="reaction-button '+ reactedClass +'" id="button-for-comment-'+comment.id+'" data-comment-id="'+comment.id+'">\
<img src="<%= asset_path("favorite-heart-outline-button.svg") %>" alt="Favorite heart outline button">\
<img class="voted-heart" src="<%= asset_path("emoji/emoji-one-heart.png") %>" alt="Favorite heart button">\
<span class="reactions-count" id="reactions-count-'+comment.id+'">'+comment.public_reactions_count+'</span></button>';
function react(comment) {
var reactedClass = "";
var num = 1;
var iconSmallHeart = `<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" class="crayons-icon reaction-icon not-reacted"><path d="M18.884 12.595l.01.011L12 19.5l-6.894-6.894.01-.01A4.875 4.875 0 0112 5.73a4.875 4.875 0 016.884 6.865zM6.431 7.037a3.375 3.375 0 000 4.773L12 17.38l5.569-5.569a3.375 3.375 0 10-4.773-4.773L9.613 10.22l-1.06-1.062 2.371-2.372a3.375 3.375 0 00-4.492.25v.001z"/></svg>`;
var iconSmallHeartFilled = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="crayons-icon reaction-icon--like reaction-icon reacted"><path d="M5.116 12.595a4.875 4.875 0 015.56-7.68h-.002L7.493 8.098l1.06 1.061 3.181-3.182a4.875 4.875 0 016.895 6.894L12 19.5l-6.894-6.894.01-.01z"/></svg>`;
if (!comment.newly_created && comment.heart_ids.indexOf(userData().id) > -1) {
reactedClass = "reacted"
}
if (!comment.newly_created) {
num = comment.public_reactions_count;
}
var reactButton = `<button class="crayons-btn crayons-btn--ghost crayons-btn--icon-left crayons-btn--s mr-1 reaction-like inline-flex reaction-button" id="button-for-comment-${ comment.id }" data-comment-id="${ comment.id }">
${ iconSmallHeart }
${ iconSmallHeartFilled }
<span class="reactions-count" id="reactions-count-${ comment.id }">${ num }</span>
<span class="reactions-label hidden m:inline-block">like</span>
</button>`;
return reactButton;
}

View file

@ -1,775 +0,0 @@
@import 'variables';
@import 'mixins';
@import 'functions';
@import 'config/import';
%kbd {
display: inline-block;
padding: 3px 5px;
font-size: 11px;
line-height: 10px;
color: #444d56;
vertical-align: middle;
background-color: #fafbfc;
border: solid 1px #c6cbd1;
border-bottom-color: #959da5;
border-radius: 3px;
box-shadow: inset 0 -1px 0 #959da5;
}
$indent-distance: calc(1.1vw + 13px);
@keyframes fade-out {
0% {
background-color: rgb(208, 255, 235);
}
100% {
background-color: white;
}
}
a.header-link {
color: var(--body-color);
display: block;
&:hover {
color: var(--body-color);
}
}
.comments {
.picture {
height: 180px;
max-width: 900px;
margin: auto;
border-top-right-radius: 5px;
border-top-left-radius: 5px;
@media screen and (min-width: 390px) {
height: 240px;
}
@media screen and (min-width: 820px) {
height: 280px;
}
background-color: #ecf0f2;
background: #ecf0f2 no-repeat top center;
background-size: cover;
position: relative;
z-index: 1;
}
.blank-comment-space {
height: 3px;
@media screen and (min-width: 820px) {
height: 0px;
}
}
.article-header {
width: 820px;
max-width: 100%;
margin: 50px auto 10px;
padding: 0px 0px 90px;
background: var(--card-bg);
z-index: 3;
position: relative;
@media screen and (min-width: 820px) {
margin: 72px auto 10px;
border-radius: 5px;
@include themeable(
border,
theme-container-border,
1px solid darken($lightest-gray, 4%)
);
}
h3 {
width: 94%;
margin: 12px 0 5px;
margin-left: 1.8%;
font-weight: 500;
font-size: calc(1.5vw + 22px);
}
h4 {
margin: 0;
font-weight: 300;
color: rgb(195, 195, 195);
font-size: 17px;
width: 96%;
margin: auto;
margin-bottom: -40px;
a {
color: rgb(105, 103, 103);
}
}
.body {
padding: 60px 0px 0px;
width: 96%;
margin: auto;
margin-bottom: -35px;
.read-more {
color: var(--link-brand-color);
display: inline-block;
&:hover {
opacity: 0.96;
}
}
img {
max-width: 100%;
max-height: 350px;
}
kbd {
@extend %kbd;
}
}
}
.single-comment-header {
height: 10px;
}
}
.comments-container {
padding-top: 10px;
margin: auto;
margin-bottom: var(--su-4);
text-align: left;
overflow-wrap: break-word;
&.comments-dedicated-page-container {
width: 800px;
max-width: 96%;
min-height: calc(97vh - 200px);
}
img.icon-image {
height: 21px;
opacity: 0.7;
&:hover {
opacity: 1;
}
}
.top-level-actions {
margin: 0px 0px 85px;
padding: 3px 0px;
z-index: 4;
position: relative;
border-radius: 3px;
font-size: calc(0.35vw + 16px);
h3 {
margin: 5px 0px;
a {
font-size: 0.62em;
background: $black;
color: white;
margin: 2px 0px 2px;
margin-right: 5px;
vertical-align: 2px;
}
}
.comment-action-buttons,
.comment-action-text {
display: inline-block;
color: $medium-gray;
font-weight: 300;
}
a {
display: inline-block;
background: $purple;
border-radius: 3px;
color: $black;
font-family: $helvetica-condensed;
font-stretch: condensed;
padding: 4px 8px;
font-size: 0.77em;
vertical-align: 1px;
margin: 8px 0px 2px;
}
.commentable-title {
font-size: calc(0.3vw + 16px);
color: $medium-gray;
}
}
details {
details {
.comment-deep-1,
.comment-deep-2 {
margin-top: -123px;
}
// this ms-lang hack (which behaves like a media query) is going to be ignored by all browsers
// except IE and 11 and it fixes indentation on the unsupported <details> tags
_:-ms-lang(x),
.comment-deep-1,
.comment-deep-2 {
margin-top: 0px;
}
summary {
position: relative;
}
&[open] {
> summary {
width: calc(1.1vw + 12px); //slightly smaller than indent distance
padding-bottom: 100px;
}
}
}
summary {
cursor: pointer;
color: lighten($medium-gray, 8%);
font-size: 14.5px;
font-style: oblique;
padding: 4px 0px;
padding-left: 4px;
user-select: none;
&:active {
outline: 0;
box-shadow: 0px 0px 0px $light-medium-gray !important;
}
&:focus-visible {
outline: 0;
&:not(:active) {
box-shadow: 0px 0px 6px $light-medium-gray !important;
}
}
span {
display: inline-block;
width: calc(100% - 50px);
line-height: 20px;
}
}
}
.comment-trees {
.comment-hash-marker {
margin-top: -45px;
margin-bottom: 45px;
border: 1px solid transparent;
z-index: var(--z-negative);
position: relative;
&:target + .single-comment-node {
animation-name: fade-out;
animation-duration: 5s;
animation-timing-function: ease-out;
}
}
.root-comment {
margin-top: -80px;
.comment-parent-link {
background: var(--card-bg);
@include themeable(
border,
theme-container-border,
1px solid $medium-gray
);
display: block;
padding: 9px 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 0.8em;
@include themeable(
background,
theme-container-accent-background,
lighten($lightest-gray, 1%)
);
}
}
}
.comment-view-parent {
margin-bottom: 7px;
text-align: left;
font-family: $helvetica-condensed;
font-size: 0.8em;
background: $purple;
display: inline-block;
padding: 2px 4px;
border-radius: 3px;
margin-right: 5px;
vertical-align: top;
&.comment-view-commentable {
background: transparent;
max-width: calc(100% - 100px);
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
}
.alert-error {
background: $red;
max-width: 730px;
font-family: var(--ff-sans-serif);
color: white;
border-radius: 5px;
margin: 20px auto;
padding: 20px;
}
}
.single-comment-node {
padding: 2px 0px 0px $indent-distance;
text-align: left;
&.root {
padding: 0px 0px 2px 0;
}
margin-top: 15px;
font-size: 17px;
position: relative;
@media screen and (min-width: 390px) {
font-size: 20px;
}
&.flat-node {
padding: 0px;
margin-top: -3px;
margin-bottom: -3px;
&.root {
padding: 2px 0px;
margin-top: 0px;
}
.inner-comment {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
button {
margin-bottom: 3px;
z-index: 7;
position: relative;
}
}
.comment-deep-3 {
margin-top: -5px;
padding: 2px 0px 2px calc(0.9vw + 7px);
button {
margin-bottom: 3px;
z-index: 8;
position: relative;
}
}
.permalink {
width: 40px;
position: absolute;
right: 2px;
margin-top: calc(0.4vw + 12px);
text-align: center;
opacity: 0.9;
img {
width: 14px;
}
&:hover {
opacity: 1;
}
}
.low-quality-comment-marker {
font-size: 0.66em;
padding: 6px 13px;
@include themeable(
background,
theme-container-accent-background,
$light-gray
);
font-weight: bold;
border-bottom: 1px solid $light-medium-gray;
a {
background: lighten($green, 14%);
color: $black;
padding: 1px 5px 3px;
border-radius: 3px;
vertical-align: -1px;
margin-left: 3px;
}
img {
height: 22px;
width: 22px;
border-radius: 100px;
border: 2px solid $medium-gray;
vertical-align: -0.6em;
margin-right: 2px;
}
}
.low-quality-comment > *:not(.dropdown) {
opacity: 0.5;
}
.body {
padding: 1px;
padding-left: 1.5%;
padding-right: 1.5%;
padding-bottom: 5px;
margin: 0;
width: 96%;
font-size: 0.95em;
line-height: 1.35em;
overflow: hidden;
margin-bottom: -28px;
word-wrap: break-word;
font-family: var(--ff-sans-serif);
position: relative;
margin-left: 1px;
h1,
h2,
h3,
h4,
h5,
h6 {
padding: 0px;
margin: 4px 0px calc(1.1vw + 10px);
font-size: 1em;
font-weight: 600;
}
h1 {
font-size: 1.3em;
}
p {
padding: 0px;
margin: 4px 0px calc(1.1vw + 10px);
}
ul,
ol {
padding-left: 6px;
margin: 0.8em 0.7em 0.8em 1em;
}
ul {
list-style-type: square;
}
li {
margin: 3px 0px calc(0.5vw + 5px);
}
blockquote {
border-left: calc(0.2vw + 2px) solid $dark-gray;
padding: 0.1% 3% 0.1% 2%;
margin: 1.6em 1vw;
font-size: 0.92em;
line-height: 1.4em;
p {
padding: 0;
margin: 0.95em 0 0.95em;
}
&.twitter-tweet {
background: white;
max-width: 480px;
min-height: 135px;
border: 1px solid #e9eef2;
font-size: 17px;
line-height: 20px;
border-radius: 5px;
margin-top: 10px;
padding: 10px;
}
}
.edited-notification {
color: lighten($medium-gray, 20%);
font-size: 0.7em;
padding: 3px 0px 0px;
position: relative;
z-index: 1;
}
img {
max-width: 100%;
max-height: calc(50vh + 100px);
}
.table-wrapper-paragraph {
width: 100%;
margin-left: 0%;
overflow-x: auto;
}
a {
&.anchor {
padding-top: 50px;
margin-top: -50px;
-webkit-background-clip: content-box;
background-clip: content-box;
}
}
kbd {
@extend %kbd;
}
.liquid-comment {
.details {
.comment-date {
top: calc(14px - 0.25vw);
}
}
}
&.hidden-comment {
padding: 5px 0;
margin-bottom: 0;
}
}
.icon-img {
height: 16px;
width: 16px;
margin-right: 0px;
opacity: 0.7;
}
.details {
padding: 0px;
border-top-left-radius: 3px;
color: var(--link-brand-color);
position: relative;
.comment-date {
border: none;
position: absolute;
top: calc(14px - 0.25vw);
right: calc(35px + 0.2vw);
font-size: 12px;
text-align: right;
a {
color: var(--card-color-tertiary);
}
}
.dropbtn {
border: none;
cursor: pointer;
position: absolute;
top: calc(0.3vw + 13px);
right: 10px;
&.commentable-hidden {
top: calc(0.3vw + 8px);
}
svg {
transform: translateY(3px);
fill: var(--card-color-tertiary);
}
}
.dropdown {
position: absolute;
top: 40px;
right: 200px;
display: inline-block;
@media screen and (min-width: 580px) {
right: 10px;
}
.dropdown-icon {
position: absolute;
left: 10px;
top: 7px;
padding: 4px;
max-width: 15px;
max-height: 15px;
}
}
&.hidden-comment {
.dropbtn {
top: calc(0.3vw + 8px);
}
}
img.profile-pic {
height: 33px;
width: 33px;
border-radius: 50px;
margin: 1.5% 0.2% 1.5% 1.5%;
}
.comment-username {
vertical-align: calc(0.62vw + 13px);
}
.comment-username-inner {
vertical-align: middle;
display: inline-block;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
max-width: calc(60% - 20px);
}
.icon-img {
vertical-align: calc(0.7vw + 9px);
margin-left: 2px;
}
.op-marker {
display: none;
@media screen and (min-width: 580px) {
display: inline-block;
vertical-align: calc(0.7vw + 12px);
background: $purple;
color: $black;
padding: 2px 6px 2px;
margin-left: 0.3vw;
font-family: $helvetica-condensed;
font-stretch: condensed;
font-size: 0.78em;
border-radius: 3px;
}
}
a {
@include themeable(color, theme-secondary-color, lighten($dark-gray, 3%));
}
}
.reaction-button,
.dropbtn {
border: 0px;
background: transparent;
cursor: pointer;
border-radius: 3px;
height: 32px;
display: block;
padding: 0px;
margin-left: 2px;
position: relative;
margin-top: calc(-0.6vw - 4px);
z-index: 8;
position: relative;
&.reaction-button {
padding-right: 25px;
padding-top: 4px;
margin-top: 0px;
height: 25px;
}
.reactions-count {
font-size: 0.7em;
background: $light-gray;
padding: 1px 5px;
border-radius: 3px;
border: 1px solid darken($light-gray, 10%);
margin-left: 1px;
position: absolute;
top: 4px;
left: 24px;
}
img {
height: 21px;
width: 21px;
opacity: 0.62;
left: 0px;
&.dropdown-icon {
height: 18px;
width: 18px;
}
}
&:hover {
img {
opacity: 1;
}
}
.voted-heart {
display: none;
vertical-align: 0px;
margin-left: 0px;
height: 22px;
width: 22px;
opacity: 0.9;
&:hover {
opacity: 1;
}
}
@media screen and (min-width: 390px) {
vertical-align: -7px;
}
&.reacted {
color: $green;
img {
display: none;
}
.voted-heart {
display: inline-block;
}
.reactions-count {
color: $black;
font-weight: 500;
}
}
}
img.icon-image {
height: 20px;
}
.actions {
font-size: 0.8em;
padding: 0.3em 0.6em 0.3em 0.4em;
text-align: right;
position: relative;
min-height: 1.2em;
z-index: 5;
text-transform: capitalize;
margin-left: 58px;
a.thread-indication {
color: lighten($medium-gray, 22%);
font-size: 0.8em;
cursor: default;
&:hover {
opacity: 1;
}
}
a.register-now-cta {
font-size: 1.4em;
background: lighten($yellow, 12%);
border: 1px solid darken($yellow, 15%);
box-shadow: 5px 6px 0px darken($yellow, 15%);
color: $black;
padding: 5px 10px;
margin-right: 8px;
vertical-align: 20px;
line-height: 1.8em;
}
.current-user-actions {
.hidden {
display: none;
}
a {
margin-right: 10px;
display: inline-block;
color: $medium-gray;
}
}
}
.inner-comment {
padding: 0px;
border: 1px solid $light-medium-gray;
background: var(--card-bg);
&.comment-created-via-fetch {
animation: comment-create 1.2s;
.body {
background-color: transparent;
}
}
}
.comment-read-more {
background: $green !important;
padding: 3px 6px;
display: block;
width: 250px;
text-align: center;
margin: auto;
border-radius: 3px;
margin-bottom: 20px;
font-weight: bold;
&:hover {
opacity: 1;
background: lighten($green, 5%) !important;
}
}
}
@keyframes fadein {
0% {
opacity: 0.5;
height: 21px;
width: 18px;
}
100% {
opacity: 1;
height: 25px;
width: 22px;
}
}
@keyframes comment-create {
from {
background-color: $light-green;
}
to {
@include themeable(background-color, theme-container-background, #fff);
}
}
#response-templates-data {
display: none;
}
.comments-settings__row {
display: flex;
flex-direction: row;
justify-content: space-between;
grid-gap: var(--su-2);
&--subtitle {
color: var(--base-60);
}
}

View file

@ -615,6 +615,7 @@
(),
false
),
('fs', 'font-style', ('italic': italic, 'normal': normal), (), false),
(
'text',
'text-transform',

View file

@ -1,5 +1,3 @@
@import 'variables';
#error_explanation {
width: 100%;
border: 2px solid #d94949;
@ -24,39 +22,3 @@
border-radius: 5px;
}
}
// Remove post confirmation
.delete-confirm.container {
padding: 150px 10px;
min-height: 300px;
text-align: center;
h4 {
background: $black;
padding: 5px 8px;
color: $white;
display: inline-block;
margin-bottom: 0px;
border-radius: 3px;
}
h2 {
font-size: 22px;
margin-bottom: 40px;
}
a.delete-link {
background: $red;
color: white;
padding: 10px 15px;
&:hover {
color: white;
opacity: 0.9;
}
}
form button {
background: $red;
color: white;
font-size: 1.5em;
border: 0px;
border-radius: 3px;
padding: 6px 12px;
}
}

View file

@ -9,7 +9,6 @@
@import 'podcast-episodes-show';
@import 'podcast-form';
@import 'user-profile-header';
@import 'comments';
@import 'live';
@import 'preact/sidebar-widget';
@import 'preact/article-form';

View file

@ -5,8 +5,6 @@
}
.podcast-episode-container {
font-family: var(--ff-sans-serif);
.hero {
padding-bottom: 30px;
text-align: center;
@ -204,13 +202,6 @@
margin-bottom: 20px;
}
}
.body {
padding: calc(0.7vw + 4px) 0px calc(0.5vw + 2px);
font-family: var(--ff-sans-serif);
img {
min-height: 0px !important;
}
}
}
@keyframes spin {

View file

@ -1,10 +1,14 @@
@import '../config/import';
.comment-form {
--preview-display: none;
--field-display: flex;
display: flex;
margin-bottom: var(--su-4);
&__field {
display: flex;
display: var(--field-display);
flex: 1 auto;
flex-direction: column;
margin-bottom: var(--su-3);
@ -63,34 +67,192 @@
border: 1px solid var(--base-20);
margin-bottom: var(--su-3);
background: var(--card-bg);
}
}
// temporary until we have new design for comments
.inner-comment {
&.replying {
.actions,
.reaction-button {
display: none;
}
}
.comment-form {
margin: var(--su-6) var(--su-4) 0 var(--su-4);
}
}
.comment-form {
.comment-form__preview {
display: none;
display: var(--preview-display);
}
&.preview-open {
.comment-form__preview {
--preview-display: block;
--field-display: none;
}
}
.comment-wrapper {
--level-n-indent: var(--su-3);
--level-1-indent: var(--su-4);
@media (min-width: $breakpoint-m) {
--level-n-indent: calc(var(--su-4) + var(--su-1));
--level-1-indent: calc(var(--su-6) + var(--su-1));
}
position: relative;
> summary {
position: static;
display: block;
align-items: center;
transform: initial;
top: 0;
left: var(--indent);
cursor: pointer;
font-size: var(--fs-s);
color: var(--base-60);
margin-bottom: var(--su-4);
margin-left: var(--indent);
padding: var(--su-1) var(--su-2);
font-style: italic;
border-radius: var(--radius);
background: var(--base-0);
.expanded {
display: none;
}
.collapsed {
display: block;
}
.comment-form__field {
&::-webkit-details-marker {
display: none;
}
}
&--deep-0 > summary {
--indent: 0;
}
&--deep-1:not(.root) > summary {
--indent: var(--level-1-indent);
}
&--deep-2:not(.root) > summary {
--indent: var(--level-n-indent);
}
&[open] > summary {
position: absolute;
top: var(--su-8);
background: transparent;
width: auto;
padding: var(--su-1) 0;
margin-left: 0;
@media (min-width: $breakpoint-s) {
// display: none;
}
.expanded {
display: block;
}
.collapsed {
display: none;
}
.expanded,
.collapsed {
opacity: 0.5;
transition: all var(--transition-props);
}
}
&[open]:hover {
> summary {
display: block;
}
.expanded,
.collapsed {
opacity: 1;
}
}
}
.comment {
--comment-gap: var(--su-4);
--dropdown-opacity: 0.5;
--footer-display: flex;
@media (min-width: $breakpoint-m) {
--comment-gap: var(--su-6);
}
&.child {
&:not(.comment--too-deep) {
padding-left: var(--level-n-indent);
}
&.comment--deep-1 {
padding-left: var(--level-1-indent);
}
}
&__dropdown-trigger {
@media (min-width: $breakpoint-s) {
opacity: var(--dropdown-opacity);
}
}
&__dropdown {
margin-left: auto;
margin-right: calc(-1 * var(--su-3));
position: relative;
top: calc(-1 * var(--su-2));
}
&__inner {
display: flex;
align-items: flex-start;
margin-bottom: var(--comment-gap);
&:hover,
&:focus-within {
--dropdown-opacity: 1;
}
}
&__details {
padding-left: var(--su-2);
flex: 1 auto;
min-width: 0;
&.replying {
--footer-display: none;
}
}
&__content {
padding: var(--su-1);
border-radius: var(--radius);
}
&__header {
display: flex;
align-items: center;
padding: var(--su-2) var(--su-3) 0;
font-size: var(--fs-s);
@media (min-width: $breakpoint-m) {
font-size: var(--fs-base);
}
}
&__body {
padding: 0 var(--su-3);
margin: var(--su-2) 0 var(--su-4);
}
&__footer {
// border-top: 1px solid var(--base-10);
padding-top: var(--su-1);
display: var(--footer-display);
align-items: center;
}
&__quality-marker {
border-bottom: 1px solid var(--base-10);
padding: var(--su-3);
padding-top: var(--su-2);
font-weight: var(--fw-medium);
display: flex;
align-items: center;
}
}

View file

@ -1,6 +1,6 @@
<% if articles %>
<section class="crayons-card crayons-card--secondary text-padding mb-4 print-hidden">
<h2 class="crayons-title">Read next</h2>
<h2 class="crayons-subtitle-1">Read next</h2>
<% articles.each do |article| %>
<a href="<%= article.path %>" data-preload-image="<%= cloud_cover_url(article.main_image) %>" class="crayons-link mt-6 block">
<div class="flex items-center">

View file

@ -1,3 +1,5 @@
<a href="/code-of-conduct" class="crayons-link crayons-link--brand">Code of Conduct</a>
<span class="opacity-25 px-2">&bull;</span>
<a href="/report-abuse" class="crayons-link crayons-link--brand">Report abuse</a>
<nav class="fs-s align-center block" aria-label="Conduct controls">
<a href="/code-of-conduct" class="crayons-link crayons-link--secondary">Code of Conduct</a>
<span class="opacity-25 px-2" role="presentation">&bull;</span>
<a href="/report-abuse" class="crayons-link crayons-link--secondary">Report abuse</a>
</nav>

View file

@ -1,23 +1,22 @@
<% cache("whole-comment-area-#{@article.id}-#{@article.last_comment_at}-#{@article.show_comments}", expires_in: 2.hours) do %>
<section id="comments" data-updated-at="<%= Time.current %>" class="text-padding mb-4 border-t-1 border-0 border-solid border-base-10">
<% if @article.show_comments %>
<header class="relative flex justify-between items-center mb-4">
<h3 class="crayons-title">Discussion</h3>
<header class="relative flex justify-between items-center mb-6">
<h3 class="crayons-subtitle-1">Discussion</h3>
<div id="comment-subscription">
<div role="presentation" class="crayons-btn-group">
<span class="crayons-btn crayons-btn--outlined">Subscribe</span>
</div>
</div>
</header>
<div
class="comments-container"
id="comments-container"
data-commentable-id="<%= @article.id %>"
data-commentable-type="Article">
<%= render "/comments/form",
commentable: @article,
commentable_type: "Article" %>
<div class="comment-trees" id="comment-trees-container">
<%= render "/comments/form", commentable: @article, commentable_type: "Article" %>
<div class="comments" id="comment-trees-container">
<% if @article.comments_count > 0 %>
<% Comment.tree_for(@article, @comments_to_show_count).each do |comment, sub_comments| %>
<% cache ["comment_root_cached_tree", comment] do %>
@ -27,6 +26,7 @@
<% end %>
</div>
</div>
<%= render "articles/comments_actions" %>
<% end %>
</section>

View file

@ -1,15 +1,23 @@
<div class="container delete-confirm">
<h4><%= @article.title %></h4>
<h1>Are you sure you want to delete this article?</h1>
<div class="crayons-layout crayons-layout--limited-l gap-0">
<div class="crayons-card crayons-card--secondary text-styles text-styles--secondary text-padding -mb-1 mx-3 m:mx-6 mt-3">
<%= @article.title %>
</div>
<div class="crayons-card text-padding">
<h1 class="crayons-subtitle-1 mb-2">Are you sure you want to delete this article?</h1>
<p class="fs-l mb-4">
You cannot undo this action, perhaps you just want to
<a data-no-instant href="<%= @article.path %>/edit">
<%= @article.published ? "unpublish" : "edit" %>
</a>
instead?
</p>
<h2>
You cannot undo this action, perhaps you just want to
<a data-no-instant href="<%= @article.path %>/edit">
<%= @article.published ? "unpublish" : "edit" %>
</a>
instead?
</h2>
<%= form_tag "/articles/#{@article.id}", method: :delete do %>
<button class="crayons-btn crayons-btn--danger">Delete</button>
<% end %>
<%= form_tag "/articles/#{@article.id}", method: :delete do %>
<button class="crayons-btn crayons-btn--danger">Delete</button>
<a data-no-instant href="<%= @article.path %>/edit" class="crayons-btn crayons-btn--secondary">
<%= @article.published ? "Unpublish" : "Edit" %>
</a>
<a data-no-instant href="<%= @article.path %>" class="crayons-btn crayons-btn--ghost">Dismiss</a>
<% end %>
</div>
</div>

View file

@ -1,20 +1,35 @@
<% if comment && comment.user %>
<% if comment.depth < 3 && !should_be_hidden?(comment, @root_comment) %>
<details open>
<summary><span>&nbsp;</span></summary>
<%= render("comments/comment_proper",
comment: comment,
commentable: commentable,
is_view_root: is_view_root,
is_childless: is_childless,
subtree_html: subtree_html) %>
<% if comment && comment.user && !should_be_hidden?(comment, @root_comment) %>
<% if comment.depth < 3 %>
<details class="comment-wrapper js-comment-wrapper comment-wrapper--deep-<%= comment.depth %> <%= comment_class(comment, is_view_root: is_view_root) %>" open>
<summary>
<span class="<% if comment.depth > 0 %>mx-0<% else %>m:mx-1<% end %> inline-block align-middle">
<%= inline_svg_tag("collapse.svg", aria: true, class: "crayons-icon expanded", title: "Collapse") %>
<%= inline_svg_tag("expand.svg", aria: true, class: "crayons-icon collapsed", title: "Expand") %>
</span>
<span class="js-collapse-comment-content inline-block align-middle"></span>
</summary>
<% end %>
<div
id="comment-node-<%= comment.id %>"
class="
comment single-comment-node
<%= "low-quality-comment" if comment.decorate.low_quality %>
<%= comment_class(comment, is_view_root: is_view_root) %>
comment--deep-<%= comment.depth %>
<%= "comment--too-deep" if comment.depth > 3 %>
"
data-comment-id="<%= comment.id %>"
data-path="<%= commentable.path %>/comments/<%= comment.id_code_generated %>"
data-comment-author-id="<%= comment_user_id_unless_deleted comment %>"
data-content-user-id="<%= comment_user_id_unless_deleted comment %>">
<%= render("comments/comment_proper",
comment: comment,
commentable: commentable,
is_view_root: is_view_root,
is_childless: is_childless,
subtree_html: subtree_html) %>
</div>
<% if comment.depth < 3 %>
</details>
<% elsif !should_be_hidden?(comment, @root_comment) %>
<%= render("comments/comment_proper",
comment: comment,
commentable: commentable,
is_view_root: is_view_root,
is_childless: is_childless,
subtree_html: subtree_html) %>
<% end %>
<% end %>

View file

@ -0,0 +1,9 @@
<% if comment.deleted %>
<span class="shrink-0 crayons-avatar <% if comment.depth == 0 %>m:crayons-avatar--l mt-4 m:mt-3<% else %>mt-4<% end %>">
<%= image_tag(Images::Optimizer.call(SiteConfig.mascot_image_url, width: 32, height: 32, crop: "imagga_scale"), class: "crayons-avatar__image overflow-hidden", alt: "Sloan, the sloth mascot", loading: "lazy") %>
</span>
<% else %>
<a href="<%= URL.user(comment.user) %>" class="shrink-0 crayons-avatar <% if comment.depth == 0 %>m:crayons-avatar--l mt-4 m:mt-3<% else %>mt-4<% end %>">
<img class="crayons-avatar__image" width="32" height="32" src="<%= Images::Profile.call(comment.user.profile_image_url, length: 50) %>" alt="<%= comment.user.username %> profile image" />
</a>
<% end %>

View file

@ -1,7 +1,6 @@
<div class="comment-date">
<a href="<%= decorated_comment.path %>">
<time datetime="<%= decorated_comment.published_timestamp %>">
<%= decorated_comment.readable_publish_date %>
</time>
</a>
</div>
<span class="color-base-30 px-2" role="presentation">&bull;</span>
<a href="<%= URL.comment(decorated_comment) %>" class="comment-date crayons-link crayons-link--secondary fs-s">
<time datetime="<%= decorated_comment.published_timestamp %>">
<%= decorated_comment.readable_publish_date %>
</time>
</a>

View file

@ -0,0 +1,30 @@
<footer class="comment__footer">
<button
class="crayons-btn crayons-btn--ghost crayons-btn--icon-left crayons-btn--s mr-1 reaction-like inline-flex reaction-button"
id="button-for-comment-<%= comment.id %>"
data-comment-id="<%= comment.id %>"
title="heart">
<%= inline_svg_tag("small-heart.svg", aria: true, class: "crayons-icon reaction-icon not-reacted", title: "Favorite heart outline button") %>
<%= inline_svg_tag("small-heart-filled.svg", aria: true, class: "crayons-icon reaction-icon--like reaction-icon reacted", title: "Favorite heart outline button") %>
<span class="reactions-count hidden"></span>
<span class="reactions-label hidden m:inline-block">Like</span>
</button>
<% if comment.depth < 2 || is_childless %>
<a
class="actions crayons-btn crayons-btn--ghost crayons-btn--s crayons-btn--icon-left toggle-reply-form mr-1 inline-flex"
href="#<%= commentable.path %>/comments/new/<%= comment.id_code_generated %>"
data-comment-id="<%= comment.id %>"
data-path="<%= commentable.path %>/comments/<%= comment.id_code_generated %>"
rel="nofollow">
<%= inline_svg_tag("small-comment.svg", aria: true, class: "crayons-icon reaction-icon not-reacted", title: "Comment button") %>
<span class="hidden m:inline-block">Reply</span>
</a>
<% else %>
<span class="fs-s inline-flex items-center fs-italic color-base-50 pl-1">
<%= inline_svg_tag("small-thread.svg", aria: true, class: "crayons-icon", title: "Thread") %>
Thread
</span>
<% end %>
</footer>

View file

@ -0,0 +1,31 @@
<header class="comment__header">
<a href="<%= URL.user(comment.user) %>" class="crayons-link crayons-link--secondary flex items-center fw-medium">
<span class="js-comment-username"><%= comment.user.name %></span>
<% if commentable_author_is_op?(commentable, comment) %>
<span class="crayons-tooltip inline-block spec-op-author -mr-2" data-tooltip="<%= get_ama_or_op_banner(commentable) %>">
<%= inline_svg_tag("small-medal.svg", aria: true, class: "crayons-icon", title: get_ama_or_op_banner(commentable)) %>
</span>
<% end %>
</a>
<%= render "comments/comment_date", decorated_comment: decorated_comment %>
<div class="comment__dropdown">
<button class="dropbtn comment__dropdown-trigger crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon" aria-label="Toggle dropdown menu" aria-haspopup="true">
<%= inline_svg_tag("small-overflow-horizontal.svg", aria: true, class: "crayons-icon pointer-events-none", title: "Dropdown menu") %>
</button>
<ul class="crayons-dropdown p-1 right-1 s:right-0 s:left-auto fs-base dropdown">
<li><a href="<%= URL.comment(comment) %>" class="crayons-link crayons-link--block permalink-copybtn" aria-label="Copy link to <%= comment.user.name %>'s comment" data-no-instant>Copy link</a></li>
<li class="comment-actions hidden" data-user-id="<%= comment.user_id %>" data-action="settings-button" data-path="<%= URL.comment(comment) %>/settings" aria-label="Go to <%= comment.user.name %>'s comment settings"></li>
<% action = comment.hidden_by_commentable_user ? "Unhide" : "Hide" %>
<li class="comment-actions hidden" data-action="hide-button" data-commentable-user-id="<%= commentable.user_id %>" data-user-id="<%= comment.user_id %>">
<a href="#" class="crayons-link crayons-link--block hide-comment" data-hide-type="<%= action.downcase %>" data-comment-id="<%= comment.id %>" aria-label="<%= action %> <%= comment.user.name %>'s comment">
<%= action %>
</a>
</li>
<li class="mod-actions hidden mod-actions-comment-button" data-path="<%= URL.comment(comment) %>/mod" aria-label="Moderate <%= comment.user.name %>'s comment"></li>
<li class="report-abuse-link-wrapper" data-path="/report-abuse?url=<%= URL.comment(comment) %>" aria-label="Report <%= comment.user.name %>'s comment as abusive or violating our code of conduct and/or terms and conditions"></li>
<li class="current-user-actions"></li>
</ul>
</div>
</header>

View file

@ -1,88 +1,39 @@
<div class="comment-hash-marker" id="<%= comment.id_code_generated %>"></div>
<div id="comment-node-<%= comment.id %>"
class="single-comment-node <%= comment_class(comment, is_view_root: is_view_root) %> <%= "flat-node" if comment.depth > 3 %> comment-deep-<%= comment.depth %>" data-comment-id="<%= comment.id %>" data-comment-author-id="<%= comment_user_id_unless_deleted comment %>" data-content-user-id="<%= comment_user_id_unless_deleted comment %>">
<% if comment.deleted %>
<div class="inner-comment">
<div class="body" style="padding-bottom:32px;opacity:0.3;user-select:none;cursor:default">
[deleted]
</div>
</div>
<% else %>
<div class="inner-comment">
<% decorated_comment = comment.decorate %>
<% decorated_comment = comment.decorate %>
<% if decorated_comment.low_quality %>
<div class="low-quality-comment-marker">
<%= image_tag(Images::Optimizer.call(SiteConfig.mascot_image_url, width: 50, height: 50, crop: "imagga_scale"), class: "sloan", alt: "Sloan, the sloth mascot", loading: "lazy") %>
Comment marked as low quality/non-constructive by the community
<a href="/code-of-conduct">View code of conduct</a>
<div class="comment__inner">
<%= render partial: "comments/comment_avatar", locals: { comment: comment } %>
<div class="inner-comment comment__details">
<div class="comment__content crayons-card">
<% if comment.deleted %>
<div class="p-6 align-center opacity-50 fs-s">
<span class="js-comment-username">Comment deleted</span>
</div>
<% else %>
<%= render partial: "comments/comment_quality_marker", locals: {
comment: comment,
decorated_comment: decorated_comment
} %>
<%= render partial: "comments/comment_header", locals: {
comment: comment,
commentable: commentable,
decorated_comment: decorated_comment
} %>
<div class="comment__body text-styles text-styles--secondary body">
<%= comment.safe_processed_html %>
</div>
<% end %>
<% if comment.hidden_by_commentable_user %>
<div class="low-quality-comment-marker">
Comment hidden by post author - thread only visible in this permalink
</div>
<% end %>
<div class="details <%= "low-quality-comment" if decorated_comment.low_quality %>">
<a href="/<%= comment.user.username %>">
<img class="profile-pic" src="<%= Images::Profile.call(comment.user.profile_image_url, length: 50) %>" alt="<%= comment.user.username %> profile image" />
<span class="comment-username">
<span class="comment-username-inner">
<%= comment.user.name %>
</span>
</span>
</a>
<% if comment.user.twitter_username.present? %>
<a href="https://twitter.com/<%= comment.user.twitter_username %>" rel="noopener noreferrer" target="_blank">
<%= image_tag("twitter-logo.svg", class: "icon-img", alt: "twitter logo") %></a>
<% end %>
<% if comment.user.github_username.present? %>
<a href="https://github.com/<%= comment.user.github_username %>" rel="noopener noreferrer" target="_blank">
<%= image_tag("github-logo.svg", class: "icon-img", alt: "github logo") %>
</a>
<% end %>
<% if commentable_author_is_op?(commentable, comment) %>
<span class="op-marker"><%= get_ama_or_op_banner(commentable) %></span>
<% end %>
<%= render "comments/comment_date", decorated_comment: decorated_comment %>
<button class="dropbtn" aria-label="Toggle dropdown menu">
<%= inline_svg_tag("overflow-horizontal.svg", aria: true, class: "dropdown-icon", title: "Dropdown menu") %>
</button>
<div class="dropdown">
<div class="crayons-dropdown p-1 right-1 left-1 s:right-0 s:left-auto fs-base">
<a href="<%= comment.path %>" class="crayons-link crayons-link--block">Permalink</a>
<a href="<%= comment.path %>" class="crayons-link crayons-link--block permalink-copybtn" data-no-instant>Copy Permalink</a>
<span class="comment-actions hidden" data-user-id="<%= comment.user_id %>" data-action="settings-button" data-path="<%= comment.path %>/settings">
</span>
<% action = comment.hidden_by_commentable_user ? "Unhide" : "Hide" %>
<span class="comment-actions hidden" data-action="hide-button" data-commentable-user-id="<%= commentable.user_id %>" data-user-id="<%= comment.user_id %>" style="display: none; width: 100%;">
<a href="#" class="crayons-link crayons-link--block hide-comment" data-hide-type="<%= action.downcase %>" data-comment-id="<%= comment.id %>">
<%= action %>
</a>
</span>
<span class="mod-actions hidden mod-actions-comment-button" data-path="<%= comment.path %>/mod"></span>
<span class="report-abuse-link-wrapper" data-path="/report-abuse?url=<%= comment_url(comment) %>"></span>
</div>
</div>
</div>
<div class="body <%= "low-quality-comment" if decorated_comment.low_quality %>">
<%= comment.safe_processed_html %>
<button class="reaction-button" id="button-for-comment-<%= comment.id %>" data-comment-id="<%= comment.id %>" title="heart">
<%= image_tag("favorite-heart-outline-button.svg", alt: "Favorite heart outline button") %>
<img class="voted-heart" src="<%= asset_path("emoji/emoji-one-heart.png") %>" alt="Favorite heart button" />
</button>
</div>
<div class="actions" data-comment-id="<%= comment.id %>" data-path="<%= commentable.path %>/comments/<%= comment.id_code_generated %>">
<span class="current-user-actions hidden" style="display:none">
</span>
<% if comment.depth < 2 || is_childless %>
<a href="#<%= commentable.path %>/comments/new/<%= comment.id_code_generated %>" class="toggle-reply-form" rel="nofollow">Reply</a>
<% else %>
<a href="#<%= commentable.path %>/comments/new/<%= comment.id_code_generated %>" class="toggle-reply-form thread-indication" rel="nofollow">Thread</a>
<% end %>
</div>
</div>
<% end %>
<%= subtree_html %>
<%= unless comment.deleted
(render partial: "comments/comment_footer", locals: {
comment: comment,
commentable: commentable,
is_childless: is_childless
})
end %>
</div>
</div>
<%= subtree_html %>

View file

@ -0,0 +1,14 @@
<% if decorated_comment.low_quality %>
<div class="low-quality-comment-marker comment__quality-marker">
<%= inline_svg_tag("info.svg", aria: true, class: "crayons-icon mr-2", title: "Info") %>
Comment marked as low quality/non-constructive by the community.
<a href="/code-of-conduct">View code of conduct</a>
</div>
<% end %>
<% if comment.hidden_by_commentable_user %>
<div class="low-quality-comment-marker comment__quality-marker">
<%= inline_svg_tag("info.svg", aria: true, class: "crayons-icon mr-2", title: "Info") %>
Comment hidden by post author - thread only visible in this permalink
</div>
<% end %>

View file

@ -1,4 +1,4 @@
<div id="response-templates-data"></div>
<div id="response-templates-data" class="hidden"></div>
<%= javascript_packs_with_chunks_tag "validateFileInputs", "responseTemplates", defer: true %>
<% if @comment.errors.any? %>
@ -28,8 +28,8 @@
<%= f.hidden_field :parent_id, value: @parent_comment.id if @parent_comment %>
<% end %>
<span class="crayons-avatar crayons-avatar--l mr-2 shrink-0">
<img src="<%= Images::Optimizer.call(SiteConfig.logo_png, width: 256) %>" width="32" height="32" alt="pic" class="crayons-avatar__image" id="comment-primary-user-profile--avatar">
<span class="crayons-avatar m:crayons-avatar--l mr-2 shrink-0">
<img src="<%= Images::Optimizer.call(SiteConfig.logo_png, width: 256) %>" width="32" height="32" alt="pic" class="crayons-avatar__image overflow-hidden" id="comment-primary-user-profile--avatar">
</span>
<div class="comment-form__inner">
<div class="comment-form__field">

View file

@ -1,12 +1,18 @@
<div class="container delete-confirm">
<h4><%= truncate(sanitize(@comment.processed_html, tags: []), length: 80) %></h4>
<h1>Are you sure you want to delete this comment?</h1>
<h2>You cannot undo this action, perhaps you just want to
<a data-no-instant href="<%= @comment.path %>/edit">EDIT</a> instead?</h2>
<%= form_tag "/comments/#{@comment.id}", method: :delete do %>
<button class="crayons-btn crayons-btn--danger">Delete</button>
<% end %>
<div class="crayons-layout crayons-layout--limited-l gap-0">
<div class="crayons-card crayons-card--secondary text-styles text-styles--secondary text-padding -mb-1 mx-3 m:mx-6 mt-3">
<%= truncate(sanitize(@comment.processed_html, tags: []), length: 200) %>
</div>
<div class="crayons-card text-padding">
<h1 class="crayons-subtitle-1 mb-2">Are you sure you want to delete this comment?</h1>
<p class="fs-l mb-4">
You cannot undo this action, perhaps you just want to
<a data-no-instant href="<%= @comment.path %>/edit">Edit</a> instead?
</p>
<%= form_tag "/comments/#{@comment.id}", method: :delete do %>
<button class="crayons-btn crayons-btn--danger">Delete</button>
<a data-no-instant href="<%= @comment.path %>/edit" class="crayons-btn crayons-btn--secondary">Edit</a>
<a data-no-instant href="<%= @comment.path %>" class="crayons-btn crayons-btn--ghost">Dismiss</a>
<% end %>
</div>
</div>

View file

@ -1,4 +1,3 @@
<div class="single-comment-header"></div>
<div class="comments-container comments-dedicated-page-container">
<h3>Comment from a deleted article or podcast</h3>
<div class="single-comment-node root">

View file

@ -1,10 +1,12 @@
<div
class="comments-container comments-dedicated-page-container"
id="comments-container"
data-commentable-id="<%= @comment.commentable_id %>"
data-commentable-type="<%= @comment.commentable_type %>">
<h1 class="mb-6">Editing comment</h1>
<%= render "form",
commentable: @commentable,
commentable_type: @comment.commentable_type %>
<div class="crayons-layout crayons-layout--limited-l gap-0">
<div
class="comments-container crayons-card text-padding"
id="comments-container"
data-commentable-id="<%= @comment.commentable_id %>"
data-commentable-type="<%= @comment.commentable_type %>">
<h1 class="crayons-title mb-6">Editing comment</h1>
<%= render "form",
commentable: @commentable,
commentable_type: @comment.commentable_type %>
</div>
</div>

View file

@ -37,96 +37,113 @@
<% end %>
<% end %>
<% if @commentable.class.name == "Article" %>
<%= render "shared/payment_pointer", user: @commentable.user %>
<div id="comment-article-indicator" data-article-id="<%= @commentable.id %>">
<% end %>
<% if @root_comment %>
<div class="single-comment-header"></div>
<% else %>
<div class="article-header">
<a href="<%= @commentable.path %>" class="header-link">
<% if @commentable.respond_to?(:main_image) && @commentable.main_image.present? %>
<div class="picture" style="background-image:url(<%= cloud_cover_url(@commentable.main_image) %>)"></div>
<% else %>
<div class="blank-comment-space"></div>
<% end %>
<h3 id="comments-header"><%= @commentable.title %></h3>
<h4>
<%= @commentable.is_a?(PodcastEpisode) ? @commentable.podcast.name : @commentable.user.name %>
<span class="published-at"><%= "on #{@commentable.published_at.strftime('%B %d, %Y')}" if @commentable.published_at %></span>
</h4>
</a>
<% if @commentable.processed_html.present? %>
<% if @commentable.processed_html.size < 350 %>
<div class="body">
<%= sanitize_rendered_markdown(@commentable.processed_html) %>
</div>
<% else %>
<div class="body">
<%= truncate(sanitize(@commentable.processed_html, tags: []), length: 150) %>
<a href="<%= @commentable.path %>"><span class="read-more">[Read Full]</span></a>
</div>
<% end %>
<% end %>
</div>
<% end %>
<div class="comments-container comments-dedicated-page-container"
id="comments-container"
data-commentable-id="<%= @commentable.id %>"
data-commentable-type="<%= @commentable.class.name %>">
<% if @root_comment %>
<div id="response-templates-data"></div>
<%= javascript_packs_with_chunks_tag "responseTemplates", "fullScreenModeControl", defer: true %>
<div class="top-level-actions">
<h3>re: <%= @commentable.title %> <a href="<%= @commentable.path %>">VIEW POST</a></h3>
<span class="comment-action-buttons">
<% unless @root_comment.is_root? %>
<a href="<%= @root_comment.root.path %>">TOP OF THREAD</a>
<% end %>
<a href="<%= @commentable.path %><%= user_signed_in? ? "/" : "#" %>comments">FULL DISCUSSION</a>
</span>
</div>
<% else %>
<%= render "form",
commentable: @commentable,
commentable_type: "Article" %>
<div class="crayons-layout crayons-layout--limited-l gap-0">
<% if @commentable.class.name == "Article" %>
<%= render "shared/payment_pointer", user: @commentable.user %>
<span id="comment-article-indicator" data-article-id="<%= @commentable.id %>"></span>
<% end %>
<div class="comment-trees" id="comment-trees-container">
<% if @root_comment.present? %>
<div class="root-comment">
<% if @root_comment.depth > 0 && parent = @root_comment.parent %>
<a href="<%= parent.path %>" class="comment-parent-link">
re: <%= parent.title(150) %>
</a>
<% end %>
<% cache ["comment_root-view-root_#{user_signed_in?}", @root_comment] do %>
<%= tree_for(@root_comment, @root_comment.subtree.includes(:user).arrange[@root_comment], @commentable) %>
<% end %>
</div>
<% else %>
<% Comment.tree_for(@commentable).each do |comment, sub_comments| %>
<% cache ["comment_root_#{user_signed_in?}", comment] do %>
<%= tree_for(comment, sub_comments, @commentable) %>
<% unless @root_comment %>
<article class="crayons-article crayons-card crayons-card--secondary s:mx-2 m:mx-4 -mb-1 z-0">
<header class="crayons-article__header">
<% if @commentable.respond_to?(:main_image) && @commentable.main_image.present? %>
<div class="crayons-article__cover">
<img src="<%= cloud_cover_url(@commentable.main_image) %>" width="1000" height="420" style="background-color:<%= @commentable.main_image_background_hex_color %>;" class="crayons-article__cover__image" alt="Cover image for <%= sanitize_and_decode @commentable.title %>">
</div>
<% end %>
<div class="crayons-article__header__meta">
<h1 class="fs-2xl s:fs-3xl l:fs-4xl fw-bold s:fw-heavy lh-tight mb-1">
<%= @commentable.title %>
</h1>
<p class="color-base-60">
<%= @commentable.is_a?(PodcastEpisode) ? @commentable.podcast.name : @commentable.user.name %>
<%= "on #{@commentable.published_at.strftime('%B %d, %Y')}" if @commentable.published_at %></span>
</p>
</div>
</header>
<% if @commentable.processed_html.present? %>
<div class="crayons-article__main">
<div class="crayons-article__body text-styles -mt-1 m:-mt-2">
<% if @commentable.processed_html.size < 350 %>
<%= sanitize_rendered_markdown(@commentable.processed_html) %>
<% else %>
<%= truncate(sanitize(@commentable.processed_html, tags: []), length: 150) %>
<div class="pt-6">
<a class="crayons-btn crayons-btn--outlined" href="<%= @commentable.path %>">Read full post</a>
</div>
<% end %>
</div>
</div>
<% end %>
<% end %>
</div>
<center style="margin:30px 0px 22px;">
<% if @commentable.any_comments_hidden %>
<div class="fs-s color-base-60">
<p>
Some comments have been hidden by the post's author - <a href="/faq#what-does-comment-hidden-by-post-author-mean">find out more</a>
</p>
</article>
<% else %>
<div id="response-templates-data" class="hidden"></div>
<%= javascript_packs_with_chunks_tag "responseTemplates", defer: true %>
<header class="p-2 pb-4 m:p-6 m:pb-6 crayons-card crayons-card--secondary s:mx-2 m:mx-4 -mb-1 z-0">
<h1 class="crayons-subtitle-1 mb-4">
<span class="fw-normal color-base-60">Discussion on: </span>
<%= @commentable.title %>
</h1>
<div class="flex">
<a class="crayons-btn crayons-btn--outlined mr-2" href="<%= @commentable.path %>">View post</a>
<a class="crayons-btn crayons-btn--outlined mr-2" href="<%= @commentable.path %><%= user_signed_in? ? "/" : "#" %>comments">Full discussion</a>
</div>
</header>
<% end %>
<% if @root_comment.present? && (@root_comment.depth > 0 && parent = @root_comment.parent) %>
<div class="p-2 pb-4 m:p-6 m:pb-6 crayons-card crayons-card--secondary s:mx-1 m:mx-2 -mb-2 z-0">
<h2 class="crayons-subtitle-2 pb-2">
<a href="<%= parent.path %>" class="crayons-link">
<span class="fw-normal opacity-75">Replies for:</span> <%= parent.title(150) %>
</a>
</h2>
</div>
<% end %>
<section
class="crayons-card text-padding min-w-0 z-elevate"
id="comments-container"
data-commentable-id="<%= @commentable.id %>"
data-commentable-type="<%= @commentable.class.name %>">
<% unless @root_comment %>
<%= render "form",
commentable: @commentable,
commentable_type: "Article" %>
<% end %>
<div class="comments" id="comment-trees-container">
<% if @root_comment.present? %>
<% cache ["comment_root-view-root_#{user_signed_in?}", @root_comment] do %>
<%= tree_for(@root_comment, @root_comment.subtree.includes(:user).arrange[@root_comment], @commentable) %>
<% end %>
<% else %>
<% Comment.tree_for(@commentable).each do |comment, sub_comments| %>
<% cache ["comment_root_#{user_signed_in?}", comment] do %>
<%= tree_for(comment, sub_comments, @commentable) %>
<% end %>
<% end %>
<% end %>
</div>
<% if @commentable.any_comments_hidden %>
<p class="mb-2 pt-6 fs-s align-center color-base-60">
Some comments have been hidden by the post's author - <a href="/faq#what-does-comment-hidden-by-post-author-mean">find out more</a>
</p>
<% end %>
</section>
<div class="py-6">
<%= render "articles/conduct_and_abuse_actions", page: "comments_index" %>
</center>
</div>
<% if has_vid?(@commentable) %>
<%= render "articles/fitvids" %>
<% end %>
</div>
<% if has_vid?(@commentable) %>
<%= render "articles/fitvids" %>
<% end %>
<div class="fullscreen-code js-fullscreen-code"></div>

View file

@ -1,47 +1,36 @@
<% title "Comment Settings" %>
<div class="crayons-layout crayons-layout--limited">
<div class="crayons-layout crayons-layout--limited-m gap-0">
<div class="crayons-card text-padding">
<h1 class="pb-4">
Comment Settings
</h1>
<h1 class="crayons-title mb-4">Comment Settings</h1>
<div class="comments-settings__row">
<% if @notification_subscription.persisted? %>
<div class="grid">
<h4 class="comments-settings__row--title">
Showing thread notifications
</h4>
<p class="comments-settings__row--subtitle">
You will receive all notifications for this comment and any of your comments in this chain.
</p>
</div>
<% else %>
<div class="grid">
<h4 class="comments-settings__row--title">
Muting thread notifications
</h4>
<p class="comments-settings__row--subtitle">
All notifications for this comment and any of your comments in the thread are currently muted.
</p>
</div>
<div class="flex">
<div class="pr-4 m:pr-8 flex-1">
<% if @notification_subscription.persisted? %>
<p class="fw-bold">Showing thread notifications</p>
<p class="fs-s color-base-60">You will receive all notifications for this comment and any of your comments in this chain.</p>
<% else %>
<p class="fw-bold">Muting thread notifications</p>
<p class="fs-s color-base-60">All notifications for this comment and any of your comments in the thread are currently muted.</p>
<% end %>
</div>
<%= form_for(@notification_subscription, url: "/notification_subscriptions/Comment/#{@comment.id}", method: "post", html: { class: "mute-form shrink-0" }) do |f| %>
<input type="hidden" name="config" value="<%= @notification_subscription.persisted? ? "not_subscribed" : "all_comments" %>">
<%= f.submit @notification_subscription.persisted? ? "Mute notifications" : "Unmute notifications", class: "crayons-btn crayons-btn--secondary #{@notification_subscription.persisted? ? 'mute' : 'unmute'}" %>
<% end %>
<%= form_for(@notification_subscription, url: "/notification_subscriptions/Comment/#{@comment.id}", method: "post", html: { class: "mute-form" }) do |f| %>
<input type="hidden" name="config" value="<%= @notification_subscription.persisted? ? "not_subscribed" : "all_comments" %>">
<%= f.submit @notification_subscription.persisted? ? "Mute notifications" : "Unmute notifications", class: "crayons-btn #{@notification_subscription.persisted? ? 'mute' : 'unmute'}" %>
<% end %>
</div>
</div>
<% if NotificationSubscription.where(user_id: current_user, notifiable_type: "Article", notifiable_id: @comment.commentable_id, config: "all_comments").any? %>
<p>
You are still subscribed to all comments in the broader thread, which will mean you will still receive notifications for replies to this comment.
</p>
<%= form_for(@notification_subscription, url: "/notification_subscriptions/Article/#{@comment.commentable_id}", method: "post", html: { class: "mute-form" }) do |f| %>
<input type="hidden" name="config" value="not_subscribed">
<%= f.submit "Unsubscribe from parent post", class: "crayons-btn crayons-btn--secondary" %>
<% end %>
<div class="flex pt-6">
<div class="pr-4 m:pr-8 flex-1">
<p class="fw-bold">Subscription</p>
<p class="fs-s color-base-60">You are still subscribed to all comments in the broader thread, which will mean you will still receive notifications for replies to this comment.</p>
</div>
<%= form_for(@notification_subscription, url: "/notification_subscriptions/Article/#{@comment.commentable_id}", method: "post", html: { class: "mute-form" }) do |f| %>
<input type="hidden" name="config" value="not_subscribed">
<%= f.submit "Unsubscribe from parent post", class: "crayons-btn crayons-btn--secondary" %>
<% end %>
</div>
<% end %>
</div>
</div>

View file

@ -1,18 +1,24 @@
<%= javascript_include_tag "application" %>
<div class="container delete-confirm">
<h4><%= @listing.title %></h4>
<h1>Are you sure you want to delete this listing?</h1>
<div class="crayons-layout crayons-layout--limited-l gap-0">
<div class="crayons-card crayons-card--secondary text-styles text-styles--secondary text-padding -mb-1 mx-3 m:mx-6 mt-3">
<%= @listing.title %>
</div>
<div class="crayons-card text-padding">
<h1 class="crayons-subtitle-1 mb-2">Are you sure you want to delete this listing?</h1>
<p class="fs-l mb-4">
You cannot undo this action, perhaps you just want to
<a data-no-instant href="/listings/<%= @listing.id %>/edit">
<%= @listing.published ? "unpublish" : "edit" %>
</a>
instead?
</p>
<h2>
You cannot undo this action, perhaps you just want to
<a data-no-instant href="/listings/<%= @listing.id %>/edit">
<%= @listing.published ? "unpublish" : "edit" %>
</a>
instead?
</h2>
<%= form_tag "/listings/#{@listing.id}", method: :delete do %>
<button class="crayons-btn crayons-btn--danger">Delete</button>
<% end %>
<%= form_tag "/listings/#{@listing.id}", method: :delete do %>
<button class="crayons-btn crayons-btn--danger">Delete</button>
<a data-no-instant href="/listings/<%= @listing.id %>/edit" class="crayons-btn crayons-btn--secondary">
<%= @article.published ? "Unpublish" : "Edit" %>
</a>
<% end %>
</div>
</div>

View file

@ -76,7 +76,7 @@
<%= f.hidden_field :commentable_type, value: json_data["comment"]["commentable"]["class"]["name"] %>
<%= f.hidden_field :parent_id, value: json_data["comment"]["id"] %>
<%= f.text_area :body_markdown, id: "comment-textarea-for-#{json_data['comment']['id']}", class: "crayons-textfield mb-2", placeholder: "Reply..." %>
<%= f.text_area :body_markdown, id: "comment-textarea-for-#{json_data['comment']['id']}", class: "crayons-textfield mb-2", placeholder: "Reply...", 'aria-label': "Reply to a comment..." %>
<div class="actions">
<button type="submit" class="crayons-btn comment-action-button" onclick="validateField(event)">Submit</button>
</div>

View file

@ -73,34 +73,37 @@
</p>
</center>
<% end %>
<div class="container">
<div class="body">
<% if @episode.podcast.feed_url.include?("soundcloud") %>
<%= simple_format((@episode.processed_html || @episode.body), sanitize: true) %>
<% else %>
<%= sanitize (@episode.processed_html || ""),
tags: %w[strong br em a table tbody thead tfoot th tr td col colgroup del p h1 h2 h3 h4 h5 h6 blockquote time div span i em u b ul ol li dd dl dt q code pre img sup cite center small],
attributes: %w[href strong em class ref rel src title alt colspan height width size rowspan span value start data-conversation data-lang id] %>
<% end %>
<p style="font-size:16px;">
<a href="<%= @episode.website_url %>">Episode source</a>
</p>
</div>
<div
class="comments-container"
id="comments-container"
data-commentable-id="<%= @episode.id %>"
data-commentable-type="PodcastEpisode">
<%= render "/comments/form",
commentable: @episode,
commentable_type: "PodcastEpisode" %>
<div class="comment-trees" id="comment-trees-container">
<% Comment.tree_for(@episode, 12).each do |comment, sub_comments| %>
<% cache ["comment_root_#{user_signed_in?}", comment] do %>
<%= tree_for(comment, sub_comments, @episode) %>
<% end %>
<div class="crayons-layout crayons-layout--limited-l gap-0">
<div class="crayons-card">
<div class="text-padding text-styles">
<% if @episode.podcast.feed_url.include?("soundcloud") %>
<%= simple_format((@episode.processed_html || @episode.body), sanitize: true) %>
<% else %>
<%= sanitize (@episode.processed_html || ""),
tags: %w[strong br em a table tbody thead tfoot th tr td col colgroup del p h1 h2 h3 h4 h5 h6 blockquote time div span i em u b ul ol li dd dl dt q code pre img sup cite center small],
attributes: %w[href strong em class ref rel src title alt colspan height width size rowspan span value start data-conversation data-lang id] %>
<% end %>
<p><a href="<%= @episode.website_url %>">Episode source</a></p>
</div>
<section
class="comments-container text-padding"
id="comments-container"
data-commentable-id="<%= @episode.id %>"
data-commentable-type="PodcastEpisode">
<%= render "/comments/form",
commentable: @episode,
commentable_type: "PodcastEpisode" %>
<div class="comment-trees" id="comment-trees-container">
<% Comment.tree_for(@episode, 12).each do |comment, sub_comments| %>
<% cache ["comment_root_#{user_signed_in?}", comment] do %>
<%= tree_for(comment, sub_comments, @episode) %>
<% end %>
<% end %>
</div>
</section>
</div>
</div>
</div>

View file

@ -21,7 +21,7 @@ RSpec.describe "Comments", type: :request do
it "displays full discussion text" do
get comment.path
expect(response.body).to include("FULL DISCUSSION")
expect(response.body).to include("Full discussion")
end
it "renders user payment pointer if set" do
@ -37,11 +37,6 @@ RSpec.describe "Comments", type: :request do
end
context "when the comment is a root" do
it "does not display top of thread button" do
get comment.path
expect(response.body).not_to include("TOP OF THREAD")
end
it "displays the comment hidden message if the comment is hidden" do
comment.update(hidden_by_commentable_user: true)
get comment.path
@ -61,7 +56,6 @@ RSpec.describe "Comments", type: :request do
it "displays proper button and text for child comment" do
get child.path
expect(response.body).to include("TOP OF THREAD")
expect(response.body).to include(CGI.escapeHTML(comment.title(150)))
expect(response.body).to include(child.processed_html)
end

View file

@ -28,6 +28,7 @@ RSpec.describe "Editing A Comment", type: :system, js: true do
visit article.path.to_s
wait_for_javascript
click_on(class: 'comment__dropdown-trigger')
click_link("Edit")
assert_updated
end
@ -41,6 +42,7 @@ RSpec.describe "Editing A Comment", type: :system, js: true do
wait_for_javascript
click_on(class: 'comment__dropdown-trigger')
click_link("Edit")
assert_updated
end

View file

@ -22,12 +22,11 @@ RSpec.describe "Visiting article comments", type: :system, js: true do
end
it "displays child comments" do
expect(page).to have_selector(".comment-deep-1", visible: :visible, count: 3)
expect(page).to have_selector(".comment--deep-1", visible: :visible, count: 3)
end
it "displays grandchild comments" do
expect(page).to have_selector("#comment-node-#{grandchild_comment.id}.comment-deep-2", visible: :visible,
count: 1)
expect(page).to have_selector(".comment--deep-2#comment-node-#{grandchild_comment.id}", visible: :visible, count: 1)
end
end
@ -39,12 +38,11 @@ RSpec.describe "Visiting article comments", type: :system, js: true do
end
it "displays child comments" do
expect(page).to have_selector(".comment-deep-1", visible: :visible, count: 2)
expect(page).to have_selector(".comment--deep-1", visible: :visible, count: 2)
end
it "displays grandchild comments" do
expect(page).to have_selector("#comment-node-#{grandchild_comment.id}.comment-deep-2", visible: :visible,
count: 1)
expect(page).to have_selector(".comment--deep-2#comment-node-#{grandchild_comment.id}", visible: :visible, count: 1)
end
end
end

View file

@ -86,8 +86,8 @@ RSpec.describe "User visits podcast show page", type: :system, js: true do
it "sees the comments", js: true do
visit podcast_episode.path.to_s
expect(page).to have_selector(".comment-deep-0#comment-node-#{comment.id}", visible: :visible, count: 1)
expect(page).to have_selector(".comment-deep-1#comment-node-#{comment2.id}", visible: :visible, count: 1)
expect(page).to have_selector(".comment--deep-0#comment-node-#{comment.id}", visible: :visible, count: 1)
expect(page).to have_selector(".comment--deep-1#comment-node-#{comment2.id}", visible: :visible, count: 1)
end
end
end

View file

@ -14,7 +14,7 @@ RSpec.describe "Views an article", type: :system, js: true do
visit "#{article.path}/comments"
expect(page).to have_selector(".single-comment-node", visible: :visible, count: 3)
expect(page).not_to have_selector(".op-marker")
expect(page).not_to have_selector(".spec-op-author")
end
it "shows op marker on author and co-author comments" do
@ -22,21 +22,21 @@ RSpec.describe "Views an article", type: :system, js: true do
create(:comment, user: co_author, commentable: article)
visit "#{article.path}/comments"
expect(page).to have_selector(".op-marker", visible: :visible, text: "Author", count: 2)
expect(page).to have_selector(".spec-op-author", visible: :visible, text: "Author", count: 2)
end
it "shows special op marker on ama articles" do
create(:comment, user: user, commentable: ama_article)
visit "#{ama_article.path}/comments"
expect(page).to have_selector(".op-marker", visible: :visible, text: "Ask Me Anything")
expect(page).to have_selector(".spec-op-author", visible: :visible, text: "Ask Me Anything")
end
it "shows a thread" do
visit "#{article.path}/comments/#{comment.id_code_generated}"
expect(page).to have_selector(".single-comment-node", visible: :visible, count: 2)
expect(page).to have_selector(".comment-deep-0#comment-node-#{comment.id}", visible: :visible, count: 1)
expect(page).to have_selector(".comment-deep-1#comment-node-#{child_comment.id}", visible: :visible, count: 1)
expect(page).to have_selector(".comment--deep-0#comment-node-#{comment.id}", visible: :visible, count: 1)
expect(page).to have_selector(".comment--deep-1#comment-node-#{child_comment.id}", visible: :visible, count: 1)
end
end

View file

@ -15,9 +15,7 @@ RSpec.describe "rendering locals in a partial", type: :view do
subtree_html: ""
expect(rendered).to match(/low-quality-comment-marker/)
.and match(/low-quality-comment/)
.and match(/sloan/)
.and match(%r{Comment marked as low quality/non-constructive by the community})
.and match(%r{Comment marked as low quality/non-constructive by the community.})
expect(rendered).to have_link "View code of conduct", href: "/code-of-conduct"
end
end