feat: Initial commit :)
This commit is contained in:
427
_includes/comments.html
Normal file
427
_includes/comments.html
Normal file
@ -0,0 +1,427 @@
|
||||
<!--
|
||||
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/​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`
|
||||
);
|
||||
avatarImg.setAttribute("loading", "lazy");
|
||||
|
||||
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-GB', {
|
||||
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-GB', {
|
||||
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.reblogs_count > 0) {
|
||||
let boosts = document.createElement("span");
|
||||
boosts.className = "boosts";
|
||||
boosts.setAttribute("title", "Boosts");
|
||||
boosts.textContent = status.reblogs_count;
|
||||
|
||||
interactions.appendChild(boosts);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
mediaElement.setAttribute("loading", "lazy");
|
||||
mediaElement.className = "media hover";
|
||||
|
||||
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", "");
|
||||
mediaElement.className = "media hover";
|
||||
|
||||
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);
|
||||
cardImg.setAttribute("loading", "lazy");
|
||||
|
||||
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");
|
||||
img.setAttribute("loading", "lazy");
|
||||
|
||||
picture.appendChild(source);
|
||||
picture.appendChild(img);
|
||||
|
||||
output = output.replace(`:${ emoji.shortcode }:`, picture.outerHTML);
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/'/g, "'")
|
||||
.replace(/"/g, """)
|
||||
;
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
const commentsSection = document.getElementById("comments");
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
loadComments();
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(commentsSection);
|
||||
</script>
|
||||
</section>
|
11
_includes/footer.html
Normal file
11
_includes/footer.html
Normal file
@ -0,0 +1,11 @@
|
||||
<footer class="site-footer">
|
||||
<p title="Last built at {{ site.time }} ">© {{ site.title }}, 2023</p>
|
||||
|
||||
<p><a href="{{ site.sourceurl }}">Website source</a></p>
|
||||
|
||||
<small>
|
||||
<p>
|
||||
Powered by <a href="https://jekyllrb.com">Jekyll</a> and <a href="https://codeberg.org/daudix-UFO/duckquill-source">Duckquill</a>
|
||||
</p>
|
||||
</small>
|
||||
</footer>
|
25
_includes/head.html
Normal file
25
_includes/head.html
Normal file
@ -0,0 +1,25 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="{{ site.primary-color }}"><!-- primary color -->
|
||||
|
||||
<title>{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}</title>
|
||||
|
||||
<link rel="canonical" href="{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}" />
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}" />
|
||||
|
||||
<link rel="stylesheet" href="{{ "/assets/css/style.css" | prepend: site.baseurl }}" />
|
||||
|
||||
<link rel="icon" type="image/gif" href="{{ "/favicon.png" | prepend: site.baseurl }}" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ "/apple-touch-icon.png" | prepend: site.baseurl }}" />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="{{ site.title }}" />
|
||||
<meta property="og:url" content="{{ site.url }}" />
|
||||
<meta property="og:description" content="{{ site.description }}" />
|
||||
<meta property="og:image" content="{{ "/assets/card.png" | prepend: site.baseurl | prepend: site.url }}" />
|
||||
<meta property="og:image:width" content="600" />
|
||||
<meta property="og:image:height" content="400" />
|
||||
<meta property="og:image:alt" content="A card with Duckquill written on it" />
|
||||
</head>
|
19
_includes/navigation.html
Normal file
19
_includes/navigation.html
Normal file
@ -0,0 +1,19 @@
|
||||
<nav class="nav">
|
||||
<div class="nav-container">
|
||||
<a href="{{ site.baseurl }}/">
|
||||
<h2 class="nav-title">{{ site.title }}</h2>
|
||||
</a>
|
||||
<ul>
|
||||
<li><a href="{{ '/' | prepend: relative_url }}">Home</a></li>
|
||||
<li><a href="{{ '/posts' | prepend: site.baseurl }}">Posts</a></li>
|
||||
<li><a href="{{ '/tags' | prepend: site.baseurl }}">Tags</a></li>
|
||||
<li><a href="{{ '/feed.xml' | prepend: site.baseurl }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
||||
<path
|
||||
d="M1.988 1.988V3c.008.547.453.984 1 .988.004-.004.008-.004.012-.004v.028A8.977 8.977 0 0 1 11.988 13a.991.991 0 0 0 1 .984h1V13h-.004c0-.004 0-.004.004-.008C13.984 7.02 9.184 2.148 3.242 2.02A1.004 1.004 0 0 0 3 1.988v-.004zm0 4V7c.008.547.453.984 1 .988.004-.004.008-.004.012-.004V8a4.985 4.985 0 0 1 4.996 4.844 1.002 1.002 0 0 0 .988 1.145c.008-.005.012-.005.016-.005v.004h.984V13H10c0-3.793-3.047-6.898-6.82-6.992 0-.004-.004-.004-.004-.004A.892.892 0 0 0 3 5.988v-.004zm2 4a1.999 1.999 0 1 0-.002 3.998 1.999 1.999 0 0 0 .002-3.998zm0 0" />
|
||||
</svg>
|
||||
Feed
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
Reference in New Issue
Block a user