Skip to content

Commit

Permalink
basic comments display with threading working
Browse files Browse the repository at this point in the history
  • Loading branch information
traceypooh committed Jul 17, 2024
1 parent 676ebc1 commit e69a8ad
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 37 deletions.
49 changes: 21 additions & 28 deletions js/blogtini.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ async function main() {
/* eg:
deno run -A js/blogtini.js index.html
*/
if (!Deno.args.length) return
const fi = Deno.args[0]
const body = Deno.readTextFileSync(fi)
if (!body.startsWith(HEADLINE)) {
Expand Down Expand Up @@ -621,37 +622,29 @@ function storage_add(post) { // xxx use snippet
}


async function comments_markup(path) {
/**
* @param {string} path
*/
async function comments_get(path) {
let posts_with_comments
try {
posts_with_comments = (await fetcher(`${state.top_dir}comments/index.txt`))?.split('\n')
/* eslint-disable-next-line no-empty */ // deno-lint-ignore no-empty
} catch {}
if (!posts_with_comments?.includes(path)) return null

const comments = (await fetcher(`${state.top_dir}comments/${path}/index.json`))?.filter((e) => Object.keys(e).length)

const refId = 'xxx'

return comments.map((e) => `
<article id="${refId}" class="comment" data-reply-thread="${refId}">
<header>
<img class="comment-avatar circle" src="https://www.gravatar.com/avatar/${e.email}?s=100" alt="${e.name}'s Gravatar">
<div>
<div class="comment-author-container">
<h3 class="comment-author">${e.name}</h3>
<a class="comment-date" href="#${refId}" title="Permalink to this comment">
${'' /* eslint-disable-next-line no-use-before-define */}
<time datetime="${e.date /* xxx 2022-01-23T04:44:06.937Z */}">${datetime(e.date)}</time>
</a>
</div>
<a class="comment-reply-btn" href="#say-something">Reply</a>
</div>
</header>
<div class="comment-content">
${e.body}
</div>
</article>`).join('')
if (!posts_with_comments?.includes(path))
return []

// oldest comments (or oldest in a thread) first
return (await fetcher(`${state.top_dir}comments/${path}/index.json`))?.filter((e) => Object.keys(e).length).sort((a, b) => a.date < b.date).map((e) => {
// delete any unused keys in each comment hashmap
for (const [k, v] of Object.entries(e)) {
if (v === '' || v === undefined || v === null)
delete e[k]
if (!['id', 'name', 'email', 'date', 'website', 'replyID', 'body'].includes(k))
delete e[k]
}
return e
})
}


Expand Down Expand Up @@ -696,7 +689,7 @@ function create_comment_form(entryId, comments) {
<div class="comments-container">
<h2>Comments</h2>
${comments ?? '<p>Nothing yet.</p>'}
${comments && comments.length ? '' : '<p>Nothing yet.</p>'}
</div>
</div>`
Expand Down Expand Up @@ -971,7 +964,7 @@ export {
cssify,
datetime,
markdown_to_html,
comments_markup,
comments_get,
create_comment_form,
share_buttons,
dark_mode,
Expand Down
60 changes: 60 additions & 0 deletions theme/future-imperfect/bt-comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { LitElement, html, css } from 'https://esm.archive.org/lit'
import { cssify, datetime } from '../../js/blogtini.js'
import {
css_post, css_dark, css_footer, css_title, css_buttons,
} from './index.js'


customElements.define('bt-comment', class extends LitElement {
static get properties() {
return {
id: { type: String },
name: { type: String },
email: { type: String },
date: { type: String },
website: { type: String },
replyID: { type: String },
body: { type: String },
}
}

render() {
return html`
<link href="${cssify('theme/future-imperfect/css.css')}" rel="stylesheet" type="text/css"/><!-- xxx -->
<article id="${this.id}" class="comment" style="${this.replID ? 'margin-left:150px' : ''}">
<header>
<img class="comment-avatar circle" src="https://www.gravatar.com/avatar/${this.email}?s=100" alt="${this.name}'s Gravatar">
<div>
<div class="comment-author-container">
<h3 class="comment-author">
${this.website ? html`<a rel="nofollow external" href="${this.website.match(/^https*:\/\//) ? this.website : `https://${this.website}`}">${this.name}</a>` : this.name}
</h3>
<a class="comment-date" href="#${this.id}" title="Permalink to this comment">
${'' /* eslint-disable-next-line no-use-before-define */}
<time datetime="${this.date /* xxx 2022-01-23T04:44:06.937Z */}">${datetime(this.date)}</time>
</a>
</div>
<a class="comment-reply-btn" href="#say-something">Reply</a>
</div>
</header>
<div class="comment-content">
${this.body}
<slot></slot>
</div>
</article>
`
}

static get styles() {
return [
css_post(), // xxxcc figure these out
css_title(),
css_footer(),
css_buttons(),
css_dark(),
]
}
})
62 changes: 53 additions & 9 deletions theme/future-imperfect/bt-post-full.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { LitElement, html } from 'https://esm.archive.org/lit'

import {
url2post, cssify,
markdown_to_html, comments_markup, create_comment_form,
markdown_to_html, comments_get, create_comment_form,
share_buttons,
} from '../../js/blogtini.js'
import {
Expand All @@ -16,21 +16,22 @@ customElements.define('bt-post-full', class extends LitElement {
return {
url: { type: String },
comments_form: { type: String },
comments: { type: Object },
}
}

render() {
const post = url2post(this.url)
const body = markdown_to_html(post.body_raw)

// use a default base in case url is relative (pathname) only
const key = new URL(post.url, 'https://blogtini.com').pathname.replace(/^\/+/, '').replace(/\/+$/, '') // xxx
// console.error({key})

if (post.type === 'post') {
comments_markup(key).then(
(comments_htm) => {
this.comments_form = create_comment_form(post.url, comments_htm)
if (post.type === 'post' && !this.comments_form) {
// use a default base in case url is relative (pathname) only
const key = new URL(post.url, 'https://blogtini.com').pathname.replace(/^\/+/, '').replace(/\/+$/, '') // xxx
// console.error({key})
comments_get(key).then(
(comments) => {
this.comments_form = create_comment_form(post.url, comments)
this.comments = comments
},
)
}
Expand Down Expand Up @@ -81,6 +82,9 @@ customElements.define('bt-post-full', class extends LitElement {


updated() {
if (this.comments && this.comments.length)
this.comments_insert()

// add code highlighting
const codes = this.shadowRoot.querySelectorAll('pre code')
if (codes.length) {
Expand All @@ -90,6 +94,46 @@ customElements.define('bt-post-full', class extends LitElement {
}
}

/**
* Cleverly use DOM to add (potentially nested) comment elements into a div
* -- because threading replies (and replies of replies) gets complicated suuuuper quick
*/
comments_insert() {
// loop over comments, appending each into the right parent, until all are added
// (or no more addable if corrupted / invalid parent pointer)
// eslint-disable-next-line no-empty
while (this.comments.reduce((sum, e) => sum + this.comment_insert(e), 0)) {}

for (const com of this.comments) {
// eslint-disable-next-line no-console
if (com.id) console.error('Comment orphaned', com)
}
}

/**
* Adds a comment into DOM, finding the right parent for threaded comments, etc.
* @param {object} com
* @returns {number} 0 or 1 comments added
*/
comment_insert(com) {
if (!com.id) return 0
const e = document.createElement('bt-comment')
for (const [k, v] of Object.entries(com))
e.setAttribute(k, v) // xxxcc JSON.stringify(v))

const addto = com.replyID
? this.shadowRoot.getElementById(com.replyID)
: this.shadowRoot.querySelector('.comments-container')
if (!addto)
return 0 // *should* never happen -- but cant find parent for this comment's `replyID`

addto.appendChild(e)

// eslint-disable-next-line no-param-reassign
delete com.id
return 1
}


static get styles() {
return [
Expand Down
1 change: 1 addition & 0 deletions theme/future-imperfect/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import './bt-post-mini.js'
import './bt-post-header.js'
import './featured-image.js'
import './post-stats.js'
import './bt-comment.js'


function css_buttons() {
Expand Down

0 comments on commit e69a8ad

Please sign in to comment.