104 lines
2.6 KiB
TypeScript
104 lines
2.6 KiB
TypeScript
import {
|
|
findNodeMatching,
|
|
findNodeOfType,
|
|
ParseTree,
|
|
renderToText,
|
|
} from "$sb/lib/tree.ts";
|
|
|
|
import { extractAttributes } from "$sb/lib/attribute.ts";
|
|
|
|
export type FeedItem = {
|
|
id: string;
|
|
title?: string;
|
|
attributes?: Record<string, any>;
|
|
text: string;
|
|
};
|
|
|
|
// tree = Document node
|
|
export async function extractFeedItems(tree: ParseTree): Promise<FeedItem[]> {
|
|
let nodes: ParseTree[] = [];
|
|
const feedItems: FeedItem[] = [];
|
|
if (tree.type !== "Document") {
|
|
throw new Error("Did not get a document");
|
|
}
|
|
// Run through the whole document to find the feed items
|
|
for (const node of tree.children!) {
|
|
if (node.type === "FrontMatter") {
|
|
// Not interested
|
|
console.log("Ignoring", node);
|
|
continue;
|
|
}
|
|
if (node.type === "HorizontalRule") {
|
|
// Ok we reached the end of a feed item
|
|
feedItems.push(await nodesToFeedItem(nodes));
|
|
nodes = [];
|
|
} else {
|
|
nodes.push(node);
|
|
}
|
|
}
|
|
if (renderToText({ children: nodes }).trim().length > 0) {
|
|
feedItems.push(await nodesToFeedItem(nodes));
|
|
}
|
|
|
|
return feedItems;
|
|
}
|
|
|
|
async function nodesToFeedItem(nodes: ParseTree[]): Promise<FeedItem> {
|
|
const wrapperNode: ParseTree = {
|
|
children: nodes,
|
|
};
|
|
const attributes = await extractAttributes(wrapperNode, true);
|
|
let id = attributes.id;
|
|
delete attributes.id;
|
|
if (!id) {
|
|
const anchor = findNodeOfType(wrapperNode, "NamedAnchor");
|
|
if (anchor) {
|
|
id = anchor.children![0].text!.substring(1);
|
|
if (id.startsWith("id/")) {
|
|
id = id.substring(3);
|
|
}
|
|
// Empty it out
|
|
anchor.children = [];
|
|
}
|
|
}
|
|
|
|
// Find a title
|
|
let title: string | undefined;
|
|
const titleNode = findNodeMatching(
|
|
wrapperNode,
|
|
(node) => !!node.type?.startsWith("ATXHeading"),
|
|
);
|
|
if (titleNode) {
|
|
title = titleNode.children![1].text!.trim();
|
|
titleNode.children = [];
|
|
}
|
|
|
|
const text = renderToText(wrapperNode).trim();
|
|
|
|
if (!id) {
|
|
// If all else fails, generate content based ID
|
|
id = `gen/${djb2Hash(JSON.stringify({ attributes, text }))}`;
|
|
}
|
|
// console.log("Extracted attributes", attributes);
|
|
const feedItem: FeedItem = { id, text };
|
|
if (title) {
|
|
feedItem.title = title;
|
|
}
|
|
if (Object.keys(attributes).length > 0) {
|
|
feedItem.attributes = attributes;
|
|
}
|
|
return feedItem;
|
|
}
|
|
|
|
function djb2Hash(input: string): string {
|
|
let hash = 5381; // Initial hash value
|
|
|
|
for (let i = 0; i < input.length; i++) {
|
|
// Update the hash value by shifting and adding the character code
|
|
hash = (hash * 33) ^ input.charCodeAt(i);
|
|
}
|
|
|
|
// Convert the hash to a hexadecimal string representation
|
|
return hash.toString(16);
|
|
}
|