diff --git a/src/app/configs/server.config.ts b/src/app/configs/server.config.ts index b94d706..be420cd 100644 --- a/src/app/configs/server.config.ts +++ b/src/app/configs/server.config.ts @@ -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, diff --git a/src/app/resources/markmark/links.mark.md b/src/app/resources/markmark/links.mark.md index 87271c3..08c2ee1 100644 --- a/src/app/resources/markmark/links.mark.md +++ b/src/app/resources/markmark/links.mark.md @@ -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 diff --git a/src/app/services/MarkMark.service.ts b/src/app/services/MarkMark.service.ts index 6f33f4c..4ae16f4 100644 --- a/src/app/services/MarkMark.service.ts +++ b/src/app/services/MarkMark.service.ts @@ -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 { + const path = appPath('resources', 'markmark', 'links.mark.md') + return path.read() + } + + private async readFromOutline(): Promise { + 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 { if ( this.cachedLinksHTML ) { return this.cachedLinksHTML diff --git a/src/app/services/blog/MarkMarkBlog.service.ts b/src/app/services/blog/MarkMarkBlog.service.ts index f721dfb..4424179 100644 --- a/src/app/services/blog/MarkMarkBlog.service.ts +++ b/src/app/services/blog/MarkMarkBlog.service.ts @@ -29,26 +29,32 @@ export class MarkMarkBlog extends AbstractBlog { async getAllPosts(): Promise> { const mm = await this.mark.getLinks() - const links = mm.sections - .map(s => s.links) - .reduce((l, c): Link[] => [...c, ...l], []) + const posts: Collection = 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 - markdown: (new MarkMarkRenderer()).render({ - ...mm, - sections: [{ - links: [l], - }], - }, false), - })) + 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: [{ + ...section, + links: [link], + }], + }, false), + }) + } + } + + return posts.sort((a, b) => b.date!.getTime() - a.date!.getTime()) } protected createFeed(lastUpdated: Maybe): Awaitable { diff --git a/src/markmark/parser.ts b/src/markmark/parser.ts index 7079c88..1a7fbdd 100644 --- a/src/markmark/parser.ts +++ b/src/markmark/parser.ts @@ -105,7 +105,6 @@ export class Parser { } } - marked.marked.use({ walkTokens }) marked.marked.parse(content)