Implement basic Chorus comment rendering for blog posts
This commit is contained in:
parent
eed21b9097
commit
d6ada9a222
@ -15,6 +15,10 @@ export default {
|
|||||||
|
|
||||||
forceSsl: env('SERVER_FORCE_SSL', false),
|
forceSsl: env('SERVER_FORCE_SSL', false),
|
||||||
|
|
||||||
|
chorus: {
|
||||||
|
baseUrl: env('CHORUS_BASE_URL'),
|
||||||
|
},
|
||||||
|
|
||||||
session: {
|
session: {
|
||||||
/* The implementation of @extollo/lib.Session that serves as the session backend. */
|
/* The implementation of @extollo/lib.Session that serves as the session backend. */
|
||||||
driver: ORMSession,
|
driver: ORMSession,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {Controller, view, Inject, Injectable, collect, plaintext} from '@extollo/lib'
|
import {Controller, view, Inject, Injectable, collect, plaintext, Config} 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'
|
||||||
@ -13,6 +13,9 @@ export class Blog extends Controller {
|
|||||||
@Inject()
|
@Inject()
|
||||||
protected readonly blog!: BlogService
|
protected readonly blog!: BlogService
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
protected readonly config!: Config
|
||||||
|
|
||||||
public async index() {
|
public async index() {
|
||||||
const home = <Home> this.make(Home)
|
const home = <Home> this.make(Home)
|
||||||
const posts = await this.blog.getAllPosts()
|
const posts = await this.blog.getAllPosts()
|
||||||
@ -37,6 +40,7 @@ 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(),
|
||||||
@ -128,7 +132,11 @@ export class Blog extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getBlogData(): any {
|
public getBlogData(): any {
|
||||||
|
let chorusUrl = String(this.config.get('server.chorus.baseUrl') || '')
|
||||||
|
if ( chorusUrl && !chorusUrl.endsWith('/') ) chorusUrl += '/'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
chorusUrl,
|
||||||
blogUrl: (post: BlogPost): string => this.blog.getUrl(post),
|
blogUrl: (post: BlogPost): string => this.blog.getUrl(post),
|
||||||
blogDate: (date: Date): string => {
|
blogDate: (date: Date): string => {
|
||||||
const year = date.getFullYear()
|
const year = date.getFullYear()
|
||||||
|
106
src/app/resources/assets/chorus.js
Normal file
106
src/app/resources/assets/chorus.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
|
||||||
|
if ( !window.Chorus ) window.Chorus = {}
|
||||||
|
|
||||||
|
Chorus.processThreadData = function(threadData) {
|
||||||
|
threadData.refresh.date = new Date(threadData.refresh.date)
|
||||||
|
for ( const comment of threadData.comments ) {
|
||||||
|
comment.date = new Date(comment.date)
|
||||||
|
}
|
||||||
|
|
||||||
|
threadData.comments = threadData.comments
|
||||||
|
.sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||||
|
|
||||||
|
return threadData
|
||||||
|
};
|
||||||
|
|
||||||
|
Chorus.init = async function(selector) {
|
||||||
|
const el = document.querySelector(selector)
|
||||||
|
if ( !el ) {
|
||||||
|
console.warn('[Chorus] Could not init: could not find element with selector: ' + selector)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseUrl = el.getAttribute('data-chorus-url')
|
||||||
|
if ( !baseUrl ) {
|
||||||
|
console.warn('[Chorus] Could not init: host element is missing data-chorus-url attribute')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ( !baseUrl.endsWith('/') ) {
|
||||||
|
baseUrl += '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
const threadId = el.getAttribute('data-chorus-thread')
|
||||||
|
if ( !threadId ) {
|
||||||
|
console.warn('[Chorus] Could not init: host element is missing data-chorus-thread attribute')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const threadDataUrl = `${baseUrl}threads/${threadId}.json`
|
||||||
|
const response = await fetch(threadDataUrl)
|
||||||
|
|
||||||
|
if ( response.status === 404 ) {
|
||||||
|
// fixme
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !response.ok ) {
|
||||||
|
console.error('[Chorus] Failed to load thread data')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const threadData = Chorus.processThreadData(await response.json())
|
||||||
|
|
||||||
|
Chorus.renderSummary(el, threadData)
|
||||||
|
|
||||||
|
const commentsDiv = document.createElement('div')
|
||||||
|
commentsDiv.classList.add('chorus-comments-list')
|
||||||
|
for ( const comment of threadData.comments ) {
|
||||||
|
Chorus.renderComment(commentsDiv, comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
el.appendChild(commentsDiv)
|
||||||
|
console.log(threadData)
|
||||||
|
};
|
||||||
|
|
||||||
|
Chorus.renderComment = function(commentsDiv, comment) {
|
||||||
|
const commentDiv = document.createElement('div')
|
||||||
|
commentDiv.classList.add('chorus-comment')
|
||||||
|
commentsDiv.appendChild(commentDiv)
|
||||||
|
|
||||||
|
const authorEl = document.createElement('h2')
|
||||||
|
authorEl.classList.add('chorus-byline')
|
||||||
|
authorEl.innerText = comment.user.name
|
||||||
|
authorEl.title = 'User ID: ' + comment.user.mailId + '\nDomain ID: ' + comment.user.domainId
|
||||||
|
commentDiv.appendChild(authorEl)
|
||||||
|
|
||||||
|
const statusEl = document.createElement('p')
|
||||||
|
statusEl.classList.add('chorus-status')
|
||||||
|
statusEl.innerText = comment.date.toLocaleString()
|
||||||
|
commentDiv.appendChild(statusEl)
|
||||||
|
|
||||||
|
if ( comment.subject ) {
|
||||||
|
const subjectEl = document.createElement('h3')
|
||||||
|
subjectEl.innerText = comment.subject
|
||||||
|
commentDiv.appendChild(subjectEl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentEl = document.createElement('p')
|
||||||
|
contentEl.innerHTML = comment.rendered
|
||||||
|
commentDiv.appendChild(contentEl)
|
||||||
|
};
|
||||||
|
|
||||||
|
Chorus.renderSummary = function(el, threadData) {
|
||||||
|
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 = `${threadData.comments.length} comment${threadData.comments.length === 1 ? '' : 's'}`
|
||||||
|
summaryUl.appendChild(countLi)
|
||||||
|
|
||||||
|
const lastRefreshLi = document.createElement('li')
|
||||||
|
lastRefreshLi.innerText = `Last refreshed: ${threadData.refresh.date.toLocaleString()}`
|
||||||
|
summaryUl.appendChild(lastRefreshLi)
|
||||||
|
};
|
@ -893,3 +893,50 @@ section#auth h3 {
|
|||||||
padding: 3px 7px;
|
padding: 3px 7px;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comments-container {
|
||||||
|
margin-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-container > h1 {
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments .chorus-summary ul {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments .chorus-summary ul li:first-of-type {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments .chorus-comment {
|
||||||
|
background-color: var(--c-background-offset);
|
||||||
|
border-left: 3px solid var(--c-hero);
|
||||||
|
padding: 1px 20px;
|
||||||
|
margin: 50px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments .chorus-comment .chorus-byline {
|
||||||
|
font-size: 1.5em;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments .chorus-comment .chorus-status {
|
||||||
|
font-size: 1.0em;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments .chorus-comment > h3 {
|
||||||
|
font-family: "Lora", serif;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px 0 0;
|
||||||
|
color: var(--c-font);
|
||||||
|
background-color: var(--c-font);
|
||||||
|
}
|
||||||
|
@ -29,6 +29,10 @@ block append script
|
|||||||
script(src=asset('highlight/highlight.min.js'))
|
script(src=asset('highlight/highlight.min.js'))
|
||||||
script.
|
script.
|
||||||
hljs.highlightAll();
|
hljs.highlightAll();
|
||||||
|
if chorusUrl && chorusThread
|
||||||
|
script(src=asset('chorus.js'))
|
||||||
|
script.
|
||||||
|
Chorus?.init('#chorus-container')
|
||||||
|
|
||||||
block blog_content
|
block blog_content
|
||||||
h2.post-title #{post.title}
|
h2.post-title #{post.title}
|
||||||
@ -40,3 +44,16 @@ block blog_content
|
|||||||
a.button(href=named('blog')+'/tag/'+tag) ##{tag}
|
a.button(href=named('blog')+'/tag/'+tag) ##{tag}
|
||||||
|
|
||||||
.post-content !{renderedPost}
|
.post-content !{renderedPost}
|
||||||
|
|
||||||
|
if chorusUrl && chorusThread
|
||||||
|
.section-border
|
||||||
|
.section-border-inner-1
|
||||||
|
.section-border-inner-2
|
||||||
|
|
||||||
|
.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 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)
|
||||||
|
Loading…
Reference in New Issue
Block a user