import * as commonmark from './commonmark.min.js'; function escape(text) { return (text ?? '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>'); } function escapeAttribute(text) { return (text ?? '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } export async function get_blog_message(id) { let message; await ssb.sqlAsync( 'SELECT author, timestamp, content FROM messages WHERE id = ?', [id], function (row) { let content = JSON.parse(row.content); message = { author: row.author, timestamp: row.timestamp, blog: content?.blog, title: content?.title, }; } ); if (message) { await ssb.sqlAsync( ` SELECT json_extract(content, '$.name') AS name FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'about' AND json_extract(content, '$.about') = author AND name IS NOT NULL ORDER BY sequence DESC LIMIT 1 `, [message.author], function (row) { message.name = row.name; } ); } return message; } export function markdown(md) { let reader = new commonmark.Parser({safe: true}); let writer = new commonmark.HtmlRenderer(); let parsed = reader.parse(md || ''); let walker = parsed.walker(); let event, node; while ((event = walker.next())) { node = event.node; if (event.entering) { if (node.destination?.startsWith('&')) { node.destination = '/' + node.destination + '/view?filename=' + node.firstChild?.literal; } else if ( node.destination?.startsWith('@') || node.destination?.startsWith('%') ) { node.destination = '/~core/ssb/#' + escape(node.destination); } } } return writer.render(parsed); } export async function render_blog_post_html(blog_post) { let blob = utf8Decode(await ssb.blobGet(blog_post.blog)); return `<!DOCTYPE html> <html> <head> <title>🪵Tilde Friends Blog - ${markdown(blog_post.title)}</title> <base target="_top"> </head> <body> <h1><a href="./">🪵Tilde Friends Blog</a></h1> <div> <div><a href="../ssb/#${escapeAttribute(blog_post.author)}">${escape(blog_post.name)}</a> ${escape(new Date(blog_post.timestamp).toString())}</div> <div>${markdown(blob)}</div> </div> </body> </html> `; } function render_blog_post(blog_post) { return ` <div> <h2><a href="/~${core.app.owner}/${core.app.name}/${escapeAttribute(blog_post.id)}">${escape(blog_post.title)}</a></h2> <div><a href="../ssb/#${escapeAttribute(blog_post.author)}">${escape(blog_post.name)}</a> ${escape(new Date(blog_post.timestamp).toString())}</div> <div>${markdown(blog_post.summary)}</div> </div> `; } export function render_html(blogs) { return `<!DOCTYPE html> <html> <head> <title>🪵Tilde Friends Blog</title> <link href="./atom" type="application/atom+xml" rel="alternate" title="🪵Tilde Blog"/> <style> html { background-color: #ccc; } </style> <base target="_top"> </head> <body> <div style="display: flex; flex-direction: row; align-items: center; gap: 1em"> <h1>🪵Tilde Friends Blog</h1> <div style="font-size: xx-small; vertical-align: middle"><a href="/~cory/blog/atom">atom feed</a></div> </div> ${blogs.map((blog_post) => render_blog_post(blog_post)).join('\n')} </body> </html>`; } function render_blog_post_atom(blog_post) { return `<entry> <title>${escape(blog_post.title)}</title> <link href="/~cory/ssb/#${blog_post.id}" /> <id>${blog_post.id}</id> <published>${escape(new Date(blog_post.timestamp).toString())}</published> <summary>${escape(blog_post.summary)}</summary> <author> <name>${escape(blog_post.name)}</name> <feed>${escape(blog_post.author)}</feed> </author> </entry>`; } export function render_atom(blogs) { return `<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> <title>🪵Tilde Blog</title> <subtitle>A subtitle.</subtitle> <link href="${core.url}/atom" rel="self"/> <link href="${core.url}"/> <id>${core.url}</id> <updated>${new Date().toString()}</updated> ${blogs.map((blog_post) => render_blog_post_atom(blog_post)).join('\n')} </feed>`; } export async function get_posts() { let blogs = []; let ids = await ssb.getIdentities(); await ssb.sqlAsync( ` WITH blogs AS ( SELECT messages.author, messages.id, json_extract(messages.content, '$.title') AS title, json_extract(messages.content, '$.summary') AS summary, json_extract(messages.content, '$.blog') AS blog, messages.timestamp FROM messages_fts('blog') JOIN messages ON messages.rowid = messages_fts.rowid WHERE json_extract(messages.content, '$.type') = 'blog'), public AS ( SELECT author FROM ( SELECT messages.author, RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank, json_extract(messages.content, '$.publicWebHosting') AS is_public FROM messages_fts('about') JOIN messages ON messages.rowid = messages_fts.rowid WHERE json_extract(messages.content, '$.type') = 'about' AND is_public IS NOT NULL) WHERE author_rank = 1 AND is_public), names AS ( SELECT author, name FROM ( SELECT messages.author, RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank, json_extract(messages.content, '$.name') AS name FROM messages_fts('about') JOIN messages ON messages.rowid = messages_fts.rowid WHERE json_extract(messages.content, '$.type') = 'about' AND json_extract(messages.content, '$.about') = messages.author AND name IS NOT NULL) WHERE author_rank = 1) SELECT blogs.*, names.name FROM blogs JOIN json_each(?) AS self ON self.value = blogs.author JOIN public ON public.author = blogs.author LEFT OUTER JOIN names ON names.author = blogs.author ORDER BY blogs.timestamp DESC LIMIT 20 `, [JSON.stringify(ids)], function (row) { blogs.push(row); } ); return blogs; }