diff --git a/src/app/http/controllers/MarkMark.controller.ts b/src/app/http/controllers/MarkMark.controller.ts index 42e3223..2deca3e 100644 --- a/src/app/http/controllers/MarkMark.controller.ts +++ b/src/app/http/controllers/MarkMark.controller.ts @@ -2,12 +2,16 @@ import {appPath, Controller, Inject, Injectable, plaintext, view} from '@extollo import {Home} from './Home.controller' import * as marked from 'marked' import {MarkMarkService} from '../../services/MarkMark.service' +import {MarkMarkBlog} from '../../services/blog/MarkMarkBlog.service' @Injectable() export class MarkMark extends Controller { @Inject() protected readonly markmark!: MarkMarkService + @Inject() + protected readonly blog!: MarkMarkBlog + public welcome() { const home = this.make(Home) return view('markmark:welcome', { @@ -47,4 +51,18 @@ export class MarkMark extends Controller { .contentType('text/markdown;variant=markmark') } + public async rss() { + const feed = await this.blog.getFeed() + return plaintext(feed.rss2()).contentType('application/rss+xml; charset=UTF-8') + } + + public async atom() { + const feed = await this.blog.getFeed() + return plaintext(feed.atom1()).contentType('application/atom+xml; charset=UTF-8') + } + + public async json() { + const feed = await this.blog.getFeed() + return plaintext(feed.json1()).contentType('application/feed+json; charset=UTF-8') + } } diff --git a/src/app/http/routes/app.routes.ts b/src/app/http/routes/app.routes.ts index f9ecd66..8bcfbf8 100644 --- a/src/app/http/routes/app.routes.ts +++ b/src/app/http/routes/app.routes.ts @@ -184,6 +184,19 @@ Route Route.get('/links') .pre(SiteTheme) .calls(MarkMark, m => m.linksHtml) + .alias('links') + + Route.get('/links/rss2.xml') + .calls(MarkMark, m => m.rss) + .alias('links:rss') + + Route.get('/links/atom.xml') + .calls(MarkMark, m => m.atom) + .alias('links:atom') + + Route.get('/links/json.json') + .calls(MarkMark, m => m.json) + .alias('links:json') Route.get('/links.mark.md') .calls(MarkMark, m => m.linksMarkMark) diff --git a/src/app/resources/views/links.pug b/src/app/resources/views/links.pug index d9f7854..f165301 100644 --- a/src/app/resources/views/links.pug +++ b/src/app/resources/views/links.pug @@ -10,7 +10,12 @@ block content h1 Bookmarks p Below is a random collection of links to other sites that I found interesting. p I think publishing personal bookmark lists is a great way to better connect and explore the smaller side of the Internet. - p This list is also available in MarkMark format (learn more). + p This list is also available in the following formats: + ul + li MarkMark (learn more). + li RSS + li Atom + li JSON section#links !{contentHtml} block append style diff --git a/src/app/resources/views/template_70s.pug b/src/app/resources/views/template_70s.pug index 6436d03..9f020cc 100644 --- a/src/app/resources/views/template_70s.pug +++ b/src/app/resources/views/template_70s.pug @@ -31,9 +31,15 @@ head link(rel='author' href='/humans.txt') link(rel="alternate" href="/links.mark.md" title="Garrett Mills - My Bookmarks" type="text/markdown;variant=markmark") link(rel="alternate" href="/links" title="Garrett Mills - My Bookmarks" type="text/html") - link(rel="alternate" href="/feed/atom.xml" title="Garrett Mills - Posts & Updates" type="application/atom+xml") - link(rel="alternate" href="/feed/rss.xml" title="Garrett Mills - Posts & Updates" type="application/rss+xml") - link(rel="alternate" href="/feed/json.json" title="Garrett Mills - Posts & Updates" type="application/feed+json") + link(rel="alternate" href="/links/atom.xml" title="Garrett's Bookmarks (Atom)" type="application/atom+xml") + link(rel="alternate" href="/links/rss2.xml" title="Garrett's Bookmarks (RSS)" type="application/rss+xml") + link(rel="alternate" href="/links/json.json" title="Garrett's Bookmarks (JSON)" type="application/feed+json") + link(rel="alternate" href="/feed/atom.xml" title="Garrett Mills - Posts & Updates (Atom)" type="application/atom+xml") + link(rel="alternate" href="/feed/rss.xml" title="Garrett Mills - Posts & Updates (RSS)" type="application/rss+xml") + link(rel="alternate" href="/feed/json.json" title="Garrett Mills - Posts & Updates (JSON)" type="application/feed+json") + link(rel="alternate" href="/blog/atom.xml" title="Garrett's Blog (Atom)" type="application/atom+xml") + link(rel="alternate" href="/blog/rss2.xml" title="Garrett's Blog (RSS)" type="application/rss+xml") + link(rel="alternate" href="/blog/json.json" title="Garrett's Blog (JSON)" type="application/feed+json") link(rel='apple-touch-icon' sizes='180x180' href=asset('favicon/apple-touch-icon.png')) link(rel='manifest' href=asset('favicon/site.webmanifest')) diff --git a/src/app/services/blog/MarkMarkBlog.service.ts b/src/app/services/blog/MarkMarkBlog.service.ts new file mode 100644 index 0000000..f721dfb --- /dev/null +++ b/src/app/services/blog/MarkMarkBlog.service.ts @@ -0,0 +1,84 @@ +import * as RSSFeed from 'feed' +import {Awaitable, collect, Collection, Inject, Maybe, Singleton} from '@extollo/lib' +import {AbstractBlog, BlogBackend, BlogPost, BlogPostFrontMatter, isBlogPostFrontMatter} from './AbstractBlog.service' +import {MarkMarkService} from '../MarkMark.service' +import {Link} from '../../../markmark/types' +import {MarkMarkRenderer} from '../../../markmark/markmark.renderer' + +@Singleton() +export class MarkMarkBlog extends AbstractBlog { + @Inject() + protected readonly mark!: MarkMarkService + + protected getBackend(): BlogBackend { + return { + routePrefix: '/links', + resourcePath: [], + author: { + name: 'Garrett Mills', + email: 'shout@garrettmills.dev', + link: 'https://garrettmills.dev/#about', + }, + } + } + + protected isValidFrontMatter(what: unknown): what is BlogPostFrontMatter { + return isBlogPostFrontMatter(what) + } + + async getAllPosts(): Promise> { + const mm = await this.mark.getLinks() + + const links = mm.sections + .map(s => s.links) + .reduce((l, c): Link[] => [...c, ...l], []) + + 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), + })) + } + + protected createFeed(lastUpdated: Maybe): Awaitable { + return new RSSFeed.Feed({ + title: 'Garrett\'s Bookmarks', + description: 'Links to articles, projects, and sites I found interesting', + id: this.routing.getNamedPath('links').toRemote, + link: this.routing.getNamedPath('links').toRemote, + language: 'en', + image: this.routing.getAssetPath('favicon', 'apple-touch-icon.png').toRemote, + favicon: this.routing.getAssetPath('favicon', 'favicon.ico').toRemote, + copyright: `Copyright (c) ${(new Date).getFullYear()} Garrett Mills. See website for licensing details.`, + updated: lastUpdated, + generator: '', + feedLinks: { + json: this.routing.getNamedPath('links:json').toRemote, + atom: this.routing.getNamedPath('links:atom').toRemote, + rss: this.routing.getNamedPath('links:rss').toRemote, + }, + author: { + name: 'Garrett Mills', + email: 'shout@garrettmills.dev', + link: 'https://garrettmills.dev/#about', + }, + }) + } + + getUrl(post: BlogPost): string { + let prefix = this.getBackend().routePrefix + if ( !prefix.startsWith('/') ) prefix = `/${prefix}` + return `${prefix}/#link-${post.slug}` + } +} diff --git a/src/markmark/html.renderer.ts b/src/markmark/html.renderer.ts index c963d27..4bb30a5 100644 --- a/src/markmark/html.renderer.ts +++ b/src/markmark/html.renderer.ts @@ -29,7 +29,7 @@ export class HtmlRenderer { linkTitle += ` ${link.tags.map(x => '#' + x + '').join(' ')}` } - mmLines.push(`