Implement deterministic ID generation for blog posts, wire up Chorus thread ID and address logic, Chorus 404 handling
This commit is contained in:
parent
d6ada9a222
commit
92018efa5c
@ -25,6 +25,7 @@
|
||||
"lit": "^3.1.4",
|
||||
"marked": "^4.2.12",
|
||||
"marked-footnote": "^1.2.2",
|
||||
"short-unique-id": "^5.2.0",
|
||||
"ts-expose-internals": "^4.5.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-patch": "^2.0.1",
|
||||
|
@ -56,6 +56,9 @@ dependencies:
|
||||
marked-footnote:
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2(marked@4.2.12)
|
||||
short-unique-id:
|
||||
specifier: ^5.2.0
|
||||
version: 5.2.0
|
||||
ts-expose-internals:
|
||||
specifier: ^4.5.4
|
||||
version: 4.8.4
|
||||
@ -3895,6 +3898,11 @@ packages:
|
||||
interpret: 1.4.0
|
||||
rechoir: 0.6.2
|
||||
|
||||
/short-unique-id@5.2.0:
|
||||
resolution: {integrity: sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/signal-exit@3.0.7:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
|
||||
|
@ -17,6 +17,10 @@ export default {
|
||||
|
||||
chorus: {
|
||||
baseUrl: env('CHORUS_BASE_URL'),
|
||||
thread: {
|
||||
template: env('CHORUS_THREAD_TEMPLATE'),
|
||||
idPrefix: env('CHORUS_ID_PREFIX', 'c.'),
|
||||
},
|
||||
},
|
||||
|
||||
session: {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {Controller, view, Inject, Injectable, collect, plaintext, Config} from '@extollo/lib'
|
||||
import {Controller, view, Inject, Injectable, collect, plaintext, Config, Maybe} from '@extollo/lib'
|
||||
import {Home} from './Home.controller'
|
||||
import {Blog as BlogService} from '../../services/Blog.service'
|
||||
import {BlogPost} from '../../services/blog/AbstractBlog.service'
|
||||
@ -40,16 +40,37 @@ export class Blog extends Controller {
|
||||
})
|
||||
}
|
||||
|
||||
// FIXME: set chorusThread here
|
||||
return view('blog:post', {
|
||||
...home.getThemeCSS(),
|
||||
...this.getBlogData(),
|
||||
...this.getChorusData(post),
|
||||
post,
|
||||
title: post.title,
|
||||
renderedPost: await this.blog.renderPost(post.slug),
|
||||
})
|
||||
}
|
||||
|
||||
private getChorusData(post: BlogPost): {}|{ chorusThread: string, chorusAddress: string } {
|
||||
if ( !post.shortId ) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if ( !this.config.get('server.chorus.baseUrl') ) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const template = this.config.get('server.chorus.thread.template')
|
||||
const idPrefix = this.config.get('server.chorus.thread.idPrefix')
|
||||
if ( !template || !idPrefix ) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const chorusThread = `${idPrefix}blog${post.shortId}`
|
||||
const chorusAddress = String(template).replaceAll('%ID%', chorusThread)
|
||||
|
||||
return { chorusThread, chorusAddress }
|
||||
}
|
||||
|
||||
public async archive() {
|
||||
const home = <Home> this.make(Home)
|
||||
const postsByYear = await this.blog.getAllPosts()
|
||||
|
@ -39,7 +39,8 @@ Chorus.init = async function(selector) {
|
||||
const response = await fetch(threadDataUrl)
|
||||
|
||||
if ( response.status === 404 ) {
|
||||
// fixme
|
||||
await Chorus.render404(el, baseUrl)
|
||||
return
|
||||
}
|
||||
|
||||
if ( !response.ok ) {
|
||||
@ -61,6 +62,40 @@ Chorus.init = async function(selector) {
|
||||
console.log(threadData)
|
||||
};
|
||||
|
||||
Chorus.render404 = async function(el, baseUrl) {
|
||||
let refreshDate = undefined
|
||||
try {
|
||||
const rootDataUrl = `${baseUrl}root.json`
|
||||
const response = await fetch(rootDataUrl)
|
||||
if ( response.ok ) {
|
||||
const rootData = await response.json()
|
||||
if ( rootData && rootData.refresh && rootData.refresh.date ) {
|
||||
const date = new Date(rootData.refresh.date)
|
||||
if ( !isNaN(date.getTime()) ) {
|
||||
refreshDate = date
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Chorus] Error when loading refresh date from root data', e)
|
||||
}
|
||||
|
||||
const summaryDiv = document.createElement('div')
|
||||
summaryDiv.classList.add('chorus-summary')
|
||||
el.appendChild(summaryDiv)
|
||||
|
||||
const summaryUl = document.createElement('ul')
|
||||
summaryDiv.appendChild(summaryUl)
|
||||
|
||||
const countLi = document.createElement('li')
|
||||
countLi.innerText = `There are no comments yet.`
|
||||
summaryUl.appendChild(countLi)
|
||||
|
||||
const lastRefreshLi = document.createElement('li')
|
||||
lastRefreshLi.innerText = `Last refreshed: ${refreshDate.toLocaleString()}`
|
||||
summaryUl.appendChild(lastRefreshLi)
|
||||
};
|
||||
|
||||
Chorus.renderComment = function(commentsDiv, comment) {
|
||||
const commentDiv = document.createElement('div')
|
||||
commentDiv.classList.add('chorus-comment')
|
||||
|
@ -908,10 +908,13 @@ section#auth h3 {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.comments .chorus-summary ul li:first-of-type {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.comments .chorus-comment {
|
||||
|
@ -45,7 +45,7 @@ block blog_content
|
||||
|
||||
.post-content !{renderedPost}
|
||||
|
||||
if chorusUrl && chorusThread
|
||||
if chorusUrl && chorusThread && chorusAddress
|
||||
.section-border
|
||||
.section-border-inner-1
|
||||
.section-border-inner-2
|
||||
@ -53,7 +53,7 @@ block blog_content
|
||||
.comments-container
|
||||
h1 Comments
|
||||
p Thanks for reading! I'd love to hear your thoughts and questions.
|
||||
p My blog uses an email-based comments system: <a href="#">Submit a Comment</a>
|
||||
p My blog uses an email-based comments system: <a href="mailto:#{chorusAddress}">Submit a Comment</a>
|
||||
p You can also <a href="mailto:shout@garrettmills.dev">email me</a> directly.
|
||||
hr
|
||||
div.comments#chorus-container(data-chorus-url=chorusUrl data-chorus-thread=chorusThread)
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
export interface BlogPostFrontMatter {
|
||||
title: string
|
||||
slug: string
|
||||
shortId?: string
|
||||
date: Date
|
||||
tags: string[]
|
||||
}
|
||||
@ -89,11 +90,37 @@ export abstract class AbstractBlog<TFrontMatter extends BlogPostFrontMatter> {
|
||||
}
|
||||
|
||||
this.posts = this.posts.sortByDesc('date')
|
||||
.tap(c => this.computeShortIds(c))
|
||||
}
|
||||
|
||||
return this.posts
|
||||
}
|
||||
|
||||
computeShortIds(posts: Collection<BlogPost<TFrontMatter>>): Collection<BlogPost<TFrontMatter>> {
|
||||
const ShortUniqueId = require('short-unique-id')
|
||||
const uid = new ShortUniqueId({ length: 6, counter: 0, shuffle: false })
|
||||
const usedIds: {[id: string]: boolean} = {}
|
||||
|
||||
posts.sortBy('date')
|
||||
.each(post => {
|
||||
let shortId = uid.sequentialUUID()
|
||||
let tries = 10
|
||||
while ( usedIds[shortId] && tries > 0 ) {
|
||||
shortId = uid.sequentialUUID()
|
||||
tries -= 1
|
||||
}
|
||||
|
||||
if ( usedIds[shortId] ) {
|
||||
throw new Error('Could not recover from collision when generating blog post short ID')
|
||||
}
|
||||
|
||||
usedIds[shortId] = true
|
||||
post.shortId = shortId
|
||||
})
|
||||
|
||||
return posts
|
||||
}
|
||||
|
||||
getPost(slug: string): Promise<Maybe<BlogPost<TFrontMatter>>> {
|
||||
return this.getAllPosts()
|
||||
.then(p => p.firstWhere('slug', '=', slug))
|
||||
|
Loading…
Reference in New Issue
Block a user