Skip to content

Commit

Permalink
Code improvements for comments
Browse files Browse the repository at this point in the history
  • Loading branch information
flq committed Jan 26, 2025
1 parent 3543326 commit 9837bba
Showing 1 changed file with 89 additions and 155 deletions.
244 changes: 89 additions & 155 deletions src/components/mdx/Comments.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const url = `https://${host}/@${userName}/${mastodonRef}`;
---

<style>

hr {
margin-top: 4rem;
}
Expand All @@ -22,8 +21,10 @@ const url = `https://${host}/@${userName}/${mastodonRef}`;
opacity: 0.9;
}

.reply {
text-align: right;
.actions {
display: flex;
align-items: center;
justify-content: space-between;

a {
font-size: 0.8em;
Expand All @@ -43,8 +44,8 @@ const url = `https://${host}/@${userName}/${mastodonRef}`;
padding: 0.5rem 1rem;
border-radius: 10px;

&:disabled {
opacity: 0.5;
&[data-loaded] {
opacity: 0;
}
}

Expand All @@ -58,8 +59,8 @@ const url = `https://${host}/@${userName}/${mastodonRef}`;
header {
display: grid;
gap: 1rem;
grid-template-columns: auto 1fr auto; /* Avatar, middle content, and time */
grid-template-rows: 1.2rem 1.2rem; /* Two rows */
grid-template-columns: auto 1fr auto;
grid-template-rows: 1.2rem 1.2rem;
grid-template-areas:
"avatar name time"
"avatar instance time";
Expand Down Expand Up @@ -117,10 +118,14 @@ const url = `https://${host}/@${userName}/${mastodonRef}`;
href={url}>post</a
>. Since Mastodon is decentralized, you can use your existing account hosted
by another Mastodon server or compatible platform if you don't have an
account on this one. Known non-private replies are displayed below by pressing the "load comments" button.
account on this one. Known non-private replies are displayed below by
pressing the "load comments" button.
</p>

<button id="load-comment">Load comments</button>
<div class="actions">
<button id="load-comment">Load comments</button>
<a href={url} target="_blank">Reply to this blog post via mastodon</a>
</div>

<div id="comments-container">
<noscript
Expand All @@ -131,14 +136,10 @@ const url = `https://${host}/@${userName}/${mastodonRef}`;
</noscript>
</div>

<p class="reply">
<a href={url} target="_blank">Reply to this blog post via mastodon</a>
</p>

<script>
const formatter = new Intl.DateTimeFormat(navigator.language, {
dateStyle: "short", // Short date format
timeStyle: "short", // Short time format
dateStyle: "short",
timeStyle: "short",
});
const commentSection = document.getElementById("comments")!;
const commentsContainer = document.getElementById("comments-container")!;
Expand All @@ -148,49 +149,12 @@ const url = `https://${host}/@${userName}/${mastodonRef}`;
const host = commentSection.dataset["host"]!;
const userName = commentSection.dataset["user"]!;
const id = commentSection.dataset["id"]!;

function escapeHtml(unsafe: string) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}

function emojify(
input: string,
emojis: { url: string; static_url: string; shortcode: string }[]
) {
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;
}
loadCommentsButton.addEventListener("click", loadComments);

async function loadComments() {
const commentsWrapper = document.getElementById("comments-container")!;
loadCommentsButton.innerHTML = "Loading";

try {
const response = await fetch(
`https://${host}/api/v1/statuses/${id}/context`
Expand All @@ -211,19 +175,21 @@ const url = `https://${host}/@${userName}/${mastodonRef}`;
descendants = [];
}

var parser = new DOMParser();

descendants.forEach((status: any) => {
commentsWrapper.appendChild(createComment(status));
commentsWrapper.appendChild(createComment(status, parser));
});

loadCommentsButton.innerHTML = "Loaded";
loadCommentsButton.dataset["loaded"] = "true";
loadCommentsButton.disabled = true;
} catch (error: any) {
loadCommentsButton.innerHTML = "Loading failed.";
commentsContainer.innerHTML = error.message;
}
}

function createComment(status: any) {
function createComment(status: any, parser: DOMParser) {
if (status.account.display_name.length > 0) {
status.account.display_name = escapeHtml(status.account.display_name);
status.account.display_name = emojify(
Expand All @@ -240,110 +206,78 @@ const url = `https://${host}/@${userName}/${mastodonRef}`;

const isReply = status.in_reply_to_id !== id;

let op = status.account.acct == userName;
let blogOwner = status.account.acct == userName;

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.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.dataset["avatar"] = "true";
avatar.setAttribute("target", "_blank");
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.dataset["instance"] = "true";
instanceBadge.setAttribute("href", status.account.url);
instanceBadge.setAttribute("target", "_blank");
instanceBadge.setAttribute(
"title",
`@${status.account.username}@${instance}`
);
instanceBadge.setAttribute("rel", "external nofollow");
instanceBadge.textContent = instance;

let display = document.createElement("span");
display.innerHTML = status.account.display_name;

let permalink = document.createElement("a");
permalink.setAttribute("href", status.url);
permalink.setAttribute("target", "_blank");
permalink.setAttribute("title", `View comment at ${instance}`);
permalink.setAttribute("rel", "external nofollow");
permalink.textContent = formatter.format(new Date(status.created_at));

let timestamp = document.createElement("time");
timestamp.setAttribute("datetime", status.created_at);
timestamp.appendChild(permalink);

let header = document.createElement("header");
header.className = "author";
header.append(avatar);
header.appendChild(display);
header.appendChild(instanceBadge);
header.appendChild(timestamp);

let main = document.createElement("main");
main.setAttribute("itemprop", "text");
main.innerHTML = status.content;

let comment = document.createElement("article");
comment.id = `comment-${status.id}`;
if (isReply) {
comment.dataset["reply"] = "true";
}
comment.appendChild(header);
comment.appendChild(main);

if (status.favourites_count > 0) {
let interactions = document.createElement("footer");
let faves = document.createElement("a");
faves.className = "faves";
faves.setAttribute("href", `${status.url}/favourites`);
faves.setAttribute("title", `Favorites from ${instance}`);
faves.textContent = status.favourites_count;
interactions.appendChild(faves);
comment.appendChild(interactions);
}
const comment = `
<article id="comment-${status.id}" ${isReply ? "data-reply" : ""} ${blogOwner ? "data-owner" : ""}>
<header>
<a data-avatar target="_blank" href="${status.account.url}" rel="external nofollow" title="View profile at @${status.account.username}@${instance}">
<picture>
<source srcset="${escapeHtml(status.account.avatar)}" media="(prefers-reduced-motion: no-preference)" />
<img src="${escapeHtml(status.account.avatar_static)}" alt="@${status.account.username}@${instance} avatar" />
</picture>
</a>
<span>
${status.account.display_name}
</span>
<a data-instance href="${status.account.url}" target="_blank" title="@${status.account.username}@${instance}" rel="external nofollow">
${instance}
</a>
<time datetime="${status.created_at}">
<a href="${status.url}" target="_blank" title="View comment at ${instance}" rel="external nofollow">
${formatter.format(new Date(status.created_at))}
</a>
</time>
</header>
<main>
${status.content}
</main>
</article>
`;

return parser.parseFromString(comment, "text/html").body
.firstElementChild!;
}

if (op) {
comment.dataset["owner"] = "true";
function escapeHtml(unsafe: string) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}

avatar.setAttribute(
"title",
"Blog post author; " + avatar.getAttribute("title")
);
function emojify(
input: string,
emojis: { url: string; static_url: string; shortcode: string }[]
) {
let output = input;

instanceBadge.setAttribute(
"title",
"Blog post author: " + instanceBadge.getAttribute("title")
);
}
return comment;
}
emojis.forEach((emoji) => {
let picture = document.createElement("picture");

loadCommentsButton.addEventListener("click", loadComments);
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;
}
</script>
</section>

0 comments on commit 9837bba

Please sign in to comment.