Bookmarks - load bookmarks page from Outline document instead of file
This commit is contained in:
parent
3c70c9a06e
commit
611b73c3c3
@ -23,6 +23,12 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
outline: {
|
||||
apiUrl: env('OUTLINE_API_URL'),
|
||||
apiKey: env('OUTLINE_API_KEY'),
|
||||
markmarkDocumentId: env('OUTLINE_MM_DOCUMENT_ID'),
|
||||
},
|
||||
|
||||
session: {
|
||||
/* The implementation of @extollo/lib.Session that serves as the session backend. */
|
||||
driver: ORMSession,
|
||||
|
@ -16,49 +16,3 @@
|
||||
- Homemade Pumpkin Pie Filling
|
||||
- https://unsophisticook.com/homemade-pumpkin-pie-filling/
|
||||
|
||||
# Tech Projects
|
||||
|
||||
Tech projects that I found interesting, funny, or wanted to experiment with later.
|
||||
|
||||
- ScratchDB - Open-Source Snowflake on ClickHouse
|
||||
- https://www.scratchdb.com/
|
||||
- https://github.com/scratchdata/ScratchDB
|
||||
- COBOL for GCC Development #lang
|
||||
- https://cobolworx.com/pages/cobforgcc.html
|
||||
|
||||
# Technical Guides
|
||||
|
||||
- EV Code Certificates + Automated Builds for Windows
|
||||
- https://medium.com/@joshualipson/ev-code-certificates-automated-builds-for-windows-6100fb8e8be6
|
||||
|
||||
# Blogs & Posts
|
||||
|
||||
- Dan Luu (2023-11-20) #dev
|
||||
- https://danluu.com/
|
||||
- https://danluu.com/everything-is-broken/
|
||||
- The Case of a Curious SQL Query - Justing Jaffray #dev
|
||||
- https://buttondown.email/jaffray/archive/the-case-of-a-curious-sql-query/
|
||||
- An Aborted Experiment with Server Swift - flak Blog #dev
|
||||
- https://flak.tedunangst.com/post/an-aborted-experiment-with-server-swift
|
||||
- The Grug Brained Developer #dev
|
||||
- https://grugbrain.dev/
|
||||
- Code Rant - The Configuration Complexity Clock #dev
|
||||
- https://mikehadlow.blogspot.com/2012/05/configuration-complexity-clock.html
|
||||
- Mark L. Irons - Patterns for Personal Web Sites #dev #archive
|
||||
- https://web.archive.org/web/20190904131208/http://www.rdrop.com/~half/Creations/Writings/Web.patterns/index.html
|
||||
- https://web.archive.org/web/20190826113439/http://www.rdrop.com/~half/Creations/Writings/Web.patterns/standard.header.and.footer.html
|
||||
- https://web.archive.org/web/20200107155946/http://www.rdrop.com/~half/Creations/Writings/Web.patterns/history.page.html
|
||||
- https://web.archive.org/web/20200107162931/http://www.rdrop.com/~half/Creations/Writings/Web.patterns/index.pages.html
|
||||
- https://web.archive.org/web/20230314204244/http://www.rdrop.com/~half/Creations/Writings/Web.patterns/gift.to.the.community.html
|
||||
- https://web.archive.org/web/20211102185515/http://www.rdrop.com/~half/Creations/Writings/Web.patterns/downloadable.weblet.html
|
||||
- https://web.archive.org/web/20220120050151/http://www.rdrop.com/~half/Creations/Writings/Web.patterns/site.map.html
|
||||
|
||||
# MarkMark Meta
|
||||
|
||||
Links, resources, and tools related to MarkMark itself. ([_What's MarkMark?_](https://garrettmills.dev/markmark))
|
||||
|
||||
- Standard MarkMark (v1.0)
|
||||
- https://garrettmills.dev/markmark
|
||||
- https://garrettmills.dev/markmark/standard
|
||||
- `mark-mark`: a WIP TypeScript MarkMark parser/renderer collection
|
||||
- https://code.garrettmills.dev/garrettmills/www/src/branch/master/src/markmark
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {appPath, Singleton} from '@extollo/lib'
|
||||
import {appPath, Cache, Config, fetch, hasOwnProperty, Inject, Singleton} from '@extollo/lib'
|
||||
import {MarkMark} from '../../markmark/types'
|
||||
import {Parser} from '../../markmark/parser'
|
||||
import {HtmlRenderer} from '../../markmark/html.renderer'
|
||||
@ -6,6 +6,12 @@ import {MarkMarkRenderer} from '../../markmark/markmark.renderer'
|
||||
|
||||
@Singleton()
|
||||
export class MarkMarkService {
|
||||
@Inject()
|
||||
protected readonly config!: Config
|
||||
|
||||
@Inject()
|
||||
protected readonly cache!: Cache
|
||||
|
||||
private cachedLinks?: MarkMark
|
||||
private cachedLinksHTML?: string
|
||||
private cachedLinksMM?: string
|
||||
@ -15,11 +21,66 @@ export class MarkMarkService {
|
||||
return this.cachedLinks
|
||||
}
|
||||
|
||||
const path = appPath('resources', 'markmark', 'links.mark.md')
|
||||
const content = await path.read()
|
||||
let content = await this.cache.fetch('www-markmark-content')
|
||||
if ( !content || this.config.get('server.debug') ) {
|
||||
content = await this.readFromOutline()
|
||||
const expDateEpoch = (new Date).getTime() + (15 * 60 * 1000)
|
||||
await this.cache.put('www-markmark-content', content, new Date(expDateEpoch))
|
||||
}
|
||||
|
||||
return (this.cachedLinks = (new Parser()).parse(content))
|
||||
}
|
||||
|
||||
private async readFromFile(): Promise<string> {
|
||||
const path = appPath('resources', 'markmark', 'links.mark.md')
|
||||
return path.read()
|
||||
}
|
||||
|
||||
private async readFromOutline(): Promise<string> {
|
||||
const apiUrl = this.config.safe('server.outline.apiUrl').string()
|
||||
const apiKey = this.config.safe('server.outline.apiKey').string()
|
||||
const docId = this.config.safe('server.outline.markmarkDocumentId').string()
|
||||
|
||||
const response = await fetch(`${apiUrl}/documents.info`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: docId,
|
||||
}),
|
||||
})
|
||||
|
||||
if ( !response.ok ) {
|
||||
throw new Error('Unable to load bookmarks from Outline: response was not okay')
|
||||
}
|
||||
|
||||
const body = await response.json()
|
||||
if (
|
||||
typeof body === 'object' && body
|
||||
&& hasOwnProperty(body, 'data')
|
||||
&& typeof body.data === 'object' && body.data
|
||||
&& hasOwnProperty(body.data, 'text')
|
||||
&& typeof body.data.text === 'string'
|
||||
) {
|
||||
// the frontmatter gets escaped by Outline, so fix it
|
||||
return body.data.text
|
||||
.replaceAll('\\[//\\]:', '[//]:')
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
if (line.trim()
|
||||
.startsWith('[//]:')) {
|
||||
return line.replaceAll('\\n', '\n')
|
||||
}
|
||||
return line
|
||||
})
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
throw new Error('Unable to load bookmarks from Outline: invalid body content')
|
||||
}
|
||||
|
||||
public async getLinksHTML(): Promise<string> {
|
||||
if ( this.cachedLinksHTML ) {
|
||||
return this.cachedLinksHTML
|
||||
|
@ -29,26 +29,32 @@ export class MarkMarkBlog extends AbstractBlog<BlogPostFrontMatter> {
|
||||
async getAllPosts(): Promise<Collection<BlogPost>> {
|
||||
const mm = await this.mark.getLinks()
|
||||
|
||||
const links = mm.sections
|
||||
.map(s => s.links)
|
||||
.reduce((l, c): Link[] => [...c, ...l], [])
|
||||
const posts: Collection<BlogPost> = collect()
|
||||
|
||||
return collect(links)
|
||||
.filter(l => l.date)
|
||||
.sort((a, b) => b.date!.getTime() - a.date!.getTime())
|
||||
.map(l => ({
|
||||
title: l.title,
|
||||
slug: l.hash,
|
||||
date: l.date!,
|
||||
tags: l.tags,
|
||||
file: l.hash, // not used
|
||||
for ( const section of mm.sections ) {
|
||||
for ( const link of section.links ) {
|
||||
if ( !link.date ) {
|
||||
continue
|
||||
}
|
||||
|
||||
posts.push({
|
||||
title: link.title,
|
||||
slug: link.hash,
|
||||
date: link.date,
|
||||
tags: link.tags,
|
||||
file: link.hash, // not used
|
||||
markdown: (new MarkMarkRenderer()).render({
|
||||
...mm,
|
||||
sections: [{
|
||||
links: [l],
|
||||
...section,
|
||||
links: [link],
|
||||
}],
|
||||
}, false),
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return posts.sort((a, b) => b.date!.getTime() - a.date!.getTime())
|
||||
}
|
||||
|
||||
protected createFeed(lastUpdated: Maybe<Date>): Awaitable<RSSFeed.Feed> {
|
||||
|
@ -105,7 +105,6 @@ export class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
marked.marked.use({ walkTokens })
|
||||
marked.marked.parse(content)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user