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;
 | 
						|
}
 |