-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
675 additions
and
185 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,5 @@ | ||
<script src="/libs/highlight/highlight.min.js"></script> | ||
<script>hljs.highlightAll();hljs.configure({tabReplace: ' '});</script> | ||
<script> | ||
hljs.highlightAll(); | ||
hljs.configure({ tabReplace: " " }); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
{ | ||
"dependencies": { | ||
"bluesky-comments": "^0.4.0", | ||
"highlight.js": "^11.10.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
@def title = "Bluesky comments sections in Franklin.jl" | ||
@def date = "11/26/2024" | ||
@def tags = ["julia", "BlueSky"] | ||
|
||
@def rss_pubdate = Date(2024, 11, 26) | ||
|
||
## Adding a comments section from BlueSky | ||
|
||
This [post](https://emilyliu.me/blog/comments) shows how you can add Bluesky posts as blog comments in general, and the code was added to an npm package [here](https://www.npmjs.com/package/bluesky-comments). We can steal that code to add it to a Franklin.jl site easily. Download the bsky-comments.js file [here](https://gist.githubusercontent.com/LoueeD/b7dec10b2ea56c825cbb0b3a514720ed/raw/1caceb84ec7612503db3a955a55af4501bcf0150/bsky-comments.js) (or grab my slightly modified one from this website's github repo under posts/bsky-comments.js) and add the following function to your utils.jl file: | ||
|
||
``` | ||
function hfun_add_bsky_comments(post_url::Vector{String}) | ||
post = post_url[1] | ||
html = " | ||
<script src=\"../bsky-comments.js\"></script> | ||
<bsky-comments post=\"$(post)\"></bsky-comments> | ||
" | ||
return html | ||
end | ||
``` | ||
|
||
Now add the following to the end of your post markdown (data-bluesky-uri can be seen if you click "embed post" on any post, amoung other places): | ||
|
||
``` | ||
{{ add_bsky_comments data-bluesky-uri_of_your_post_in_quotes }} | ||
``` | ||
|
||
Which should give you: | ||
|
||
{{ add_bsky_comments "at://did:plc:2h5e6whhbk5vnnerqqoi256k/app.bsky.feed.post/3lbupbkcn4s2n" }} | ||
|
||
--- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
class BskyComments extends HTMLElement { | ||
constructor() { | ||
super(); | ||
this.attachShadow({ mode: "open" }); | ||
this.visibleCount = 3; | ||
this.thread = null; | ||
this.error = null; | ||
} | ||
|
||
connectedCallback() { | ||
const postUri = this.getAttribute("post"); | ||
if (!postUri) { | ||
this.renderError("Post URI is required"); | ||
return; | ||
} | ||
this.loadThread(postUri); | ||
} | ||
|
||
async loadThread(uri) { | ||
try { | ||
const thread = await this.fetchThread(uri); | ||
this.thread = thread; | ||
this.render(); | ||
} catch (err) { | ||
this.renderError("Error loading comments"); | ||
} | ||
} | ||
|
||
async fetchThread(uri) { | ||
if (!uri || typeof uri !== "string") { | ||
throw new Error("Invalid URI: A valid string URI is required."); | ||
} | ||
|
||
const params = new URLSearchParams({ uri }); | ||
const url = `https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?${params.toString()}`; | ||
|
||
try { | ||
const response = await fetch(url, { | ||
method: "GET", | ||
headers: { | ||
Accept: "application/json", | ||
}, | ||
cache: "no-store", | ||
}); | ||
|
||
if (!response.ok) { | ||
const errorText = await response.text(); | ||
console.error("Fetch Error: ", errorText); | ||
throw new Error(`Failed to fetch thread: ${response.statusText}`); | ||
} | ||
|
||
const data = await response.json(); | ||
|
||
if (!data.thread || !data.thread.replies) { | ||
throw new Error("Invalid thread data: Missing expected properties."); | ||
} | ||
|
||
return data.thread; | ||
} catch (error) { | ||
console.error("Error fetching thread:", error.message); | ||
throw error; | ||
} | ||
} | ||
|
||
render() { | ||
if (!this.thread || !this.thread.replies) { | ||
this.renderError("No comments found"); | ||
return; | ||
} | ||
|
||
const sortedReplies = this.thread.replies.sort( | ||
(a, b) => (b.post.likeCount ?? 0) - (a.post.likeCount ?? 0), | ||
); | ||
|
||
const container = document.createElement("div"); | ||
container.innerHTML = ` | ||
<comments> | ||
<p class="reply-info"> | ||
Reply on Bluesky | ||
<a href="https://bsky.app/profile/${this.thread.post?.author?.did}/post/${this.thread.post?.uri.split("/").pop()}" target="_blank" rel="noopener noreferrer"> | ||
here | ||
</a> to join the conversation. | ||
</p> | ||
<div id="comments"></div> | ||
<button id="show-more"> | ||
Show more comments | ||
</button> | ||
</comments> | ||
`; | ||
|
||
const commentsContainer = container.querySelector("#comments"); | ||
sortedReplies.slice(0, this.visibleCount).forEach((reply) => { | ||
commentsContainer.appendChild(this.createCommentElement(reply)); | ||
}); | ||
|
||
const showMoreButton = container.querySelector("#show-more"); | ||
if (this.visibleCount >= sortedReplies.length) { | ||
showMoreButton.style.display = "none"; | ||
} | ||
showMoreButton.addEventListener("click", () => { | ||
this.visibleCount += 5; | ||
this.render(); | ||
}); | ||
|
||
this.shadowRoot.innerHTML = ""; | ||
this.shadowRoot.appendChild(container); | ||
|
||
if (!this.hasAttribute("no-css")) { | ||
this.addStyles(); | ||
} | ||
} | ||
|
||
escapeHTML(htmlString) { | ||
return htmlString | ||
.replace(/&/g, "&") // Escape & | ||
.replace(/</g, "<") // Escape < | ||
.replace(/>/g, ">") // Escape > | ||
.replace(/"/g, """) // Escape " | ||
.replace(/'/g, "'"); // Escape ' | ||
} | ||
|
||
createCommentElement(reply) { | ||
const comment = document.createElement("div"); | ||
comment.classList.add("comment"); | ||
|
||
const author = reply.post.author; | ||
const text = reply.post.record?.text || ""; | ||
|
||
comment.innerHTML = ` | ||
<div class="author"> | ||
<a href="https://bsky.app/profile/${author.did}" target="_blank" rel="noopener noreferrer"> | ||
${author.avatar ? `<img width="22px" src="${author.avatar}" />` : ""} | ||
${author.displayName ?? author.handle} @${author.handle} | ||
</a> | ||
<p class="comment-text">${this.escapeHTML(text)}</p> | ||
<small class="comment-meta"> | ||
${reply.post.likeCount ?? 0} likes • ${reply.post.replyCount ?? 0} replies | ||
</small> | ||
</div> | ||
`; | ||
|
||
if (reply.replies && reply.replies.length > 0) { | ||
const repliesContainer = document.createElement("div"); | ||
repliesContainer.classList.add("replies-container"); | ||
|
||
reply.replies | ||
.sort((a, b) => (b.post.likeCount ?? 0) - (a.post.likeCount ?? 0)) | ||
.forEach((childReply) => { | ||
repliesContainer.appendChild(this.createCommentElement(childReply)); | ||
}); | ||
|
||
comment.appendChild(repliesContainer); | ||
} | ||
|
||
return comment; | ||
} | ||
|
||
renderError(message) { | ||
this.shadowRoot.innerHTML = `<p class="error">${message}</p>`; | ||
} | ||
|
||
addStyles() { | ||
const style = document.createElement("style"); | ||
style.textContent = ` | ||
:host { | ||
--text-color: #1185FE; | ||
--link-color: gray; | ||
--link-hover-color: black; | ||
--comment-meta-color: gray; | ||
--error-color: red; | ||
--reply-border-color: #ccc; | ||
--button-background-color: rgba(0,0,0,0.05); | ||
--button-hover-background-color: rgba(0,0,0,0.1); | ||
--author-avatar-border-radius: 100%; | ||
} | ||
comments { | ||
margin: 0 auto; | ||
padding: 1.2em; | ||
max-width: 720px; | ||
display: block; | ||
background-color: var(--background-color); | ||
color: var(--text-color); | ||
} | ||
.reply-info { | ||
font-size: 14px; | ||
color: var(--text-color); | ||
} | ||
#show-more { | ||
margin-top: 10px; | ||
width: 100%; | ||
padding: 1em; | ||
font: inherit; | ||
box-sizing: border-box; | ||
background: var(--button-background-color); | ||
border-radius: 0.8em; | ||
cursor: pointer; | ||
border: 0; | ||
&:hover { | ||
background: var(--button-hover-background-color); | ||
} | ||
} | ||
.comment { | ||
margin-bottom: 2em; | ||
} | ||
.author { | ||
a { | ||
font-size: 0.9em; | ||
margin-bottom: 0.4em; | ||
display: inline-block; | ||
color: var(--link-color); | ||
&:not(:hover) { | ||
text-decoration: none; | ||
} | ||
&:hover { | ||
color: var(--link-hover-color); | ||
} | ||
img { | ||
margin-right: 0.4em; | ||
border-radius: var(--author-avatar-border-radius); | ||
vertical-align: middle; | ||
} | ||
} | ||
} | ||
.comment-text { | ||
margin: 5px 0; | ||
white-space: pre-line; | ||
} | ||
.comment-meta { | ||
color: var(--comment-meta-color); | ||
display: block; | ||
margin: 1em 0 2em; | ||
} | ||
.replies-container { | ||
border-left: 1px solid var(--reply-border-color); | ||
margin-left: 1.6em; | ||
padding-left: 1.6em; | ||
} | ||
.error { | ||
color: var(--error-color); | ||
} | ||
`; | ||
this.shadowRoot.appendChild(style); | ||
} | ||
} | ||
|
||
customElements.define("bsky-comments", BskyComments); |
Oops, something went wrong.