chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { lfsPointer } from '@pdsjs/git/browser';
import { useEffect, useMemo, useRef } from 'react';
import { bytes } from '#/lib/format.js';
import { account, readerFor } from '#/lib/git.js';
import { renderMarkdown } from '#/lib/markdown.js';
/**
* The stand-in for an image the repository holds only as a Git LFS pointer.
* @param {Element} image
* @param {import('@pdsjs/git/browser').LfsPointer} pointer
* @returns {Element}
*/
function lfsNotice(image, pointer) {
const note = document.createElement('span');
note.className = 'md-lfs';
const alt = image.getAttribute('alt');
note.textContent = `${alt || 'Image'}: stored with Git LFS, ${bytes(pointer.size)}`;
return note;
}
/**
* A rendered markdown document from the repository.
*
* An image the document names by a relative path is a file in the repository,
* and the repository's objects are already in memory by the time this
* renders. The renderer leaves those images without a source and puts the
* path on the tag; this fills them in from the object store, which is why a
* README's diagrams appear without the server serving anything.
*/
export function Markdown({ text, repo, refName, basePath = '', space }) {
const container = useRef(null);
const html = useMemo(
() => renderMarkdown(text, { repo, refName, basePath }),
[text, repo, refName, basePath],
);
// The effect reads the DOM that html produced, so it has to run again
// whenever html changes, even though it names no variable from it.
// biome-ignore lint/correctness/useExhaustiveDependencies: html rebuilds the DOM this reads
useEffect(() => {
const node = container.current;
if (!node) return undefined;
const images = node.querySelectorAll('img[data-repo-path]');
if (images.length === 0) return undefined;
let live = true;
/** @type {string[]} */
const urls = [];
const reader = readerFor(repo, undefined, space);
(async () => {
for (const image of images) {
const path = image.getAttribute('data-repo-path');
try {
const file = await reader.readImage(account.did, repo, refName, path);
if (!live) return;
if (!file) continue;
const pointer = lfsPointer(file.bytes);
if (pointer) {
image.replaceWith(lfsNotice(image, pointer));
continue;
}
const url = URL.createObjectURL(
new Blob([file.bytes], { type: file.mediaType }),
);
urls.push(url);
image.setAttribute('src', url);
} catch {
// A path the document names but the tree does not hold, or a file
// that is no image. The alt text stands in, the way a missing image
// always does. The next image still gets its turn.
}
}
})();
return () => {
live = false;
for (const url of urls) URL.revokeObjectURL(url);
};
}, [html, repo, refName]);
return (
<div
ref={container}
className="markdown"
// Every tag here was written by renderMarkdown, from the list of tags
// this page allows.
// biome-ignore lint/security/noDangerouslySetInnerHtml: the renderer emits only its own tags
dangerouslySetInnerHTML={{ __html: html }}
/>
);
}