feat: add support for remaining leaflet blocks (#12)

* add horizontal rule lexicon

* add horizontal rule tag to block converter code

* add unordered lists

* fix formatting issues with lists

* add code and math blocks

* massive refactor

* explicitly allow specific tags and attributes

* fix test that was changed for some reason

* add changeset

authored by Dane Miller and committed by GitHub 6d70cc64 44ba46f9

+13
.changeset/plain-beans-taste.md
···
··· 1 + --- 2 + "@nulfrost/leaflet-loader-astro": minor 3 + --- 4 + 5 + Added support for these leaflet blocks: 6 + 7 + - ul/li 8 + - math 9 + - code 10 + - img 11 + - hr 12 + 13 + the only remaining block to implement is "website", though I haven't thought of a good way to output that yet. stay tuned for a further release
+1 -1
lex.config.js
··· 2 3 export default defineLexiconConfig({ 4 files: ["lexicons/**/*.json"], 5 - outdir: "src/lexicons/", 6 });
··· 2 3 export default defineLexiconConfig({ 4 files: ["lexicons/**/*.json"], 5 + outdir: "lib/lexicons/", 6 });
+11
lexicons/pub/leaflet/blocks/horizontalRule.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "pub.leaflet.blocks.horizontalRule", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "required": [], 8 + "properties": {} 9 + } 10 + } 11 + }
+13 -15
lib/leaflet-live-loader.ts
··· 1 - import { Agent } from "@atproto/api"; 2 import { isDid } from "@atproto/did"; 3 import type { LiveLoader } from "astro/loaders"; 4 import type { ··· 17 resolveMiniDoc, 18 uriToRkey, 19 } from "./utils.js"; 20 21 export function leafletLiveLoader( 22 options: LiveLeafletLoaderOptions, ··· 44 name: "leaflet-loader-astro", 45 loadCollection: async ({ filter }) => { 46 try { 47 - const pds_url = await resolveMiniDoc(repo); 48 - const agent = new Agent({ service: pds_url }); 49 50 const { documents } = await getLeafletDocuments({ 51 - agent, 52 repo, 53 reverse: filter?.reverse, 54 cursor: filter?.cursor, ··· 67 }), 68 rendered: { 69 html: leafletBlocksToHTML({ 70 - id, 71 - uri: document.uri, 72 - cid: document.cid, 73 - value: document.value as unknown as LeafletDocumentRecord, 74 }), 75 }, 76 }; ··· 95 }; 96 } 97 try { 98 - const pds_url = await resolveMiniDoc(repo); 99 - const agent = new Agent({ service: pds_url }); 100 const document = await getSingleLeafletDocument({ 101 - agent, 102 id: filter.id, 103 repo, 104 }); ··· 114 }), 115 rendered: { 116 html: leafletBlocksToHTML({ 117 - id: filter.id, 118 - uri: document.uri, 119 - cid, 120 - value: document.value as unknown as LeafletDocumentRecord, 121 }), 122 }, 123 };
··· 1 import { isDid } from "@atproto/did"; 2 import type { LiveLoader } from "astro/loaders"; 3 import type { ··· 16 resolveMiniDoc, 17 uriToRkey, 18 } from "./utils.js"; 19 + import { Client, simpleFetchHandler } from "@atcute/client"; 20 21 export function leafletLiveLoader( 22 options: LiveLeafletLoaderOptions, ··· 44 name: "leaflet-loader-astro", 45 loadCollection: async ({ filter }) => { 46 try { 47 + const { pds, did } = await resolveMiniDoc(repo); 48 + const handler = simpleFetchHandler({ service: pds }); 49 + const rpc = new Client({ handler }); 50 51 const { documents } = await getLeafletDocuments({ 52 + rpc, 53 repo, 54 reverse: filter?.reverse, 55 cursor: filter?.cursor, ··· 68 }), 69 rendered: { 70 html: leafletBlocksToHTML({ 71 + record: document.value as unknown as LeafletDocumentRecord, 72 + did, 73 }), 74 }, 75 }; ··· 94 }; 95 } 96 try { 97 + const { pds, did } = await resolveMiniDoc(repo); 98 + const handler = simpleFetchHandler({ service: pds }); 99 + const rpc = new Client({ handler }); 100 const document = await getSingleLeafletDocument({ 101 + rpc, 102 id: filter.id, 103 repo, 104 }); ··· 114 }), 115 rendered: { 116 html: leafletBlocksToHTML({ 117 + record: document.value as unknown as LeafletDocumentRecord, 118 + did, 119 }), 120 }, 121 };
+10 -11
lib/leaftlet-static-loader.ts
··· 1 - import { Agent } from "@atproto/api"; 2 import { isDid } from "@atproto/did"; 3 import type { Loader, LoaderContext } from "astro/loaders"; 4 import { LeafletDocumentSchema } from "schema.js"; ··· 7 StaticLeafletLoaderOptions, 8 } from "types.js"; 9 import { 10 LiveLoaderError, 11 resolveMiniDoc, 12 - getLeafletDocuments, 13 uriToRkey, 14 - leafletDocumentRecordToView, 15 - leafletBlocksToHTML, 16 } from "utils.js"; 17 18 export function leafletStaticLoader( ··· 43 }: LoaderContext) => { 44 try { 45 logger.info("fetching latest leaflet documents"); 46 - const pds_url = await resolveMiniDoc(repo); 47 - const agent = new Agent({ service: pds_url }); 48 49 let cursor: string | undefined; 50 let count = 0; ··· 52 fetching: do { 53 const { documents, cursor: documentsCursor } = 54 await getLeafletDocuments({ 55 - agent, 56 repo, 57 cursor, 58 limit: 100, ··· 83 digest, 84 rendered: { 85 html: leafletBlocksToHTML({ 86 - id, 87 - uri: document.uri, 88 - cid: document.cid, 89 - value: document.value as unknown as LeafletDocumentRecord, 90 }), 91 }, 92 });
··· 1 + import { Client, simpleFetchHandler } from "@atcute/client"; 2 import { isDid } from "@atproto/did"; 3 import type { Loader, LoaderContext } from "astro/loaders"; 4 import { LeafletDocumentSchema } from "schema.js"; ··· 7 StaticLeafletLoaderOptions, 8 } from "types.js"; 9 import { 10 + getLeafletDocuments, 11 + leafletBlocksToHTML, 12 + leafletDocumentRecordToView, 13 LiveLoaderError, 14 resolveMiniDoc, 15 uriToRkey, 16 } from "utils.js"; 17 18 export function leafletStaticLoader( ··· 43 }: LoaderContext) => { 44 try { 45 logger.info("fetching latest leaflet documents"); 46 + const { pds, did } = await resolveMiniDoc(repo); 47 + const handler = simpleFetchHandler({ service: pds }); 48 + const rpc = new Client({ handler }); 49 50 let cursor: string | undefined; 51 let count = 0; ··· 53 fetching: do { 54 const { documents, cursor: documentsCursor } = 55 await getLeafletDocuments({ 56 + rpc, 57 repo, 58 cursor, 59 limit: 100, ··· 84 digest, 85 rendered: { 86 html: leafletBlocksToHTML({ 87 + record: document.value as unknown as LeafletDocumentRecord, 88 + did, 89 }), 90 }, 91 });
+1
lib/lexicons/index.ts
··· 1 export * as ComAtprotoRepoStrongRef from "./types/com/atproto/repo/strongRef.js"; 2 export * as PubLeafletBlocksCode from "./types/pub/leaflet/blocks/code.js"; 3 export * as PubLeafletBlocksHeader from "./types/pub/leaflet/blocks/header.js"; 4 export * as PubLeafletBlocksImage from "./types/pub/leaflet/blocks/image.js"; 5 export * as PubLeafletBlocksMath from "./types/pub/leaflet/blocks/math.js"; 6 export * as PubLeafletBlocksText from "./types/pub/leaflet/blocks/text.js";
··· 1 export * as ComAtprotoRepoStrongRef from "./types/com/atproto/repo/strongRef.js"; 2 export * as PubLeafletBlocksCode from "./types/pub/leaflet/blocks/code.js"; 3 export * as PubLeafletBlocksHeader from "./types/pub/leaflet/blocks/header.js"; 4 + export * as PubLeafletBlocksHorizontalRule from "./types/pub/leaflet/blocks/horizontalRule.js"; 5 export * as PubLeafletBlocksImage from "./types/pub/leaflet/blocks/image.js"; 6 export * as PubLeafletBlocksMath from "./types/pub/leaflet/blocks/math.js"; 7 export * as PubLeafletBlocksText from "./types/pub/leaflet/blocks/text.js";
+16
lib/lexicons/types/pub/leaflet/blocks/horizontalRule.ts
···
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + 4 + const _mainSchema = /*#__PURE__*/ v.object({ 5 + $type: /*#__PURE__*/ v.optional( 6 + /*#__PURE__*/ v.literal("pub.leaflet.blocks.horizontalRule"), 7 + ), 8 + }); 9 + 10 + type main$schematype = typeof _mainSchema; 11 + 12 + export interface mainSchema extends main$schematype {} 13 + 14 + export const mainSchema = _mainSchema as mainSchema; 15 + 16 + export interface Main extends v.InferInput<typeof mainSchema> {}
+1 -1
lib/lexicons/types/pub/leaflet/blocks/image.ts
··· 14 ), 15 alt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 16 get aspectRatio() { 17 - return aspectRatioSchema; 18 }, 19 image: /*#__PURE__*/ v.blob(), 20 });
··· 14 ), 15 alt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 16 get aspectRatio() { 17 + return _aspectRatioSchema; 18 }, 19 image: /*#__PURE__*/ v.blob(), 20 });
+7 -5
lib/types.ts
··· 1 - import type { Agent } from "@atproto/api"; 2 import type { PubLeafletRichtextFacet } from "./lexicons/index.js"; 3 4 export interface LiveLeafletLoaderOptions { ··· 58 } 59 60 export interface GetLeafletDocumentsParams { 61 - repo: string; 62 - agent: Agent; 63 cursor?: string; 64 limit?: number; 65 reverse?: boolean; 66 } 67 68 export interface GetSingleLeafletDocumentParams { 69 - repo: string; 70 - agent: Agent; 71 id: string; 72 } 73
··· 1 + import type { Client } from "@atcute/client"; 2 + import type { ActorIdentifier } from "@atcute/lexicons"; 3 + import type { XRPCProcedures, XRPCQueries } from "@atcute/lexicons/ambient"; 4 import type { PubLeafletRichtextFacet } from "./lexicons/index.js"; 5 6 export interface LiveLeafletLoaderOptions { ··· 60 } 61 62 export interface GetLeafletDocumentsParams { 63 + repo: ActorIdentifier; 64 + rpc: Client<XRPCQueries, XRPCProcedures>; 65 cursor?: string; 66 limit?: number; 67 reverse?: boolean; 68 } 69 70 export interface GetSingleLeafletDocumentParams { 71 + repo: ActorIdentifier; 72 + rpc: Client<XRPCQueries, XRPCProcedures>; 73 id: string; 74 } 75
+187 -107
lib/utils.ts
··· 1 import { is } from "@atcute/lexicons"; 2 import { AtUri, UnicodeString } from "@atproto/api"; 3 import sanitizeHTML from "sanitize-html"; 4 import { 5 PubLeafletBlocksHeader, 6 PubLeafletBlocksText, 7 PubLeafletPagesLinearDocument, 8 } from "./lexicons/index.js"; 9 import type { ··· 29 export function uriToRkey(uri: string): string { 30 const u = AtUri.make(uri); 31 if (!u.rkey) { 32 - throw new Error("Failed to get rkey from uri."); 33 } 34 return u.rkey; 35 } ··· 47 } 48 const data = (await response.json()) as MiniDoc; 49 50 - return data.pds; 51 } catch { 52 throw new Error(`failed to resolve handle: ${handleOrDid}`); 53 } ··· 57 repo, 58 reverse, 59 cursor, 60 - agent, 61 limit, 62 }: GetLeafletDocumentsParams) { 63 - const response = await agent.com.atproto.repo.listRecords({ 64 - repo, 65 - collection: "pub.leaflet.document", 66 - cursor, 67 - reverse, 68 - limit, 69 }); 70 71 - if (response.success === false) { 72 throw new LiveLoaderError( 73 "error fetching leaflet documents", 74 "DOCUMENT_FETCH_ERROR", ··· 76 } 77 78 return { 79 - documents: response?.data?.records, 80 - cursor: response?.data?.cursor, 81 }; 82 } 83 84 export async function getSingleLeafletDocument({ 85 - agent, 86 repo, 87 id, 88 }: GetSingleLeafletDocumentParams) { 89 - const response = await agent.com.atproto.repo.getRecord({ 90 - repo, 91 - collection: "pub.leaflet.document", 92 - rkey: id, 93 }); 94 95 - if (response.success === false) { 96 throw new LiveLoaderError( 97 "error fetching single document", 98 "DOCUMENT_FETCH_ERROR", 99 ); 100 } 101 102 - return response?.data; 103 } 104 105 export function leafletDocumentRecordToView({ ··· 122 }; 123 } 124 125 - export function leafletBlocksToHTML(record: { 126 - id: string; 127 - uri: string; 128 - cid: string; 129 - value: LeafletDocumentRecord; 130 }) { 131 let html = ""; 132 - const firstPage = record.value.pages[0]; 133 let blocks: PubLeafletPagesLinearDocument.Block[] = []; 134 if (is(PubLeafletPagesLinearDocument.mainSchema, firstPage)) { 135 blocks = firstPage.blocks || []; 136 } 137 138 for (const block of blocks) { 139 - if (is(PubLeafletBlocksText.mainSchema, block.block)) { 140 - const rt = new RichText({ 141 - text: block.block.plaintext, 142 - facets: block.block.facets || [], 143 - }); 144 - const children = []; 145 - for (const segment of rt.segments()) { 146 - const link = segment.facet?.find( 147 - (segment) => segment.$type === "pub.leaflet.richtext.facet#link", 148 - ); 149 - const isBold = segment.facet?.find( 150 - (segment) => segment.$type === "pub.leaflet.richtext.facet#bold", 151 - ); 152 - const isCode = segment.facet?.find( 153 - (segment) => segment.$type === "pub.leaflet.richtext.facet#code", 154 - ); 155 - const isStrikethrough = segment.facet?.find( 156 - (segment) => 157 - segment.$type === "pub.leaflet.richtext.facet#strikethrough", 158 - ); 159 - const isUnderline = segment.facet?.find( 160 - (segment) => segment.$type === "pub.leaflet.richtext.facet#underline", 161 - ); 162 - const isItalic = segment.facet?.find( 163 - (segment) => segment.$type === "pub.leaflet.richtext.facet#italic", 164 - ); 165 - if (isCode) { 166 - children.push(` <code> 167 - ${segment.text} 168 - </code>`); 169 - } else if (link) { 170 - children.push( 171 - ` <a 172 - href="${link.uri}" 173 - target="_blank" 174 - > 175 - ${segment.text} 176 - </a>`, 177 - ); 178 - } else if (isBold) { 179 - children.push(`<b>${segment.text}</b>`); 180 - } else if (isStrikethrough) { 181 - children.push(`<s>${segment.text}</s>`); 182 - } else if (isUnderline) { 183 - children.push( 184 - `<span style="text-decoration:underline;">${segment.text}</span>`, 185 - ); 186 - } else if (isItalic) { 187 - children.push(`<i>${segment.text}</i>`); 188 - } else { 189 - children.push( 190 - ` 191 - ${segment.text} 192 - `, 193 - ); 194 - } 195 - } 196 - html += `<p>${children.join("\n")}</p>`; 197 - } 198 - 199 - if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 200 - if (block.block.level === 1) { 201 - html += `<h2>${block.block.plaintext}</h2>`; 202 - } 203 - } 204 - if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 205 - if (block.block.level === 2) { 206 - html += `<h3>${block.block.plaintext}</h3>`; 207 - } 208 - } 209 - if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 210 - if (block.block.level === 3) { 211 - html += `<h4>${block.block.plaintext}</h4>`; 212 - } 213 - } 214 - if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 215 - if (!block.block.level) { 216 - html += `<h6>${block.block.plaintext}</h6>`; 217 - } 218 - } 219 } 220 221 - return sanitizeHTML(html); 222 } 223 224 export class RichText { 225 unicodeText: UnicodeString; 226 facets?: Facet[]; 227 - 228 constructor(props: { text: string; facets: Facet[] }) { 229 this.unicodeText = new UnicodeString(props.text); 230 this.facets = props.facets; ··· 278 } 279 } 280 }
··· 1 + import type {} from "@atcute/atproto"; 2 import { is } from "@atcute/lexicons"; 3 import { AtUri, UnicodeString } from "@atproto/api"; 4 + import katex from "katex"; 5 import sanitizeHTML from "sanitize-html"; 6 import { 7 + PubLeafletBlocksCode, 8 PubLeafletBlocksHeader, 9 + PubLeafletBlocksHorizontalRule, 10 + PubLeafletBlocksImage, 11 + PubLeafletBlocksMath, 12 PubLeafletBlocksText, 13 + PubLeafletBlocksUnorderedList, 14 PubLeafletPagesLinearDocument, 15 } from "./lexicons/index.js"; 16 import type { ··· 36 export function uriToRkey(uri: string): string { 37 const u = AtUri.make(uri); 38 if (!u.rkey) { 39 + throw new Error("failed to get rkey"); 40 } 41 return u.rkey; 42 } ··· 54 } 55 const data = (await response.json()) as MiniDoc; 56 57 + return { 58 + pds: data.pds, 59 + did: data.did, 60 + }; 61 } catch { 62 throw new Error(`failed to resolve handle: ${handleOrDid}`); 63 } ··· 67 repo, 68 reverse, 69 cursor, 70 + rpc, 71 limit, 72 }: GetLeafletDocumentsParams) { 73 + const { ok, data } = await rpc.get("com.atproto.repo.listRecords", { 74 + params: { 75 + collection: "pub.leaflet.document", 76 + cursor, 77 + reverse, 78 + limit, 79 + repo, 80 + }, 81 }); 82 83 + if (!ok) { 84 throw new LiveLoaderError( 85 "error fetching leaflet documents", 86 "DOCUMENT_FETCH_ERROR", ··· 88 } 89 90 return { 91 + documents: data?.records, 92 + cursor: data?.cursor, 93 }; 94 } 95 96 export async function getSingleLeafletDocument({ 97 + rpc, 98 repo, 99 id, 100 }: GetSingleLeafletDocumentParams) { 101 + const { ok, data } = await rpc.get("com.atproto.repo.getRecord", { 102 + params: { 103 + collection: "pub.leaflet.document", 104 + repo, 105 + rkey: id, 106 + }, 107 }); 108 109 + if (!ok) { 110 throw new LiveLoaderError( 111 "error fetching single document", 112 "DOCUMENT_FETCH_ERROR", 113 ); 114 } 115 116 + return data; 117 } 118 119 export function leafletDocumentRecordToView({ ··· 136 }; 137 } 138 139 + export function leafletBlocksToHTML({ 140 + record, 141 + did, 142 + }: { 143 + record: LeafletDocumentRecord; 144 + did: string; 145 }) { 146 let html = ""; 147 + const firstPage = record.pages[0]; 148 let blocks: PubLeafletPagesLinearDocument.Block[] = []; 149 + 150 if (is(PubLeafletPagesLinearDocument.mainSchema, firstPage)) { 151 blocks = firstPage.blocks || []; 152 } 153 154 for (const block of blocks) { 155 + html += parseBlocks({ block, did }); 156 } 157 158 + return sanitizeHTML(html, { 159 + allowedAttributes: { 160 + "*": ["class", "style"], 161 + img: ["src", "height", "width", "alt"], 162 + a: ["href", "target", "rel"], 163 + }, 164 + allowedTags: [ 165 + "img", 166 + "pre", 167 + "code", 168 + "p", 169 + "a", 170 + "b", 171 + "s", 172 + "ul", 173 + "li", 174 + "i", 175 + "h1", 176 + "h2", 177 + "h3", 178 + "h4", 179 + "h5", 180 + "h6", 181 + "hr", 182 + "div", 183 + "span", 184 + ], 185 + selfClosing: ["img"], 186 + }); 187 } 188 189 export class RichText { 190 unicodeText: UnicodeString; 191 facets?: Facet[]; 192 constructor(props: { text: string; facets: Facet[] }) { 193 this.unicodeText = new UnicodeString(props.text); 194 this.facets = props.facets; ··· 242 } 243 } 244 } 245 + 246 + function parseBlocks({ 247 + block, 248 + did, 249 + }: { 250 + block: PubLeafletPagesLinearDocument.Block; 251 + did: string; 252 + }): string { 253 + let html = ""; 254 + 255 + if (is(PubLeafletBlocksText.mainSchema, block.block)) { 256 + const rt = new RichText({ 257 + text: block.block.plaintext, 258 + facets: block.block.facets || [], 259 + }); 260 + const children = []; 261 + for (const segment of rt.segments()) { 262 + const link = segment.facet?.find( 263 + (segment) => segment.$type === "pub.leaflet.richtext.facet#link", 264 + ); 265 + const isBold = segment.facet?.find( 266 + (segment) => segment.$type === "pub.leaflet.richtext.facet#bold", 267 + ); 268 + const isCode = segment.facet?.find( 269 + (segment) => segment.$type === "pub.leaflet.richtext.facet#code", 270 + ); 271 + const isStrikethrough = segment.facet?.find( 272 + (segment) => 273 + segment.$type === "pub.leaflet.richtext.facet#strikethrough", 274 + ); 275 + const isUnderline = segment.facet?.find( 276 + (segment) => segment.$type === "pub.leaflet.richtext.facet#underline", 277 + ); 278 + const isItalic = segment.facet?.find( 279 + (segment) => segment.$type === "pub.leaflet.richtext.facet#italic", 280 + ); 281 + if (isCode) { 282 + children.push(`<pre><code>${segment.text}</code></pre>`); 283 + } else if (link) { 284 + children.push( 285 + `<a href="${link.uri}" target="_blank" rel="noopener noreferrer">${segment.text}</a>`, 286 + ); 287 + } else if (isBold) { 288 + children.push(`<b>${segment.text}</b>`); 289 + } else if (isStrikethrough) { 290 + children.push(`<s>${segment.text}</s>`); 291 + } else if (isUnderline) { 292 + children.push( 293 + `<span style="text-decoration:underline;">${segment.text}</span>`, 294 + ); 295 + } else if (isItalic) { 296 + children.push(`<i>${segment.text}</i>`); 297 + } else { 298 + children.push(`${segment.text}`); 299 + } 300 + } 301 + html += `<p>${children.join("")}</p>`; 302 + } 303 + 304 + if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 305 + if (block.block.level === 1) { 306 + html += `<h2>${block.block.plaintext}</h2>`; 307 + } 308 + } 309 + if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 310 + if (block.block.level === 2) { 311 + html += `<h3>${block.block.plaintext}</h3>`; 312 + } 313 + } 314 + if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 315 + if (block.block.level === 3) { 316 + html += `<h4>${block.block.plaintext}</h4>`; 317 + } 318 + } 319 + if (is(PubLeafletBlocksHeader.mainSchema, block.block)) { 320 + if (!block.block.level) { 321 + html += `<h6>${block.block.plaintext}</h6>`; 322 + } 323 + } 324 + 325 + if (is(PubLeafletBlocksHorizontalRule.mainSchema, block.block)) { 326 + html += `<hr />`; 327 + } 328 + if (is(PubLeafletBlocksUnorderedList.mainSchema, block.block)) { 329 + html += `<ul>${block.block.children.map((child) => renderListItem({ item: child, did })).join("")}</ul>`; 330 + } 331 + 332 + if (is(PubLeafletBlocksMath.mainSchema, block.block)) { 333 + html += `<div>${katex.renderToString(block.block.tex, { displayMode: true, output: "html", throwOnError: false })}</div>`; 334 + } 335 + 336 + if (is(PubLeafletBlocksCode.mainSchema, block.block)) { 337 + html += `<pre><code data-language=${block.block.language}>${block.block.plaintext}</code></pre>`; 338 + } 339 + 340 + if (is(PubLeafletBlocksImage.mainSchema, block.block)) { 341 + // @ts-ignore 342 + html += `<div><img src="https://cdn.bsky.app/img/feed_fullsize/plain/${did}/${block.block.image.ref.$link}@jpeg" height="${block.block.aspectRatio.height}" width="${block.block.aspectRatio.width}" alt="${block.block.alt}" /></div>`; 343 + } 344 + 345 + return html.trim(); 346 + } 347 + 348 + function renderListItem({ 349 + item, 350 + did, 351 + }: { 352 + item: PubLeafletBlocksUnorderedList.ListItem; 353 + did: string; 354 + }): string { 355 + const children: string | null = item.children?.length 356 + ? `<ul>${item.children.map((child) => renderListItem({ item: child, did }))}</ul>` 357 + : ""; 358 + 359 + return `<li>${parseBlocks({ block: { block: item.content }, did })}${children}</li>`; 360 + }
+6 -2
package.json
··· 25 "typecheck": "tsc", 26 "release": "pnpm run build && changeset publish", 27 "build": "rm -rf dist && tsup --format esm --dts", 28 - "pack": "pnpm build && pnpm pack" 29 }, 30 "license": "MIT", 31 "files": [ ··· 45 } 46 }, 47 "devDependencies": { 48 "@atcute/lex-cli": "^2.1.1", 49 "@biomejs/biome": "2.1.3", 50 "@changesets/cli": "^2.29.5", 51 "@types/sanitize-html": "^2.16.0", 52 "astro": "^5.12.8", 53 "tsup": "^8.5.0", ··· 55 "vitest": "^3.2.4" 56 }, 57 "dependencies": { 58 "@atcute/lexicons": "^1.1.0", 59 - "@atproto/api": "^0.16.1", 60 "@atproto/did": "^0.1.5", 61 "sanitize-html": "^2.17.0" 62 } 63 }
··· 25 "typecheck": "tsc", 26 "release": "pnpm run build && changeset publish", 27 "build": "rm -rf dist && tsup --format esm --dts", 28 + "pack": "rm -rf *.tgz && pnpm build && pnpm pack" 29 }, 30 "license": "MIT", 31 "files": [ ··· 45 } 46 }, 47 "devDependencies": { 48 + "@atcute/atproto": "^3.1.1", 49 "@atcute/lex-cli": "^2.1.1", 50 "@biomejs/biome": "2.1.3", 51 "@changesets/cli": "^2.29.5", 52 + "@types/bun": "^1.2.19", 53 "@types/sanitize-html": "^2.16.0", 54 "astro": "^5.12.8", 55 "tsup": "^8.5.0", ··· 57 "vitest": "^3.2.4" 58 }, 59 "dependencies": { 60 + "@atcute/client": "^4.0.3", 61 "@atcute/lexicons": "^1.1.0", 62 + "@atproto/api": "^0.16.2", 63 "@atproto/did": "^0.1.5", 64 + "katex": "^0.16.22", 65 "sanitize-html": "^2.17.0" 66 } 67 }
+118 -38
pnpm-lock.yaml
··· 8 9 .: 10 dependencies: 11 '@atcute/lexicons': 12 specifier: ^1.1.0 13 version: 1.1.0 14 '@atproto/api': 15 - specifier: ^0.16.1 16 - version: 0.16.1 17 '@atproto/did': 18 specifier: ^0.1.5 19 version: 0.1.5 20 sanitize-html: 21 specifier: ^2.17.0 22 version: 2.17.0 23 devDependencies: 24 '@atcute/lex-cli': 25 specifier: ^2.1.1 26 version: 2.1.1 ··· 30 '@changesets/cli': 31 specifier: ^2.29.5 32 version: 2.29.5 33 '@types/sanitize-html': 34 specifier: ^2.16.0 35 version: 2.16.0 36 astro: 37 specifier: ^5.12.8 38 - version: 5.12.8(@types/node@24.2.0)(rollup@4.46.2)(typescript@5.9.2) 39 tsup: 40 specifier: ^8.5.0 41 version: 8.5.0(postcss@8.5.6)(typescript@5.9.2) ··· 44 version: 5.9.2 45 vitest: 46 specifier: ^3.2.4 47 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.0) 48 49 packages: 50 ··· 65 resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} 66 engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} 67 68 '@atcute/lex-cli@2.1.1': 69 resolution: {integrity: sha512-QaR0sOP8Z24opGHKsSfleDbP/ahUb6HECkVaOqSwG7ORZzbLK1w0265o1BRjCVr2dT6FxlsMUa2Ge85JMA9bxg==} 70 hasBin: true ··· 75 '@atcute/lexicons@1.1.0': 76 resolution: {integrity: sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q==} 77 78 - '@atproto/api@0.16.1': 79 - resolution: {integrity: sha512-w48BlTmzKym7nZETWxgiuUX/wwRXU3xsLLKORWo/xtGnwlvpchUFnHKI3k4ttYJ2/JQE59+/4C16BaLzDyiU2w==} 80 81 '@atproto/common-web@0.4.2': 82 resolution: {integrity: sha512-vrXwGNoFGogodjQvJDxAeP3QbGtawgZute2ed1XdRO0wMixLk3qewtikZm06H259QDJVu6voKC5mubml+WgQUw==} ··· 673 '@swc/helpers@0.5.17': 674 resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} 675 676 '@types/chai@5.2.2': 677 resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} 678 ··· 703 '@types/node@12.20.55': 704 resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} 705 706 - '@types/node@24.2.0': 707 - resolution: {integrity: sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==} 708 709 '@types/sanitize-html@2.16.0': 710 resolution: {integrity: sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==} ··· 800 resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 801 engines: {node: '>=12'} 802 803 - astro@5.12.8: 804 - resolution: {integrity: sha512-KkJ7FR+c2SyZYlpakm48XBiuQcRsrVtdjG5LN5an0givI/tLik+ePJ4/g3qrAVhYMjJOxBA2YgFQxANPiWB+Mw==} 805 engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} 806 hasBin: true 807 ··· 845 brotli@1.3.3: 846 resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} 847 848 bundle-require@5.1.0: 849 resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} 850 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} ··· 931 resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 932 engines: {node: '>= 6'} 933 934 common-ancestor-path@1.0.1: 935 resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} 936 ··· 966 resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 967 engines: {node: '>=4'} 968 hasBin: true 969 970 debug@4.4.1: 971 resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} ··· 1337 jsonfile@4.0.0: 1338 resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} 1339 1340 kleur@3.0.3: 1341 resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} 1342 engines: {node: '>=6'} ··· 1876 resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1877 engines: {node: '>=8'} 1878 1879 - smol-toml@1.4.1: 1880 - resolution: {integrity: sha512-CxdwHXyYTONGHThDbq5XdwbFsuY4wlClRGejfE2NtwUtiHYsP1QtNsHb/hnj31jKYSchztJsaA8pSQoVzkfCFg==} 1881 engines: {node: '>= 18'} 1882 1883 source-map-js@1.2.1: ··· 2218 yaml: 2219 optional: true 2220 2221 - vite@7.0.6: 2222 - resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} 2223 engines: {node: ^20.19.0 || >=22.12.0} 2224 hasBin: true 2225 peerDependencies: ··· 2398 remark-rehype: 11.1.2 2399 remark-smartypants: 3.0.2 2400 shiki: 3.9.2 2401 - smol-toml: 1.4.1 2402 unified: 11.0.5 2403 unist-util-remove-position: 5.0.0 2404 unist-util-visit: 5.0.0 ··· 2423 transitivePeerDependencies: 2424 - supports-color 2425 2426 '@atcute/lex-cli@2.1.1': 2427 dependencies: 2428 '@atcute/lexicon-doc': 1.0.3 ··· 2439 dependencies: 2440 esm-env: 1.2.2 2441 2442 - '@atproto/api@0.16.1': 2443 dependencies: 2444 '@atproto/common-web': 0.4.2 2445 '@atproto/lexicon': 0.4.12 ··· 3001 dependencies: 3002 tslib: 2.8.1 3003 3004 '@types/chai@5.2.2': 3005 dependencies: 3006 '@types/deep-eql': 4.0.2 ··· 3015 3016 '@types/fontkit@2.0.8': 3017 dependencies: 3018 - '@types/node': 24.2.0 3019 3020 '@types/hast@3.0.4': 3021 dependencies: ··· 3033 3034 '@types/node@12.20.55': {} 3035 3036 - '@types/node@24.2.0': 3037 dependencies: 3038 undici-types: 7.10.0 3039 3040 '@types/sanitize-html@2.16.0': 3041 dependencies: ··· 3053 chai: 5.2.1 3054 tinyrainbow: 2.0.0 3055 3056 - '@vitest/mocker@3.2.4(vite@7.0.6(@types/node@24.2.0))': 3057 dependencies: 3058 '@vitest/spy': 3.2.4 3059 estree-walker: 3.0.3 3060 magic-string: 0.30.17 3061 optionalDependencies: 3062 - vite: 7.0.6(@types/node@24.2.0) 3063 3064 '@vitest/pretty-format@3.2.4': 3065 dependencies: ··· 3126 3127 assertion-error@2.0.1: {} 3128 3129 - astro@5.12.8(@types/node@24.2.0)(rollup@4.46.2)(typescript@5.9.2): 3130 dependencies: 3131 '@astrojs/compiler': 2.12.2 3132 '@astrojs/internal-helpers': 0.7.1 ··· 3173 rehype: 13.0.2 3174 semver: 7.7.2 3175 shiki: 3.9.2 3176 - smol-toml: 1.4.1 3177 tinyexec: 0.3.2 3178 tinyglobby: 0.2.14 3179 tsconfck: 3.1.6(typescript@5.9.2) ··· 3182 unist-util-visit: 5.0.0 3183 unstorage: 1.16.1 3184 vfile: 6.0.3 3185 - vite: 6.3.5(@types/node@24.2.0) 3186 - vitefu: 1.1.1(vite@6.3.5(@types/node@24.2.0)) 3187 xxhash-wasm: 1.1.0 3188 yargs-parser: 21.1.1 3189 yocto-spinner: 0.2.3 ··· 3268 dependencies: 3269 base64-js: 1.5.1 3270 3271 bundle-require@5.1.0(esbuild@0.25.8): 3272 dependencies: 3273 esbuild: 0.25.8 ··· 3335 3336 commander@4.1.1: {} 3337 3338 common-ancestor-path@1.0.1: {} 3339 3340 confbox@0.1.8: {} ··· 3367 source-map-js: 1.2.1 3368 3369 cssesc@3.0.0: {} 3370 3371 debug@4.4.1: 3372 dependencies: ··· 3793 optionalDependencies: 3794 graceful-fs: 4.2.11 3795 3796 kleur@3.0.3: {} 3797 3798 kleur@4.1.5: {} ··· 4550 4551 slash@3.0.0: {} 4552 4553 - smol-toml@1.4.1: {} 4554 4555 source-map-js@1.2.1: {} 4556 ··· 4815 '@types/unist': 3.0.3 4816 vfile-message: 4.0.3 4817 4818 - vite-node@3.2.4(@types/node@24.2.0): 4819 dependencies: 4820 cac: 6.7.14 4821 debug: 4.4.1 4822 es-module-lexer: 1.7.0 4823 pathe: 2.0.3 4824 - vite: 7.0.6(@types/node@24.2.0) 4825 transitivePeerDependencies: 4826 - '@types/node' 4827 - jiti ··· 4836 - tsx 4837 - yaml 4838 4839 - vite@6.3.5(@types/node@24.2.0): 4840 dependencies: 4841 esbuild: 0.25.8 4842 fdir: 6.4.6(picomatch@4.0.3) ··· 4845 rollup: 4.46.2 4846 tinyglobby: 0.2.14 4847 optionalDependencies: 4848 - '@types/node': 24.2.0 4849 fsevents: 2.3.3 4850 4851 - vite@7.0.6(@types/node@24.2.0): 4852 dependencies: 4853 esbuild: 0.25.8 4854 fdir: 6.4.6(picomatch@4.0.3) ··· 4857 rollup: 4.46.2 4858 tinyglobby: 0.2.14 4859 optionalDependencies: 4860 - '@types/node': 24.2.0 4861 fsevents: 2.3.3 4862 4863 - vitefu@1.1.1(vite@6.3.5(@types/node@24.2.0)): 4864 optionalDependencies: 4865 - vite: 6.3.5(@types/node@24.2.0) 4866 4867 - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.2.0): 4868 dependencies: 4869 '@types/chai': 5.2.2 4870 '@vitest/expect': 3.2.4 4871 - '@vitest/mocker': 3.2.4(vite@7.0.6(@types/node@24.2.0)) 4872 '@vitest/pretty-format': 3.2.4 4873 '@vitest/runner': 3.2.4 4874 '@vitest/snapshot': 3.2.4 ··· 4886 tinyglobby: 0.2.14 4887 tinypool: 1.1.1 4888 tinyrainbow: 2.0.0 4889 - vite: 7.0.6(@types/node@24.2.0) 4890 - vite-node: 3.2.4(@types/node@24.2.0) 4891 why-is-node-running: 2.3.0 4892 optionalDependencies: 4893 '@types/debug': 4.1.12 4894 - '@types/node': 24.2.0 4895 transitivePeerDependencies: 4896 - jiti 4897 - less
··· 8 9 .: 10 dependencies: 11 + '@atcute/client': 12 + specifier: ^4.0.3 13 + version: 4.0.3 14 '@atcute/lexicons': 15 specifier: ^1.1.0 16 version: 1.1.0 17 '@atproto/api': 18 + specifier: ^0.16.2 19 + version: 0.16.2 20 '@atproto/did': 21 specifier: ^0.1.5 22 version: 0.1.5 23 + katex: 24 + specifier: ^0.16.22 25 + version: 0.16.22 26 sanitize-html: 27 specifier: ^2.17.0 28 version: 2.17.0 29 devDependencies: 30 + '@atcute/atproto': 31 + specifier: ^3.1.1 32 + version: 3.1.1 33 '@atcute/lex-cli': 34 specifier: ^2.1.1 35 version: 2.1.1 ··· 39 '@changesets/cli': 40 specifier: ^2.29.5 41 version: 2.29.5 42 + '@types/bun': 43 + specifier: ^1.2.19 44 + version: 1.2.19(@types/react@19.1.9) 45 '@types/sanitize-html': 46 specifier: ^2.16.0 47 version: 2.16.0 48 astro: 49 specifier: ^5.12.8 50 + version: 5.12.9(@types/node@24.2.1)(rollup@4.46.2)(typescript@5.9.2) 51 tsup: 52 specifier: ^8.5.0 53 version: 8.5.0(postcss@8.5.6)(typescript@5.9.2) ··· 56 version: 5.9.2 57 vitest: 58 specifier: ^3.2.4 59 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1) 60 61 packages: 62 ··· 77 resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} 78 engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} 79 80 + '@atcute/atproto@3.1.1': 81 + resolution: {integrity: sha512-D+RLTIPF0xLu7BPZY8KSewAPemJFh+3n3zeQ3ROsLxbTtCHbrTDMAmAFexaVRAPGcPYrwXaBUlv7yZjScJolMg==} 82 + 83 + '@atcute/client@4.0.3': 84 + resolution: {integrity: sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==} 85 + 86 + '@atcute/identity@1.0.3': 87 + resolution: {integrity: sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw==} 88 + 89 '@atcute/lex-cli@2.1.1': 90 resolution: {integrity: sha512-QaR0sOP8Z24opGHKsSfleDbP/ahUb6HECkVaOqSwG7ORZzbLK1w0265o1BRjCVr2dT6FxlsMUa2Ge85JMA9bxg==} 91 hasBin: true ··· 96 '@atcute/lexicons@1.1.0': 97 resolution: {integrity: sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q==} 98 99 + '@atproto/api@0.16.2': 100 + resolution: {integrity: sha512-sSTg31J8ws8DNaoiizp+/uJideRxRaJsq+Nyl8rnSxGw0w3oCvoeRU19iRWh2t0jZEmiRJAGkveGu23NKmPYEQ==} 101 102 '@atproto/common-web@0.4.2': 103 resolution: {integrity: sha512-vrXwGNoFGogodjQvJDxAeP3QbGtawgZute2ed1XdRO0wMixLk3qewtikZm06H259QDJVu6voKC5mubml+WgQUw==} ··· 694 '@swc/helpers@0.5.17': 695 resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} 696 697 + '@types/bun@1.2.19': 698 + resolution: {integrity: sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg==} 699 + 700 '@types/chai@5.2.2': 701 resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} 702 ··· 727 '@types/node@12.20.55': 728 resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} 729 730 + '@types/node@24.2.1': 731 + resolution: {integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==} 732 + 733 + '@types/react@19.1.9': 734 + resolution: {integrity: sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==} 735 736 '@types/sanitize-html@2.16.0': 737 resolution: {integrity: sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==} ··· 827 resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 828 engines: {node: '>=12'} 829 830 + astro@5.12.9: 831 + resolution: {integrity: sha512-cZ7kZ61jyE5nwSrFKSRyf5Gds+uJELqQxJFqMkcgiWQvhWZJUSShn8Uz3yc9WLyLw5Kim5P5un9SkJSGogfEZQ==} 832 engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} 833 hasBin: true 834 ··· 872 brotli@1.3.3: 873 resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} 874 875 + bun-types@1.2.19: 876 + resolution: {integrity: sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ==} 877 + peerDependencies: 878 + '@types/react': ^19 879 + 880 bundle-require@5.1.0: 881 resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} 882 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} ··· 963 resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 964 engines: {node: '>= 6'} 965 966 + commander@8.3.0: 967 + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} 968 + engines: {node: '>= 12'} 969 + 970 common-ancestor-path@1.0.1: 971 resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} 972 ··· 1002 resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 1003 engines: {node: '>=4'} 1004 hasBin: true 1005 + 1006 + csstype@3.1.3: 1007 + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 1008 1009 debug@4.4.1: 1010 resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} ··· 1376 jsonfile@4.0.0: 1377 resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} 1378 1379 + katex@0.16.22: 1380 + resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==} 1381 + hasBin: true 1382 + 1383 kleur@3.0.3: 1384 resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} 1385 engines: {node: '>=6'} ··· 1919 resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1920 engines: {node: '>=8'} 1921 1922 + smol-toml@1.4.2: 1923 + resolution: {integrity: sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==} 1924 engines: {node: '>= 18'} 1925 1926 source-map-js@1.2.1: ··· 2261 yaml: 2262 optional: true 2263 2264 + vite@7.1.1: 2265 + resolution: {integrity: sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==} 2266 engines: {node: ^20.19.0 || >=22.12.0} 2267 hasBin: true 2268 peerDependencies: ··· 2441 remark-rehype: 11.1.2 2442 remark-smartypants: 3.0.2 2443 shiki: 3.9.2 2444 + smol-toml: 1.4.2 2445 unified: 11.0.5 2446 unist-util-remove-position: 5.0.0 2447 unist-util-visit: 5.0.0 ··· 2466 transitivePeerDependencies: 2467 - supports-color 2468 2469 + '@atcute/atproto@3.1.1': 2470 + dependencies: 2471 + '@atcute/lexicons': 1.1.0 2472 + 2473 + '@atcute/client@4.0.3': 2474 + dependencies: 2475 + '@atcute/identity': 1.0.3 2476 + '@atcute/lexicons': 1.1.0 2477 + 2478 + '@atcute/identity@1.0.3': 2479 + dependencies: 2480 + '@atcute/lexicons': 1.1.0 2481 + '@badrap/valita': 0.4.6 2482 + 2483 '@atcute/lex-cli@2.1.1': 2484 dependencies: 2485 '@atcute/lexicon-doc': 1.0.3 ··· 2496 dependencies: 2497 esm-env: 1.2.2 2498 2499 + '@atproto/api@0.16.2': 2500 dependencies: 2501 '@atproto/common-web': 0.4.2 2502 '@atproto/lexicon': 0.4.12 ··· 3058 dependencies: 3059 tslib: 2.8.1 3060 3061 + '@types/bun@1.2.19(@types/react@19.1.9)': 3062 + dependencies: 3063 + bun-types: 1.2.19(@types/react@19.1.9) 3064 + transitivePeerDependencies: 3065 + - '@types/react' 3066 + 3067 '@types/chai@5.2.2': 3068 dependencies: 3069 '@types/deep-eql': 4.0.2 ··· 3078 3079 '@types/fontkit@2.0.8': 3080 dependencies: 3081 + '@types/node': 24.2.1 3082 3083 '@types/hast@3.0.4': 3084 dependencies: ··· 3096 3097 '@types/node@12.20.55': {} 3098 3099 + '@types/node@24.2.1': 3100 dependencies: 3101 undici-types: 7.10.0 3102 + 3103 + '@types/react@19.1.9': 3104 + dependencies: 3105 + csstype: 3.1.3 3106 3107 '@types/sanitize-html@2.16.0': 3108 dependencies: ··· 3120 chai: 5.2.1 3121 tinyrainbow: 2.0.0 3122 3123 + '@vitest/mocker@3.2.4(vite@7.1.1(@types/node@24.2.1))': 3124 dependencies: 3125 '@vitest/spy': 3.2.4 3126 estree-walker: 3.0.3 3127 magic-string: 0.30.17 3128 optionalDependencies: 3129 + vite: 7.1.1(@types/node@24.2.1) 3130 3131 '@vitest/pretty-format@3.2.4': 3132 dependencies: ··· 3193 3194 assertion-error@2.0.1: {} 3195 3196 + astro@5.12.9(@types/node@24.2.1)(rollup@4.46.2)(typescript@5.9.2): 3197 dependencies: 3198 '@astrojs/compiler': 2.12.2 3199 '@astrojs/internal-helpers': 0.7.1 ··· 3240 rehype: 13.0.2 3241 semver: 7.7.2 3242 shiki: 3.9.2 3243 + smol-toml: 1.4.2 3244 tinyexec: 0.3.2 3245 tinyglobby: 0.2.14 3246 tsconfck: 3.1.6(typescript@5.9.2) ··· 3249 unist-util-visit: 5.0.0 3250 unstorage: 1.16.1 3251 vfile: 6.0.3 3252 + vite: 6.3.5(@types/node@24.2.1) 3253 + vitefu: 1.1.1(vite@6.3.5(@types/node@24.2.1)) 3254 xxhash-wasm: 1.1.0 3255 yargs-parser: 21.1.1 3256 yocto-spinner: 0.2.3 ··· 3335 dependencies: 3336 base64-js: 1.5.1 3337 3338 + bun-types@1.2.19(@types/react@19.1.9): 3339 + dependencies: 3340 + '@types/node': 24.2.1 3341 + '@types/react': 19.1.9 3342 + 3343 bundle-require@5.1.0(esbuild@0.25.8): 3344 dependencies: 3345 esbuild: 0.25.8 ··· 3407 3408 commander@4.1.1: {} 3409 3410 + commander@8.3.0: {} 3411 + 3412 common-ancestor-path@1.0.1: {} 3413 3414 confbox@0.1.8: {} ··· 3441 source-map-js: 1.2.1 3442 3443 cssesc@3.0.0: {} 3444 + 3445 + csstype@3.1.3: {} 3446 3447 debug@4.4.1: 3448 dependencies: ··· 3869 optionalDependencies: 3870 graceful-fs: 4.2.11 3871 3872 + katex@0.16.22: 3873 + dependencies: 3874 + commander: 8.3.0 3875 + 3876 kleur@3.0.3: {} 3877 3878 kleur@4.1.5: {} ··· 4630 4631 slash@3.0.0: {} 4632 4633 + smol-toml@1.4.2: {} 4634 4635 source-map-js@1.2.1: {} 4636 ··· 4895 '@types/unist': 3.0.3 4896 vfile-message: 4.0.3 4897 4898 + vite-node@3.2.4(@types/node@24.2.1): 4899 dependencies: 4900 cac: 6.7.14 4901 debug: 4.4.1 4902 es-module-lexer: 1.7.0 4903 pathe: 2.0.3 4904 + vite: 7.1.1(@types/node@24.2.1) 4905 transitivePeerDependencies: 4906 - '@types/node' 4907 - jiti ··· 4916 - tsx 4917 - yaml 4918 4919 + vite@6.3.5(@types/node@24.2.1): 4920 dependencies: 4921 esbuild: 0.25.8 4922 fdir: 6.4.6(picomatch@4.0.3) ··· 4925 rollup: 4.46.2 4926 tinyglobby: 0.2.14 4927 optionalDependencies: 4928 + '@types/node': 24.2.1 4929 fsevents: 2.3.3 4930 4931 + vite@7.1.1(@types/node@24.2.1): 4932 dependencies: 4933 esbuild: 0.25.8 4934 fdir: 6.4.6(picomatch@4.0.3) ··· 4937 rollup: 4.46.2 4938 tinyglobby: 0.2.14 4939 optionalDependencies: 4940 + '@types/node': 24.2.1 4941 fsevents: 2.3.3 4942 4943 + vitefu@1.1.1(vite@6.3.5(@types/node@24.2.1)): 4944 optionalDependencies: 4945 + vite: 6.3.5(@types/node@24.2.1) 4946 4947 + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.2.1): 4948 dependencies: 4949 '@types/chai': 5.2.2 4950 '@vitest/expect': 3.2.4 4951 + '@vitest/mocker': 3.2.4(vite@7.1.1(@types/node@24.2.1)) 4952 '@vitest/pretty-format': 3.2.4 4953 '@vitest/runner': 3.2.4 4954 '@vitest/snapshot': 3.2.4 ··· 4966 tinyglobby: 0.2.14 4967 tinypool: 1.1.1 4968 tinyrainbow: 2.0.0 4969 + vite: 7.1.1(@types/node@24.2.1) 4970 + vite-node: 3.2.4(@types/node@24.2.1) 4971 why-is-node-running: 2.3.0 4972 optionalDependencies: 4973 '@types/debug': 4.1.12 4974 + '@types/node': 24.2.1 4975 transitivePeerDependencies: 4976 - jiti 4977 - less
-5
pnpm-workspace.yaml
··· 1 - ignoredBuiltDependencies: 2 - - sharp 3 - 4 - onlyBuiltDependencies: 5 - - esbuild
···