feat: Initial work on adding comments

It is not finished yet but already better than nothing, will finish
after a nap
This commit is contained in:
David Lapshin 2023-08-03 06:59:47 +03:00
parent 3a401b7d32
commit 679aea4cf6
Signed by: daudix
GPG key ID: 93ECF15D3053D81C
6 changed files with 692 additions and 10 deletions

View file

@ -11,6 +11,11 @@ issuesurl: "https://codeberg.org/daudix-UFO/blog-source/issues" # issue tracker
permalink: /:title/
primary-color: "#c061cb" #used in ios theme. further color customization in style.css
comments:
host: mstdn.social
username: Daudix
# Build settings
markdown: kramdown

410
_includes/comments.html Normal file
View file

@ -0,0 +1,410 @@
<!--
Taken from https://github.com/cassidyjames/cassidyjames.github.io/blob/99782788a7e3ba3cc52d6803010873abd1b02b9e/_includes/comments.html
-->
<!--
Inspired by https://codeberg.org/jwildeboer/jwildeboersource/src/commit/45f9750bb53b9f0f6f28399ce4d21785a3bb7d22/_includes/fediverse_comments.html
-->
{% if include.host %}
{% assign host = include.host %}
{% elsif page.comments.host %}
{% assign host = page.comments.host %}
{% else %}
{% assign host = site.comments.host %}
{% endif %}
{% if include.domain %}
{% assign domain = include.domain %}
{% elsif page.comments.domain %}
{% assign domain = page.comments.domain %}
{% elsif site.comments.domain %}
{% assign domain = site.comments.domain %}
{% else %}
{% assign domain = host %}
{% endif %}
{% if include.username %}
{% assign username = include.username %}
{% elsif page.comments.username %}
{% assign username = page.comments.username %}
{% else %}
{% assign username = site.comments.username %}
{% endif %}
{% if include.token %}
{% assign token = include.token %}
{% elsif page.comments.token %}
{% assign token = page.comments.token %}
{% else %}
{% assign token = site.comments.token %}
{% endif %}
{% if include.id %}
{% assign id = include.id %}
{% else %}
{% assign id = page.comments.id %}
{% endif %}
{% if site.comments.verified %}
{% assign verified = site.comments.verified | jsonify %}
{% else %}
{% assign verified = "[]" %}
{% endif %}
<section id="comments" class="article comments">
<h2>Comments</h2>
<p>Comment on this blog post by publicly replying to <a href="https://{{ host }}/@{{ username }}/{{ id }}">this Mastodon post</a> using a Mastodon or other ActivityPub/&ZeroWidthSpace;Fediverse account. Known non-private replies are displayed below.</p>
<div id="comments-wrapper">
<p><small>No known comments, yet. Reply to <a href="https://{{ host }}/@{{ username }}/{{ id }}">this Mastodon post</a> to add your own!</small></p>
<noscript><p>Loading comments relies on JavaScript. Try enabling JavaScript and reloading, or visit <a href="https://{{ host }}/@{{ username }}/{{ id }}">the original post</a> on Mastodon.</p></noscript>
</div>
<script>
loadComments();
function loadComments() {
const HOST = "{{ host }}";
const DOMAIN = "{{ domain }}";
const USERNAME = "{{ username }}";
const TOKEN = "{{ token }}";
const VERIFIED = {{ verified }};
const ID = "{{ id }}";
const SUPPORTED_MEDIA = [
"image",
"gifv",
];
const STATUS_URL = `https://${ HOST }/api/v1/statuses/${ ID }`;
const REQUEST_HEADERS = new Headers();
if(TOKEN != ""){
REQUEST_HEADERS.append("Authorization", "Bearer " + TOKEN);
}
const requestOptions = {
method: "GET",
headers: REQUEST_HEADERS,
mode: "cors",
cache: "default",
};
const STATUS_REQUEST = new Request(STATUS_URL, requestOptions);
const CONTEXT_REQUEST = new Request(STATUS_URL + "/context", requestOptions);
let commentsWrapper = document.getElementById("comments-wrapper");
fetch(STATUS_REQUEST).then((response) => {
return response.json();
}).then((status) => {
fetch(CONTEXT_REQUEST).then((response) => {
return response.json();
}).then((data) => {
let descendants = data['descendants'];
if(
descendants &&
Array.isArray(descendants) &&
descendants.length > 0
) {
commentsWrapper.innerHTML = "";
descendants.unshift(status);
descendants.forEach((status) => {
if( status.account.display_name.length > 0 ) {
status.account.display_name = escapeHtml(status.account.display_name);
status.account.display_name = emojify(
status.account.display_name,
status.account.emojis
);
} else {
status.account.display_name = status.account.username;
};
let instance = "";
if( status.account.acct.includes("@") ) {
instance = status.account.acct.split("@")[1];
} else {
instance = DOMAIN;
}
status.content = emojify(status.content, status.emojis);
let avatarSource = document.createElement("source");
avatarSource.setAttribute("srcset", escapeHtml(status.account.avatar));
avatarSource.setAttribute("media", "(prefers-reduced-motion: no-preference)");
let avatarImg = document.createElement("img");
avatarImg.className = "avatar";
avatarImg.setAttribute("src", escapeHtml(status.account.avatar_static));
avatarImg.setAttribute(
"alt",
`@${ status.account.username }@${ instance } avatar`
);
let avatarPicture = document.createElement("picture");
avatarPicture.appendChild(avatarSource);
avatarPicture.appendChild(avatarImg);
let avatar = document.createElement("a");
avatar.className = "avatar-link";
avatar.setAttribute("href", status.account.url);
avatar.setAttribute("rel", "external nofollow");
avatar.setAttribute(
"title",
`View profile at @${ status.account.username }@${ instance }`
);
avatar.appendChild(avatarPicture);
let instanceBadge = document.createElement("a");
instanceBadge.className = "instance";
instanceBadge.setAttribute("href", status.account.url);
instanceBadge.setAttribute(
"title",
`@${ status.account.username }@${ instance }`
);
instanceBadge.setAttribute("rel", "external nofollow");
instanceBadge.textContent = instance;
let display = document.createElement("span");
display.className = "display";
display.setAttribute("itemprop", "author");
display.setAttribute("itemtype", "http://schema.org/Person");
display.innerHTML = status.account.display_name;
let header = document.createElement("header");
header.className = "author";
header.appendChild(display);
header.appendChild(instanceBadge);
let permalink = document.createElement("a");
permalink.setAttribute("href", status.url);
permalink.setAttribute("itemprop", "url");
permalink.setAttribute("title", `View comment at ${ instance }`);
permalink.setAttribute("rel", "external nofollow");
permalink.textContent = new Date(status.created_at).toLocaleString('en-US', {
dateStyle: "long",
timeStyle: "short",
});
let timestamp = document.createElement("time");
timestamp.setAttribute("datetime", status.created_at);
timestamp.appendChild(permalink);
if(status.edited_at != null) {
timestamp.classList.add("edited");
timestamp.setAttribute(
"title",
"Edited " + new Date(status.edited_at).toLocaleString('en-US', {
dateStyle: "long",
timeStyle: "short",
})
)
};
let main = document.createElement("main");
main.setAttribute("itemprop", "text");
if(status.sensitive == true || status.spoiler_text != "") {
let summary = document.createElement("summary");
if(status.spoiler_text == "") {
status.spoiler_text == "Sensitive";
}
summary.innerHTML = status.spoiler_text;
let spoiler = document.createElement("details");
spoiler.appendChild(summary);
spoiler.innerHTML += status.content;
main.appendChild(spoiler);
} else {
main.innerHTML = status.content;
}
let interactions = document.createElement("footer");
if(status.favourites_count > 0) {
let faves = document.createElement("span");
faves.className = "faves";
faves.setAttribute("title", "Favorites");
faves.textContent = status.favourites_count;
interactions.appendChild(faves);
}
if(status.reblogs_count > 0) {
let boosts = document.createElement("span");
boosts.className = "boosts";
boosts.setAttribute("title", "Boosts");
boosts.textContent = status.reblogs_count;
interactions.appendChild(boosts);
}
let comment = document.createElement("article");
comment.id = `comment-${ status.id }`;
comment.className = "comment";
comment.setAttribute("itemprop", "comment");
comment.setAttribute("itemtype", "http://schema.org/Comment");
comment.appendChild(avatar);
comment.appendChild(header);
comment.appendChild(timestamp);
comment.appendChild(main);
let attachments = status.media_attachments;
if(
attachments &&
Array.isArray(attachments) &&
attachments.length > 0
) {
attachments.forEach((attachment) => {
if( SUPPORTED_MEDIA.includes(attachment.type) ){
let media = document.createElement("a");
media.className = "card";
media.setAttribute("href", attachment.url);
media.setAttribute("rel", "external nofollow");
let mediaElement;
switch(attachment.type){
case "image":
mediaElement = document.createElement("img");
mediaElement.setAttribute("src", attachment.preview_url);
if(attachment.description != null) {
mediaElement.setAttribute("alt", attachment.description);
mediaElement.setAttribute("title", attachment.description);
}
media.appendChild(mediaElement);
break;
case "gifv":
mediaElement = document.createElement("video");
mediaElement.setAttribute("src", attachment.url);
mediaElement.setAttribute("autoplay", "");
mediaElement.setAttribute("playsinline", "");
mediaElement.setAttribute("loop", "");
if(attachment.description != null) {
mediaElement.setAttribute("aria-title", attachment.description);
mediaElement.setAttribute("title", attachment.description);
}
media.appendChild(mediaElement);
break;
}
comment.appendChild(media);
}
});
} else if(
status.card != null &&
status.card.image != null &&
!status.card.url.startsWith("{{ site.url }}")
) {
let cardImg = document.createElement("img");
cardImg.setAttribute("src", status.card.image);
let cardTitle = document.createElement("h5");
cardTitle.innerHTML = status.card.title;
let cardDescription = document.createElement("p");
cardDescription.innerHTML = status.card.description;
let cardCaption = document.createElement("figcaption");
cardCaption.appendChild(cardTitle);
cardCaption.appendChild(cardDescription);
let cardFigure = document.createElement("figure");
cardFigure.appendChild(cardImg);
cardFigure.appendChild(cardCaption);
let card = document.createElement("a");
card.className = "card";
card.setAttribute("href", status.card.url);
card.setAttribute("rel", "external nofollow");
card.appendChild(cardFigure);
comment.appendChild(card);
}
comment.appendChild(interactions);
if(status.account.acct == USERNAME) {
comment.classList.add("op");
avatar.classList.add("op");
avatar.setAttribute(
"title",
"Blog post author; " + avatar.getAttribute("title")
);
instanceBadge.classList.add("op");
instanceBadge.setAttribute(
"title",
"Blog post author: " + instanceBadge.getAttribute("title")
);
} else if( VERIFIED.includes(status.account.acct) ) {
comment.classList.add("verified");
avatar.classList.add("verified");
avatar.setAttribute(
"title",
avatar.getAttribute("title") + " (verified by site owner)"
);
instanceBadge.classList.add("verified");
instanceBadge.setAttribute(
"title",
instanceBadge.getAttribute("title") + " (verified by site owner)"
);
}
commentsWrapper.innerHTML += comment.outerHTML;
});
}
});
});
}
function emojify(input, emojis) {
let output = input;
emojis.forEach(emoji => {
let picture = document.createElement("picture");
let source = document.createElement("source");
source.setAttribute("srcset", escapeHtml(emoji.url));
source.setAttribute("media", "(prefers-reduced-motion: no-preference)");
let img = document.createElement("img");
img.className = "emoji";
img.setAttribute("src", escapeHtml(emoji.static_url));
img.setAttribute("alt", `:${ emoji.shortcode }:`);
img.setAttribute("title", `:${ emoji.shortcode }:`);
img.setAttribute("width", "20");
img.setAttribute("height", "20");
picture.appendChild(source);
picture.appendChild(img);
output = output.replace(`:${ emoji.shortcode }:`, picture.outerHTML);
});
return output;
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/'/g, "&apos;")
.replace(/"/g, "&quot;")
;
}
</script>
</section>

View file

@ -1,9 +1,20 @@
---
layout: default
---
<h1>{{ page.title }}</h1>
<h2>Table of Contents</h2>
{{ content | toc }}
<hr>
{%- if page.comments -%}
{%- if page.comments.id -%}
{%- include comments.html -%}
<hr>
{%- endif -%}
{%- endif -%}
<p class="dialog-buttons">
<a href="{{ site.baseurl }}/" class="inline-button">Go Home</a>
<a href="{{ site.issuesurl }}">File an issue</a>

View file

@ -1,6 +1,7 @@
---
layout: default
---
{{ content }}
{% for tag in site.tags %}
<h3>{{ tag[0] }}</h3>

View file

@ -3,6 +3,8 @@ layout: post
title: "Migration from GitHub to Codeberg"
tags: Codeberg GitHub Woodpecker Migration
toc: true
comments:
id: 110720129252541458
---
## Backstory

273
style.css
View file

@ -58,6 +58,11 @@
--dark5: rgb(0, 0, 0);
--primary-color: var(--purple2); /* Set your project color */
--borders: var(--light3);
--dark-op05: rgba(0, 0, 0, 0.05);
--dark-op07: rgba(0, 0, 0, 0.07);
--dark-op09: rgba(0, 0, 0, 0.09);
--dark-op40: rgba(0, 0, 0, 0.4);
--dark-op50: rgba(0, 0, 0, 0.5);
}
/* Typography */
@ -158,7 +163,7 @@ b {
}
small {
color: rgba(0, 0, 0, 0.55);
color: var(--dark-op50);
}
dl {
@ -201,8 +206,8 @@ img.pixels {
blockquote {
padding: 0 1rem;
margin-left: 0;
color: rgba(0, 0, 0, 0.4);
border-left: .3rem solid rgba(0, 0, 0, 0.07);
color: var(--dark-op40);
border-left: .3rem solid var(--dark-op07);
}
blockquote > :first-child {
@ -214,10 +219,10 @@ blockquote > :last-child {
}
kbd {
background-color: rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.07);
background-color: var(--dark-op05);
border: 1px solid var(--dark-op07);
border-radius: 4px;
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.09);
box-shadow: inset 0 -1px 0 var(--dark-op09);
color: var(--dark4);
display: inline-block;
font-size: 11px;
@ -230,7 +235,7 @@ kbd {
}
kbd:active {
box-shadow: inset 0 0px 0 rgba(0, 0, 0, 0.09);
box-shadow: inset 0 0px 0 var(--dark-op09);
vertical-align: bottom;
filter: contrast(0.2);
}
@ -328,7 +333,7 @@ table th {
table th,
table td {
padding: 0.5rem 1rem;
border: 1px solid rgba(0, 0, 0, 0.05);
border: 1px solid var(--dark-op05);
}
td,
@ -644,7 +649,7 @@ tbody td {
/* Inline Code */
code.highlighter-rouge {
padding: 2px 6px;
background-color: rgba(0, 0, 0, 0.07);
background-color: var(--dark-op07);
}
/* Buttons */
@ -663,6 +668,254 @@ code.highlighter-rouge {
font-size: 90%;
padding: .4rem 1rem;
border-radius: var(--rounded-corner);
background-color: rgba(0, 0, 0, 0.05);
background-color: var(--dark-op05);
color: var(--dark5);
}
/* Comments */
.avatar {
background-position: center;
background-size: cover;
border-radius: 50%;
box-shadow: 0 0 2px var(--dark-op50);
margin: 0;
overflow: hidden;
}
.avatar img {
height: 100%;
width: 100%;
}
section#comments .comment time,
section#comments .comment footer {
text-align: unset;
padding: unset;
font-size: smaller;
opacity: 0.9;
}
section#comments .comment {
display: grid;
column-gap: 1rem;
grid-template-areas: "avatar name" "avatar time" "avatar post" "...... card" "...... interactions";
grid-template-columns: min-content;
justify-items: start;
margin: 2em auto 2em -1em;
padding: 1em;
}
section#comments .comment .avatar-link {
grid-area: avatar;
height: 4rem;
position: relative;
width: 4rem;
}
section#comments .comment .avatar-link .avatar {
height: 100%;
width: 100%;
}
section#comments .comment .avatar-link.op::after {
background-color: var(--purple3);
border-radius: 50%;
bottom: -0.25rem;
color: var(--light2);
content: "✓";
display: block;
font-size: 1.25rem;
font-weight: bold;
height: 1.5rem;
line-height: 1.5rem;
position: absolute;
right: -0.25rem;
text-align: center;
width: 1.5rem;
}
section#comments .comment .author {
align-items: center;
cursor: default;
display: flex;
font-weight: bold;
gap: 0.5em;
grid-area: name;
}
section#comments .comment .author img {
margin: unset;
max-width: unset;
}
section#comments .comment .author .instance {
background-color: var(--light3);
border-radius: 9999px;
color: var(--dark4);
font-size: smaller;
font-weight: normal;
padding: 0.2em 0.8em;
}
section#comments .comment .author .instance:hover {
opacity: 0.8;
text-decoration: none;
}
section#comments .comment .author .instance.op {
background-color: var(--purple2);
color: var(--light2);
}
section#comments .comment .author .instance.op::before {
content: "✓";
font-weight: bold;
margin-inline-end: 0.25em;
margin-inline-start: -0.25em;
}
section#comments .comment time {
grid-area: time;
line-height: 1.5rem;
}
section#comments .comment time.edited::after {
content: " *";
}
section#comments .comment main {
grid-area: post;
justify-self: stretch;
}
section#comments .comment main p:first-child {
margin-top: 0.25em;
}
section#comments .comment main p:last-child {
margin-bottom: 0;
}
section#comments .comment .card,
section#comments .comment .qr,
section#comments .comment .app-badge,
section#comments .comment .products .product,
.products section#comments .comment .product {
color: inherit;
grid-area: card;
max-width: 400px;
}
section#comments .comment .card:hover,
section#comments .comment .qr:hover,
section#comments .comment .app-badge:hover,
section#comments .comment .products .product:hover,
.products section#comments .comment .product:hover {
text-decoration: none;
}
section#comments .comment .card figure,
section#comments .comment .qr figure,
section#comments .comment .app-badge figure,
section#comments .comment .products .product figure,
.products section#comments .comment .product figure {
border-radius: inherit;
overflow: hidden;
}
section#comments .comment .card figure {
background-color: var(--light1);
border-radius: 12px;
margin-left: 0;
margin-right: 0;
}
section#comments .comment .card figure img {
margin: unset;
}
section#comments .comment .card figcaption,
section#comments .comment .qr figcaption,
section#comments .comment .app-badge figcaption,
section#comments .comment .products .product figcaption,
.products section#comments .comment .product figcaption {
display: grid;
gap: 0.5em;
margin: 0;
padding: 1em;
text-align: left;
}
section#comments .comment .card figcaption *,
section#comments .comment .qr figcaption *,
section#comments .comment .app-badge figcaption *,
section#comments .comment .products .product figcaption *,
.products section#comments .comment .product figcaption * {
display: -webkit-box;
line-height: 1.25;
margin: 0;
overflow: hidden;
padding: 0;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
section#comments .comment:first-of-type .card,
section#comments .comment:first-of-type .qr,
section#comments .comment:first-of-type .app-badge,
section#comments .comment:first-of-type .products .product,
.products section#comments .comment:first-of-type .product {
display: none;
}
section#comments .comment footer {
display: flex;
gap: 1.25em;
grid-area: interactions;
margin-top: 0.925rem;
}
section#comments .comment footer .faves {
cursor: default;
}
section#comments .comment footer .faves::before {
color: var(--red2);
content: "❤️";
margin-inline-end: 0.25em;
}
section#comments .comment footer .boosts {
cursor: default;
}
section#comments .comment footer .boosts::before {
color: var(--orange2);
content: "🔁";
margin-inline-end: 0.25em;
}
section#comments .comment .emoji {
display: inline;
height: 1.25em;
vertical-align: middle;
width: 1.25em;
}
section#comments .comment .invisible {
display: none;
}
section#comments .comment .ellipsis::after {
content: "…";
}
section#comments .comment details summary {
background-image: linear-gradient(90deg, transparent, transparent 0.4rem, var(--light1) 0.4rem, var(--light1) calc(100% - 0.4rem), transparent calc(100% - 0.4rem), transparent), repeating-linear-gradient(45deg, var(--dark4), var(--dark4) 0.3rem, var(--purple2) 0.3rem, var(--purple2) 0.6rem);
border-radius: 5px;
color: inherit;
cursor: default;
margin-top: 0.925rem;
padding: 1em;
}