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",
|
"lit": "^3.1.4",
|
||||||
"marked": "^4.2.12",
|
"marked": "^4.2.12",
|
||||||
"marked-footnote": "^1.2.2",
|
"marked-footnote": "^1.2.2",
|
||||||
|
"short-unique-id": "^5.2.0",
|
||||||
"ts-expose-internals": "^4.5.4",
|
"ts-expose-internals": "^4.5.4",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"ts-patch": "^2.0.1",
|
"ts-patch": "^2.0.1",
|
||||||
|
@ -56,6 +56,9 @@ dependencies:
|
|||||||
marked-footnote:
|
marked-footnote:
|
||||||
specifier: ^1.2.2
|
specifier: ^1.2.2
|
||||||
version: 1.2.2(marked@4.2.12)
|
version: 1.2.2(marked@4.2.12)
|
||||||
|
short-unique-id:
|
||||||
|
specifier: ^5.2.0
|
||||||
|
version: 5.2.0
|
||||||
ts-expose-internals:
|
ts-expose-internals:
|
||||||
specifier: ^4.5.4
|
specifier: ^4.5.4
|
||||||
version: 4.8.4
|
version: 4.8.4
|
||||||
@ -3895,6 +3898,11 @@ packages:
|
|||||||
interpret: 1.4.0
|
interpret: 1.4.0
|
||||||
rechoir: 0.6.2
|
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:
|
/signal-exit@3.0.7:
|
||||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||||
|
|
||||||
|
@ -17,6 +17,10 @@ export default {
|
|||||||
|
|
||||||
chorus: {
|
chorus: {
|
||||||
baseUrl: env('CHORUS_BASE_URL'),
|
baseUrl: env('CHORUS_BASE_URL'),
|
||||||
|
thread: {
|
||||||
|
template: env('CHORUS_THREAD_TEMPLATE'),
|
||||||
|
idPrefix: env('CHORUS_ID_PREFIX', 'c.'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
session: {
|
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 {Home} from './Home.controller'
|
||||||
import {Blog as BlogService} from '../../services/Blog.service'
|
import {Blog as BlogService} from '../../services/Blog.service'
|
||||||
import {BlogPost} from '../../services/blog/AbstractBlog.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', {
|
return view('blog:post', {
|
||||||
...home.getThemeCSS(),
|
...home.getThemeCSS(),
|
||||||
...this.getBlogData(),
|
...this.getBlogData(),
|
||||||
|
...this.getChorusData(post),
|
||||||
post,
|
post,
|
||||||
title: post.title,
|
title: post.title,
|
||||||
renderedPost: await this.blog.renderPost(post.slug),
|
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() {
|
public async archive() {
|
||||||
const home = <Home> this.make(Home)
|
const home = <Home> this.make(Home)
|
||||||
const postsByYear = await this.blog.getAllPosts()
|
const postsByYear = await this.blog.getAllPosts()
|
||||||
|
@ -39,7 +39,8 @@ Chorus.init = async function(selector) {
|
|||||||
const response = await fetch(threadDataUrl)
|
const response = await fetch(threadDataUrl)
|
||||||
|
|
||||||
if ( response.status === 404 ) {
|
if ( response.status === 404 ) {
|
||||||
// fixme
|
await Chorus.render404(el, baseUrl)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !response.ok ) {
|
if ( !response.ok ) {
|
||||||
@ -61,6 +62,40 @@ Chorus.init = async function(selector) {
|
|||||||
console.log(threadData)
|
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) {
|
Chorus.renderComment = function(commentsDiv, comment) {
|
||||||
const commentDiv = document.createElement('div')
|
const commentDiv = document.createElement('div')
|
||||||
commentDiv.classList.add('chorus-comment')
|
commentDiv.classList.add('chorus-comment')
|
||||||
|
@ -908,10 +908,13 @@ section#auth h3 {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comments .chorus-summary ul li:first-of-type {
|
.comments .chorus-summary ul li:first-of-type {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comments .chorus-comment {
|
.comments .chorus-comment {
|
||||||
|
@ -45,7 +45,7 @@ block blog_content
|
|||||||
|
|
||||||
.post-content !{renderedPost}
|
.post-content !{renderedPost}
|
||||||
|
|
||||||
if chorusUrl && chorusThread
|
if chorusUrl && chorusThread && chorusAddress
|
||||||
.section-border
|
.section-border
|
||||||
.section-border-inner-1
|
.section-border-inner-1
|
||||||
.section-border-inner-2
|
.section-border-inner-2
|
||||||
@ -53,7 +53,7 @@ block blog_content
|
|||||||
.comments-container
|
.comments-container
|
||||||
h1 Comments
|
h1 Comments
|
||||||
p Thanks for reading! I'd love to hear your thoughts and questions.
|
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.
|
p You can also <a href="mailto:shout@garrettmills.dev">email me</a> directly.
|
||||||
hr
|
hr
|
||||||
div.comments#chorus-container(data-chorus-url=chorusUrl data-chorus-thread=chorusThread)
|
div.comments#chorus-container(data-chorus-url=chorusUrl data-chorus-thread=chorusThread)
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
export interface BlogPostFrontMatter {
|
export interface BlogPostFrontMatter {
|
||||||
title: string
|
title: string
|
||||||
slug: string
|
slug: string
|
||||||
|
shortId?: string
|
||||||
date: Date
|
date: Date
|
||||||
tags: string[]
|
tags: string[]
|
||||||
}
|
}
|
||||||
@ -89,11 +90,37 @@ export abstract class AbstractBlog<TFrontMatter extends BlogPostFrontMatter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.posts = this.posts.sortByDesc('date')
|
this.posts = this.posts.sortByDesc('date')
|
||||||
|
.tap(c => this.computeShortIds(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.posts
|
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>>> {
|
getPost(slug: string): Promise<Maybe<BlogPost<TFrontMatter>>> {
|
||||||
return this.getAllPosts()
|
return this.getAllPosts()
|
||||||
.then(p => p.firstWhere('slug', '=', slug))
|
.then(p => p.firstWhere('slug', '=', slug))
|
||||||
|
Loading…
Reference in New Issue
Block a user