Some checks failed
		
		
	
	Build Tilde Friends / Build-All (push) Has been cancelled
				
			
		
			
				
	
	
		
			208 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			208 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 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();
 | |
| 	let writer = new commonmark.HtmlRenderer({safe: true});
 | |
| 	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;
 | |
| }
 |