-13
.changeset/plain-beans-taste.md
-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
-17
.changeset/true-terms-start.md
-17
.changeset/true-terms-start.md
···
1
-
---
2
-
"@nulfrost/leaflet-loader-astro": minor
3
-
---
4
-
5
-
Added the ability to use a handle or did when specifying a repo for leafletStaticLoader and leafletLiveLoader
6
-
7
-
8
-
```ts
9
-
import { defineLiveCollection} from "astro:content";
10
-
import { leafletLiveLoader } from "leaflet-loader-astro";
11
-
12
-
const documents = defineLiveCollection({
13
-
loader: leafletLiveLoader({ repo: "dane.computer" }), // or repo: did:plc:qttsv4e7pu2jl3ilanfgc3zn, both work!
14
-
});
15
-
16
-
export const collections = { documents };
17
-
```
+43
CHANGELOG.md
+43
CHANGELOG.md
···
1
1
# leaflet-loader-astro
2
2
3
+
## 1.3.0
4
+
5
+
### Minor Changes
6
+
7
+
- eb3bc4b: Add iframe block
8
+
9
+
## 1.2.0
10
+
11
+
### Minor Changes
12
+
13
+
- f920153: Add support for blockquotes
14
+
15
+
### Patch Changes
16
+
17
+
- 8922bb1: Add JSDoc comments for available loader options for leafletStaticLoader and leafletLiveLoader
18
+
19
+
## 1.1.0
20
+
21
+
### Minor Changes
22
+
23
+
- 6d70cc6: Added support for these leaflet blocks:
24
+
25
+
- ul/li
26
+
- math
27
+
- code
28
+
- img
29
+
- hr
30
+
31
+
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
32
+
33
+
- 5524ce5: Added the ability to use a handle or did when specifying a repo for leafletStaticLoader and leafletLiveLoader
34
+
35
+
```ts
36
+
import { defineLiveCollection } from "astro:content";
37
+
import { leafletLiveLoader } from "leaflet-loader-astro";
38
+
39
+
const documents = defineLiveCollection({
40
+
loader: leafletLiveLoader({ repo: "dane.computer" }), // or repo: did:plc:qttsv4e7pu2jl3ilanfgc3zn, both work!
41
+
});
42
+
43
+
export const collections = { documents };
44
+
```
45
+
3
46
## 1.0.0
4
47
5
48
### Major Changes
+57
-4
README.md
+57
-4
README.md
···
15
15
16
16
## Usage
17
17
18
-
### Build-time loader: leafletStaticLoader (recommended)
18
+
<details>
19
+
<summary>Build-time loader: leafletStaticLoader **(recommended)**</summary>
19
20
20
21
```ts
21
22
// src/content.config.ts
···
23
24
import { leafletStaticLoader } from "@nulfrost/leaflet-loader-astro";
24
25
25
26
const documents = defineCollection({
26
-
loader: leafletStaticLoader({ repo: "did:plc:qttsv4e7pu2jl3ilanfgc3zn" }),
27
+
loader: leafletStaticLoader({ repo: "did:plc:qttsv4e7pu2jl3ilanfgc3zn" }), // or repo: dane.is.extraordinarily.cool
27
28
});
28
29
29
30
export const collections = { documents };
···
81
82
82
83
<Content />
83
84
```
85
+
</details>
84
86
85
-
### Live loader: leafletLiveLoader
87
+
<details>
88
+
<summary>Live loader: leafletLiveLoader</summary>
86
89
87
90
```ts
88
91
// astro.config.mjs
···
104
107
import { leafletLiveLoader } from "@nulfrost/leaflet-loader-astro";
105
108
106
109
const documents = defineLiveCollection({
107
-
loader: leafletLiveLoader({ repo: "did:plc:qttsv4e7pu2jl3ilanfgc3zn" }),
110
+
loader: leafletLiveLoader({ repo: "did:plc:qttsv4e7pu2jl3ilanfgc3zn" }), // or repo: dane.is.extraordinarily.cool
108
111
});
109
112
110
113
export const collections = { documents };
···
158
161
159
162
<Content />
160
163
```
164
+
165
+
</details>
166
+
167
+
## Loader Options
168
+
169
+
### Static Loader
170
+
171
+
```ts
172
+
leafletStaticLoader()
173
+
```
174
+
175
+
`repo`: This can be either your DID (did:plc:qttsv4e7pu2jl3ilanfgc3zn) or your handle (dane.is.extraordinarily.cool)
176
+
177
+
`limit`: How many leaflet documents to return when calling `getCollection`. The default is 50 and the range is from 1 to 100.
178
+
179
+
`reverse`: Whether or not to return the leaflet documents in reverse order. By default this is false.
180
+
181
+
### Live Loader
182
+
183
+
```ts
184
+
leafletLiveLoader()
185
+
```
186
+
187
+
`repo`: This can be either your DID (did:plc:qttsv4e7pu2jl3ilanfgc3zn) or your handle (dane.is.extraordinarily.cool)
188
+
189
+
> [!NOTE]
190
+
> `getLiveCollection` supports a second argument where you can add additional filters, similar to the options you have access to for `leafletStaticLoader`
191
+
192
+
```ts
193
+
getLiveCollection()
194
+
```
195
+
196
+
`limit`: How many leaflet documents to return when calling `getCollection`. The default is 50 and the range is from 1 to 100.
197
+
198
+
`reverse`: Whether or not to return the leaflet documents in reverse order. By default this is false.
199
+
200
+
## Supported Leaflet Blocks
201
+
202
+
- [ ] Bluesky post
203
+
- [x] Iframe
204
+
- [x] Horizontal Rule
205
+
- [x] Unordered List
206
+
- [x] Math
207
+
- [x] Code
208
+
- [ ] Website
209
+
- [x] Image
210
+
- [x] Blockquote
211
+
- [x] Text
212
+
- [x] Header
213
+
- [x] List Item
161
214
162
215
## License
163
216
+22
lexicons/pub/leaflet/blocks/blockquote.json
+22
lexicons/pub/leaflet/blocks/blockquote.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "pub.leaflet.blocks.blockquote",
4
+
"defs": {
5
+
"main": {
6
+
"type": "object",
7
+
"required": ["plaintext"],
8
+
"properties": {
9
+
"plaintext": {
10
+
"type": "string"
11
+
},
12
+
"facets": {
13
+
"type": "array",
14
+
"items": {
15
+
"type": "ref",
16
+
"ref": "pub.leaflet.richtext.facet"
17
+
}
18
+
}
19
+
}
20
+
}
21
+
}
22
+
}
+21
lexicons/pub/leaflet/blocks/iframe.json
+21
lexicons/pub/leaflet/blocks/iframe.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "pub.leaflet.blocks.iframe",
4
+
"defs": {
5
+
"main": {
6
+
"type": "object",
7
+
"required": ["url"],
8
+
"properties": {
9
+
"url": {
10
+
"type": "string",
11
+
"format": "uri"
12
+
},
13
+
"height": {
14
+
"type": "integer",
15
+
"minimum": 16,
16
+
"maximum": 1600
17
+
}
18
+
}
19
+
}
20
+
}
21
+
}
+3
-3
lib/leaftlet-static-loader.ts
+3
-3
lib/leaftlet-static-loader.ts
···
1
1
import { Client, simpleFetchHandler } from "@atcute/client";
2
2
import { isHandle } from "@atcute/lexicons/syntax";
3
-
import { isDid } from "@atproto/did";
4
3
import type { Loader, LoaderContext } from "astro/loaders";
5
4
import { LeafletDocumentSchema } from "schema.js";
6
5
import type {
···
20
19
export function leafletStaticLoader(
21
20
options: StaticLeafletLoaderOptions,
22
21
): Loader {
23
-
const { repo, limit } = options;
22
+
const { repo, limit, reverse } = options;
24
23
25
24
if (!repo || typeof repo !== "string") {
26
25
throw new LiveLoaderError(
···
64
63
rpc,
65
64
repo,
66
65
cursor,
67
-
limit: 100,
66
+
reverse,
67
+
limit: 50,
68
68
});
69
69
for (const document of documents) {
70
70
if (limit && count >= limit) {
+2
lib/lexicons/index.ts
+2
lib/lexicons/index.ts
···
1
1
export * as ComAtprotoRepoStrongRef from "./types/com/atproto/repo/strongRef.js";
2
+
export * as PubLeafletBlocksBlockquote from "./types/pub/leaflet/blocks/blockquote.js";
2
3
export * as PubLeafletBlocksCode from "./types/pub/leaflet/blocks/code.js";
3
4
export * as PubLeafletBlocksHeader from "./types/pub/leaflet/blocks/header.js";
4
5
export * as PubLeafletBlocksHorizontalRule from "./types/pub/leaflet/blocks/horizontalRule.js";
6
+
export * as PubLeafletBlocksIframe from "./types/pub/leaflet/blocks/iframe.js";
5
7
export * as PubLeafletBlocksImage from "./types/pub/leaflet/blocks/image.js";
6
8
export * as PubLeafletBlocksMath from "./types/pub/leaflet/blocks/math.js";
7
9
export * as PubLeafletBlocksText from "./types/pub/leaflet/blocks/text.js";
+23
lib/lexicons/types/pub/leaflet/blocks/blockquote.ts
+23
lib/lexicons/types/pub/leaflet/blocks/blockquote.ts
···
1
+
import type {} from "@atcute/lexicons";
2
+
import * as v from "@atcute/lexicons/validations";
3
+
import * as PubLeafletRichtextFacet from "../richtext/facet.js";
4
+
5
+
const _mainSchema = /*#__PURE__*/ v.object({
6
+
$type: /*#__PURE__*/ v.optional(
7
+
/*#__PURE__*/ v.literal("pub.leaflet.blocks.blockquote"),
8
+
),
9
+
get facets() {
10
+
return /*#__PURE__*/ v.optional(
11
+
/*#__PURE__*/ v.array(PubLeafletRichtextFacet.mainSchema),
12
+
);
13
+
},
14
+
plaintext: /*#__PURE__*/ v.string(),
15
+
});
16
+
17
+
type main$schematype = typeof _mainSchema;
18
+
19
+
export interface mainSchema extends main$schematype {}
20
+
21
+
export const mainSchema = _mainSchema as mainSchema;
22
+
23
+
export interface Main extends v.InferInput<typeof mainSchema> {}
+22
lib/lexicons/types/pub/leaflet/blocks/iframe.ts
+22
lib/lexicons/types/pub/leaflet/blocks/iframe.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.iframe"),
7
+
),
8
+
height: /*#__PURE__*/ v.optional(
9
+
/*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [
10
+
/*#__PURE__*/ v.integerRange(16, 1600),
11
+
]),
12
+
),
13
+
url: /*#__PURE__*/ v.genericUriString(),
14
+
});
15
+
16
+
type main$schematype = typeof _mainSchema;
17
+
18
+
export interface mainSchema extends main$schematype {}
19
+
20
+
export const mainSchema = _mainSchema as mainSchema;
21
+
22
+
export interface Main extends v.InferInput<typeof mainSchema> {}
+1
-1
lib/lexicons/types/pub/leaflet/blocks/image.ts
+1
-1
lib/lexicons/types/pub/leaflet/blocks/image.ts
+8
-3
lib/types.ts
+8
-3
lib/types.ts
···
5
5
6
6
export interface LiveLeafletLoaderOptions {
7
7
/**
8
-
* @description Your repo is your DID (did:plc... or did:web...). You can find this information using: https://pdsls.dev
8
+
* @description Your repo is your DID (did:plc... or did:web...) or handle (username.bsky.social). You can find this information using: https://pdsls.dev
9
9
*/
10
10
repo: string;
11
11
}
12
12
13
13
export interface StaticLeafletLoaderOptions {
14
14
/**
15
-
* @description Your repo is your DID (did:plc... or did:web...). You can find this information using: https://pdsls.dev
15
+
* @description Your repo is your DID (did:plc... or did:web...) or handle (username.bsky.social). You can find this information using: https://pdsls.dev
16
16
*/
17
17
repo: string;
18
-
filter?: string;
19
18
/**
19
+
* @description The number of records leaflet records to return for getCollection, the default being 50. The range can be from 1 to 100.
20
20
* @default 50
21
21
*/
22
22
limit?: number;
23
+
/**
24
+
* @description Whether or not the records should be returned in reverse order.
25
+
* @default undefined
26
+
*/
27
+
reverse?: boolean;
23
28
}
24
29
25
30
export interface LeafletDocumentRecord {
+69
-49
lib/utils.ts
+69
-49
lib/utils.ts
···
1
1
import type {} from "@atcute/atproto";
2
-
import { type Handle, is } from "@atcute/lexicons";
2
+
import { is } from "@atcute/lexicons";
3
3
import { AtUri, UnicodeString } from "@atproto/api";
4
4
import katex from "katex";
5
5
import sanitizeHTML from "sanitize-html";
6
6
import {
7
+
PubLeafletBlocksBlockquote,
7
8
PubLeafletBlocksCode,
8
9
PubLeafletBlocksHeader,
9
10
PubLeafletBlocksHorizontalRule,
11
+
PubLeafletBlocksIframe,
10
12
PubLeafletBlocksImage,
11
13
PubLeafletBlocksMath,
12
14
PubLeafletBlocksText,
···
161
163
"*": ["class", "style"],
162
164
img: ["src", "height", "width", "alt"],
163
165
a: ["href", "target", "rel"],
166
+
iframe: ["height", "allow", "loading", "src"],
164
167
},
165
168
allowedTags: [
166
169
"img",
···
182
185
"hr",
183
186
"div",
184
187
"span",
188
+
"blockquote",
189
+
"iframe",
185
190
],
186
191
selfClosing: ["img"],
187
192
});
···
244
249
}
245
250
}
246
251
247
-
function parseBlocks({
252
+
export function parseTextBlock(block: PubLeafletBlocksText.Main) {
253
+
let html = "";
254
+
const rt = new RichText({
255
+
text: block.plaintext,
256
+
facets: block.facets || [],
257
+
});
258
+
const children = [];
259
+
for (const segment of rt.segments()) {
260
+
const link = segment.facet?.find(
261
+
(segment) => segment.$type === "pub.leaflet.richtext.facet#link",
262
+
);
263
+
const isBold = segment.facet?.find(
264
+
(segment) => segment.$type === "pub.leaflet.richtext.facet#bold",
265
+
);
266
+
const isCode = segment.facet?.find(
267
+
(segment) => segment.$type === "pub.leaflet.richtext.facet#code",
268
+
);
269
+
const isStrikethrough = segment.facet?.find(
270
+
(segment) => segment.$type === "pub.leaflet.richtext.facet#strikethrough",
271
+
);
272
+
const isUnderline = segment.facet?.find(
273
+
(segment) => segment.$type === "pub.leaflet.richtext.facet#underline",
274
+
);
275
+
const isItalic = segment.facet?.find(
276
+
(segment) => segment.$type === "pub.leaflet.richtext.facet#italic",
277
+
);
278
+
if (isCode) {
279
+
children.push(`<pre><code>${segment.text}</code></pre>`);
280
+
} else if (link) {
281
+
children.push(
282
+
`<a href="${link.uri}" target="_blank" rel="noopener noreferrer">${segment.text}</a>`,
283
+
);
284
+
} else if (isBold) {
285
+
children.push(`<b>${segment.text}</b>`);
286
+
} else if (isStrikethrough) {
287
+
children.push(`<s>${segment.text}</s>`);
288
+
} else if (isUnderline) {
289
+
children.push(
290
+
`<span style="text-decoration:underline;">${segment.text}</span>`,
291
+
);
292
+
} else if (isItalic) {
293
+
children.push(`<i>${segment.text}</i>`);
294
+
} else {
295
+
children.push(`${segment.text}`);
296
+
}
297
+
}
298
+
html += `<p>${children.join("")}</p>`;
299
+
300
+
return html.trim();
301
+
}
302
+
303
+
export function parseBlocks({
248
304
block,
249
305
did,
250
306
}: {
···
254
310
let html = "";
255
311
256
312
if (is(PubLeafletBlocksText.mainSchema, block.block)) {
257
-
const rt = new RichText({
258
-
text: block.block.plaintext,
259
-
facets: block.block.facets || [],
260
-
});
261
-
const children = [];
262
-
for (const segment of rt.segments()) {
263
-
const link = segment.facet?.find(
264
-
(segment) => segment.$type === "pub.leaflet.richtext.facet#link",
265
-
);
266
-
const isBold = segment.facet?.find(
267
-
(segment) => segment.$type === "pub.leaflet.richtext.facet#bold",
268
-
);
269
-
const isCode = segment.facet?.find(
270
-
(segment) => segment.$type === "pub.leaflet.richtext.facet#code",
271
-
);
272
-
const isStrikethrough = segment.facet?.find(
273
-
(segment) =>
274
-
segment.$type === "pub.leaflet.richtext.facet#strikethrough",
275
-
);
276
-
const isUnderline = segment.facet?.find(
277
-
(segment) => segment.$type === "pub.leaflet.richtext.facet#underline",
278
-
);
279
-
const isItalic = segment.facet?.find(
280
-
(segment) => segment.$type === "pub.leaflet.richtext.facet#italic",
281
-
);
282
-
if (isCode) {
283
-
children.push(`<pre><code>${segment.text}</code></pre>`);
284
-
} else if (link) {
285
-
children.push(
286
-
`<a href="${link.uri}" target="_blank" rel="noopener noreferrer">${segment.text}</a>`,
287
-
);
288
-
} else if (isBold) {
289
-
children.push(`<b>${segment.text}</b>`);
290
-
} else if (isStrikethrough) {
291
-
children.push(`<s>${segment.text}</s>`);
292
-
} else if (isUnderline) {
293
-
children.push(
294
-
`<span style="text-decoration:underline;">${segment.text}</span>`,
295
-
);
296
-
} else if (isItalic) {
297
-
children.push(`<i>${segment.text}</i>`);
298
-
} else {
299
-
children.push(`${segment.text}`);
300
-
}
301
-
}
302
-
html += `<p>${children.join("")}</p>`;
313
+
html += parseTextBlock(block.block);
303
314
}
304
315
305
316
if (is(PubLeafletBlocksHeader.mainSchema, block.block)) {
···
343
354
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>`;
344
355
}
345
356
357
+
if (is(PubLeafletBlocksBlockquote.mainSchema, block.block)) {
358
+
html += `<blockquote>${parseTextBlock(block.block)}</blockquote>`;
359
+
}
360
+
361
+
if (is(PubLeafletBlocksIframe.mainSchema, block.block)) {
362
+
// @ts-ignore
363
+
html += `<iframe height="${block.block.height}" allow="fullscreen" loading="lazy" src="${block.block.url}"></iframe>`;
364
+
}
365
+
346
366
return html.trim();
347
367
}
348
368
349
-
function renderListItem({
369
+
export function renderListItem({
350
370
item,
351
371
did,
352
372
}: {
+2
-2
package.json
+2
-2
package.json
···
1
1
{
2
2
"name": "@nulfrost/leaflet-loader-astro",
3
-
"version": "1.0.0",
3
+
"version": "1.3.0",
4
4
"description": "A leaflet.pub astro collection loader",
5
5
"keywords": [
6
6
"astro",
···
12
12
"bugs": {
13
13
"url": "https://github.com/nulfrost/leaflet-loader-astro/issues"
14
14
},
15
-
"author": "Dane Miller",
15
+
"author": "Dane Miller <me@dane.computer>",
16
16
"repository": {
17
17
"type": "git",
18
18
"url": "git+https://github.com/nulfrost/leaflet-loader-astro.git"
+189
tests/parse-blocks.test.ts
+189
tests/parse-blocks.test.ts
···
1
+
import { expect, test } from "vitest";
2
+
import { parseBlocks } from "../lib/utils";
3
+
4
+
test("should correctly parse an h1 block to an h2 tag", () => {
5
+
const html = parseBlocks({
6
+
block: {
7
+
$type: "pub.leaflet.pages.linearDocument#block",
8
+
block: {
9
+
$type: "pub.leaflet.blocks.header",
10
+
level: 1,
11
+
facets: [],
12
+
plaintext: "heading 1",
13
+
},
14
+
},
15
+
did: "did:plc:qttsv4e7pu2jl3ilanfgc3zn",
16
+
});
17
+
18
+
expect(html).toMatchInlineSnapshot(`"<h2>heading 1</h2>"`);
19
+
});
20
+
21
+
test("should correctly parse an h2 block to an h3 tag", () => {
22
+
const html = parseBlocks({
23
+
block: {
24
+
$type: "pub.leaflet.pages.linearDocument#block",
25
+
block: {
26
+
$type: "pub.leaflet.blocks.header",
27
+
level: 2,
28
+
facets: [],
29
+
plaintext: "heading 2",
30
+
},
31
+
},
32
+
did: "did:plc:qttsv4e7pu2jl3ilanfgc3zn",
33
+
});
34
+
35
+
expect(html).toMatchInlineSnapshot(`"<h3>heading 2</h3>"`);
36
+
});
37
+
38
+
test("should correctly parse an h3 block to an h4 tag", () => {
39
+
const html = parseBlocks({
40
+
block: {
41
+
$type: "pub.leaflet.pages.linearDocument#block",
42
+
block: {
43
+
$type: "pub.leaflet.blocks.header",
44
+
level: 3,
45
+
facets: [],
46
+
plaintext: "heading 3",
47
+
},
48
+
},
49
+
did: "did:plc:qttsv4e7pu2jl3ilanfgc3zn",
50
+
});
51
+
52
+
expect(html).toMatchInlineSnapshot(`"<h4>heading 3</h4>"`);
53
+
});
54
+
55
+
test("should correctly parse a block with no level to an h6 tag", () => {
56
+
const html = parseBlocks({
57
+
block: {
58
+
$type: "pub.leaflet.pages.linearDocument#block",
59
+
block: {
60
+
$type: "pub.leaflet.blocks.header",
61
+
facets: [],
62
+
plaintext: "heading 6",
63
+
},
64
+
},
65
+
did: "did:plc:qttsv4e7pu2jl3ilanfgc3zn",
66
+
});
67
+
68
+
expect(html).toMatchInlineSnapshot(`"<h6>heading 6</h6>"`);
69
+
});
70
+
71
+
test("should correctly parse an unordered list block", () => {
72
+
const html = parseBlocks({
73
+
block: {
74
+
$type: "pub.leaflet.pages.linearDocument#block",
75
+
block: {
76
+
$type: "pub.leaflet.blocks.unorderedList",
77
+
children: [
78
+
{
79
+
$type: "pub.leaflet.blocks.unorderedList#listItem",
80
+
content: {
81
+
$type: "pub.leaflet.blocks.text",
82
+
facets: [
83
+
{
84
+
index: {
85
+
byteEnd: 18,
86
+
byteStart: 0,
87
+
},
88
+
features: [
89
+
{
90
+
uri: "https://pdsls.dev/",
91
+
$type: "pub.leaflet.richtext.facet#link",
92
+
},
93
+
],
94
+
},
95
+
{
96
+
index: {
97
+
byteEnd: 28,
98
+
byteStart: 22,
99
+
},
100
+
features: [
101
+
{
102
+
uri: "https://bsky.app/profile/juli.ee",
103
+
$type: "pub.leaflet.richtext.facet#link",
104
+
},
105
+
],
106
+
},
107
+
],
108
+
plaintext: "https://pdsls.dev/ by Juliet",
109
+
},
110
+
children: [],
111
+
},
112
+
{
113
+
$type: "pub.leaflet.blocks.unorderedList#listItem",
114
+
content: {
115
+
$type: "pub.leaflet.blocks.text",
116
+
facets: [
117
+
{
118
+
index: {
119
+
byteEnd: 34,
120
+
byteStart: 0,
121
+
},
122
+
features: [
123
+
{
124
+
uri: "https://github.com/mary-ext/atcute",
125
+
$type: "pub.leaflet.richtext.facet#link",
126
+
},
127
+
],
128
+
},
129
+
{
130
+
index: {
131
+
byteEnd: 42,
132
+
byteStart: 38,
133
+
},
134
+
features: [
135
+
{
136
+
uri: "https://bsky.app/profile/mary.my.id",
137
+
$type: "pub.leaflet.richtext.facet#link",
138
+
},
139
+
],
140
+
},
141
+
],
142
+
plaintext: "https://github.com/mary-ext/atcute by mary",
143
+
},
144
+
children: [],
145
+
},
146
+
{
147
+
$type: "pub.leaflet.blocks.unorderedList#listItem",
148
+
content: {
149
+
$type: "pub.leaflet.blocks.text",
150
+
facets: [
151
+
{
152
+
index: {
153
+
byteEnd: 27,
154
+
byteStart: 0,
155
+
},
156
+
features: [
157
+
{
158
+
uri: "https://www.microcosm.blue/",
159
+
$type: "pub.leaflet.richtext.facet#link",
160
+
},
161
+
],
162
+
},
163
+
{
164
+
index: {
165
+
byteEnd: 35,
166
+
byteStart: 31,
167
+
},
168
+
features: [
169
+
{
170
+
uri: "https://bsky.app/profile/bad-example.com",
171
+
$type: "pub.leaflet.richtext.facet#link",
172
+
},
173
+
],
174
+
},
175
+
],
176
+
plaintext: "https://www.microcosm.blue/ by phil",
177
+
},
178
+
children: [],
179
+
},
180
+
],
181
+
},
182
+
},
183
+
did: "did:plc:qttsv4e7pu2jl3ilanfgc3zn",
184
+
});
185
+
186
+
expect(html).toMatchInlineSnapshot(
187
+
`"<ul><li><p><a href="https://pdsls.dev/" target="_blank" rel="noopener noreferrer">https://pdsls.dev/</a> by <a href="https://bsky.app/profile/juli.ee" target="_blank" rel="noopener noreferrer">Juliet</a></p></li><li><p><a href="https://github.com/mary-ext/atcute" target="_blank" rel="noopener noreferrer">https://github.com/mary-ext/atcute</a> by <a href="https://bsky.app/profile/mary.my.id" target="_blank" rel="noopener noreferrer">mary</a></p></li><li><p><a href="https://www.microcosm.blue/" target="_blank" rel="noopener noreferrer">https://www.microcosm.blue/</a> by <a href="https://bsky.app/profile/bad-example.com" target="_blank" rel="noopener noreferrer">phil</a></p></li></ul>"`,
188
+
);
189
+
});
+133
tests/parse-text-blocks.test.ts
+133
tests/parse-text-blocks.test.ts
···
1
+
import { expect, test } from "vitest";
2
+
import { parseTextBlock } from "../lib/utils";
3
+
4
+
test("should correctly parse a text block without facets", () => {
5
+
const html = parseTextBlock({
6
+
$type: "pub.leaflet.blocks.text",
7
+
facets: [],
8
+
plaintext: "just plaintext no facets",
9
+
});
10
+
11
+
expect(html).toMatchInlineSnapshot(`"<p>just plaintext no facets</p>"`);
12
+
});
13
+
14
+
test("should correctly parse a text block with bolded text", () => {
15
+
const html = parseTextBlock({
16
+
$type: "pub.leaflet.blocks.text",
17
+
facets: [
18
+
{
19
+
index: {
20
+
byteEnd: 11,
21
+
byteStart: 0,
22
+
},
23
+
features: [
24
+
{
25
+
$type: "pub.leaflet.richtext.facet#bold",
26
+
},
27
+
],
28
+
},
29
+
],
30
+
plaintext: "bolded text with some plaintext",
31
+
});
32
+
33
+
expect(html).toMatchInlineSnapshot(
34
+
`"<p><b>bolded text</b> with some plaintext</p>"`,
35
+
);
36
+
});
37
+
38
+
test("should correctly parse a text block with an inline link", () => {
39
+
const html = parseTextBlock({
40
+
$type: "pub.leaflet.blocks.text",
41
+
facets: [
42
+
{
43
+
index: {
44
+
byteEnd: 27,
45
+
byteStart: 0,
46
+
},
47
+
features: [
48
+
{
49
+
uri: "https://blacksky.community/",
50
+
$type: "pub.leaflet.richtext.facet#link",
51
+
},
52
+
],
53
+
},
54
+
],
55
+
plaintext: "https://blacksky.community/",
56
+
});
57
+
58
+
expect(html).toMatchInlineSnapshot(
59
+
`"<p><a href="https://blacksky.community/" target="_blank" rel="noopener noreferrer">https://blacksky.community/</a></p>"`,
60
+
);
61
+
});
62
+
63
+
test("should correctly parse a text block with strikethrough text", () => {
64
+
const html = parseTextBlock({
65
+
$type: "pub.leaflet.blocks.text",
66
+
facets: [
67
+
{
68
+
index: {
69
+
byteEnd: 13,
70
+
byteStart: 0,
71
+
},
72
+
features: [
73
+
{
74
+
$type: "pub.leaflet.richtext.facet#strikethrough",
75
+
},
76
+
],
77
+
},
78
+
],
79
+
plaintext: "strikethrough text with some plaintext",
80
+
});
81
+
82
+
expect(html).toMatchInlineSnapshot(
83
+
`"<p><s>strikethrough</s> text with some plaintext</p>"`,
84
+
);
85
+
});
86
+
87
+
test("should correctly parse a text block with underlined text", () => {
88
+
const html = parseTextBlock({
89
+
$type: "pub.leaflet.blocks.text",
90
+
facets: [
91
+
{
92
+
index: {
93
+
byteEnd: 10,
94
+
byteStart: 0,
95
+
},
96
+
features: [
97
+
{
98
+
$type: "pub.leaflet.richtext.facet#underline",
99
+
},
100
+
],
101
+
},
102
+
],
103
+
plaintext: "underlined text with some plaintext",
104
+
});
105
+
106
+
expect(html).toMatchInlineSnapshot(
107
+
`"<p><span style="text-decoration:underline;">underlined</span> text with some plaintext</p>"`,
108
+
);
109
+
});
110
+
111
+
test("should correctly parse a text block with italicized text", () => {
112
+
const html = parseTextBlock({
113
+
$type: "pub.leaflet.blocks.text",
114
+
facets: [
115
+
{
116
+
index: {
117
+
byteEnd: 10,
118
+
byteStart: 0,
119
+
},
120
+
features: [
121
+
{
122
+
$type: "pub.leaflet.richtext.facet#italic",
123
+
},
124
+
],
125
+
},
126
+
],
127
+
plaintext: "italicized text with some plaintext",
128
+
});
129
+
130
+
expect(html).toMatchInlineSnapshot(
131
+
`"<p><i>italicized</i> text with some plaintext</p>"`,
132
+
);
133
+
});