@tinacms/astro 0.0.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -20
- package/dist/bridge.d.ts +7 -1
- package/dist/bridge.js +1 -0
- package/dist/data.d.ts +19 -0
- package/dist/data.js +64 -0
- package/dist/data.test-d.d.ts +1 -0
- package/dist/experimental.d.ts +10 -0
- package/dist/experimental.js +87 -0
- package/dist/index.d.ts +36 -1
- package/dist/index.js +92 -2
- package/dist/integration.d.ts +5 -0
- package/dist/integration.js +69 -0
- package/dist/internal/admin-origin.d.ts +6 -0
- package/dist/internal/escape.d.ts +8 -0
- package/dist/internal/forms-store.d.ts +12 -0
- package/dist/internal/request-context.d.ts +16 -0
- package/dist/is-edit-mode.d.ts +32 -0
- package/dist/is-edit-mode.js +37 -0
- package/dist/island-route.d.ts +18 -0
- package/dist/island-route.js +87 -0
- package/dist/middleware.d.ts +3 -0
- package/dist/middleware.js +123 -0
- package/dist/sanitize.d.ts +12 -1
- package/dist/sanitize.js +6 -10
- package/dist/tina-field.d.ts +1 -1
- package/dist/tina-field.js +1 -0
- package/dist/types.d.ts +92 -1
- package/dist/types.js +0 -1
- package/dist/vite.d.ts +21 -0
- package/dist/vite.js +24 -0
- package/package.json +88 -17
- package/src/CodeBlockNode.astro +28 -0
- package/src/Container.astro +56 -0
- package/src/ImageNode.astro +17 -0
- package/src/LinkNode.astro +22 -0
- package/src/MdxNode.astro +24 -0
- package/src/Node.astro +11 -4
- package/src/TinaIsland.astro +44 -0
- package/src/TinaMarkdown.astro +8 -0
- package/src/__tests__/IslandStub.astro +8 -0
- package/src/__tests__/TinaIsland.test.ts +60 -0
- package/src/__tests__/TinaMarkdown.test.ts +112 -0
- package/src/__tests__/__snapshots__/TinaMarkdown.test.ts.snap +7 -0
- package/src/__tests__/fixtures/FancyHeading.astro +3 -0
- package/src/__tests__/fixtures/MyFeature.astro +4 -0
- package/src/__tests__/fixtures/basic-kitchen-sink.json +60 -0
- package/src/__tests__/fixtures/code-block.json +34 -0
- package/src/__tests__/fixtures/leaf-marks.json +199 -0
- package/src/__tests__/fixtures/mdx-jsx-flow.json +40 -0
- package/src/__tests__/fixtures/mdx-jsx-text.json +53 -0
- package/src/__tests__/forms-store.test.ts +70 -0
- package/src/__tests__/integration.test.ts +124 -0
- package/src/__tests__/island-route.test.ts +119 -0
- package/src/__tests__/middleware.test.ts +102 -0
- package/src/__tests__/sanitize.test.ts +75 -0
- package/src/__tests__/vite.test.ts +67 -0
- package/src/bridge.ts +7 -0
- package/src/data.test-d.ts +53 -0
- package/src/data.ts +73 -0
- package/src/experimental.ts +14 -0
- package/src/index.ts +54 -0
- package/src/integration.ts +94 -0
- package/src/internal/admin-origin.ts +19 -0
- package/src/internal/escape.ts +15 -0
- package/src/internal/forms-store.ts +50 -0
- package/src/internal/request-context.ts +23 -0
- package/src/is-edit-mode.ts +68 -0
- package/src/island-route.ts +109 -0
- package/src/middleware.ts +89 -0
- package/src/sanitize.ts +64 -0
- package/src/tina-field.ts +1 -0
- package/src/types.ts +97 -0
- package/src/vite.ts +40 -0
- package/dist/preview.d.ts +0 -1
- package/dist/preview.js +0 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { sanitizeImageSrc } from './sanitize';
|
|
3
|
+
import type { CustomComponentsMap, ImageElement } from './types';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
node: ImageElement;
|
|
7
|
+
components: CustomComponentsMap;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { node, components } = Astro.props;
|
|
11
|
+
const Override = components.img;
|
|
12
|
+
---
|
|
13
|
+
{Override ? (
|
|
14
|
+
<Override url={node.url} alt={node.alt} caption={node.caption} />
|
|
15
|
+
) : (
|
|
16
|
+
<img src={sanitizeImageSrc(node.url)} alt={node.alt ?? ''} />
|
|
17
|
+
)}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
import TinaMarkdown from './TinaMarkdown.astro';
|
|
3
|
+
import { sanitizeHref } from './sanitize';
|
|
4
|
+
import type { CustomComponentsMap, LinkElement } from './types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
node: LinkElement;
|
|
8
|
+
components: CustomComponentsMap;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { node, components } = Astro.props;
|
|
12
|
+
const Override = components.a;
|
|
13
|
+
---
|
|
14
|
+
{Override ? (
|
|
15
|
+
<Override url={node.url}>
|
|
16
|
+
<TinaMarkdown content={node.children} components={components} />
|
|
17
|
+
</Override>
|
|
18
|
+
) : (
|
|
19
|
+
<a href={sanitizeHref(node.url)}>
|
|
20
|
+
<TinaMarkdown content={node.children} components={components} />
|
|
21
|
+
</a>
|
|
22
|
+
)}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* mdxJsx{Flow,Text}Element nodes are dispatched by `node.name` — registered
|
|
4
|
+
* components on the `components` map render with the node's props spread in.
|
|
5
|
+
* Unregistered names emit a visible placeholder so missing registrations
|
|
6
|
+
* surface during development.
|
|
7
|
+
*/
|
|
8
|
+
import type { CustomComponentsMap, MdxElement } from './types';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
node: MdxElement;
|
|
12
|
+
components: CustomComponentsMap;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { node, components } = Astro.props;
|
|
16
|
+
const MdxComponent = components[node.name];
|
|
17
|
+
---
|
|
18
|
+
{MdxComponent ? (
|
|
19
|
+
<MdxComponent {...(node.props ?? {})} />
|
|
20
|
+
) : (
|
|
21
|
+
<span style="display:inline-block;padding:0.25rem 0.5rem;background:#fee;color:#900;border-radius:0.25rem;font-family:monospace;font-size:0.85em;">
|
|
22
|
+
No component provided for {node.name}
|
|
23
|
+
</span>
|
|
24
|
+
)}
|
package/src/Node.astro
CHANGED
|
@@ -13,7 +13,10 @@ import Leaf from './Leaf.astro';
|
|
|
13
13
|
import LinkNode from './LinkNode.astro';
|
|
14
14
|
import MdxNode from './MdxNode.astro';
|
|
15
15
|
import type {
|
|
16
|
+
CodeBlockElement,
|
|
16
17
|
CustomComponentsMap,
|
|
18
|
+
ImageElement,
|
|
19
|
+
LinkElement,
|
|
17
20
|
MdxElement,
|
|
18
21
|
TextElement,
|
|
19
22
|
TinaRichTextNode,
|
|
@@ -46,11 +49,11 @@ const containerTypes = new Set([
|
|
|
46
49
|
{containerTypes.has(t) ? (
|
|
47
50
|
<Container node={node as TinaRichTextNode & { children: TinaRichTextNode[] }} components={components} />
|
|
48
51
|
) : t === 'a' ? (
|
|
49
|
-
<LinkNode node={node as
|
|
52
|
+
<LinkNode node={node as LinkElement} components={components} />
|
|
50
53
|
) : t === 'img' ? (
|
|
51
|
-
<ImageNode node={node as
|
|
54
|
+
<ImageNode node={node as ImageElement} components={components} />
|
|
52
55
|
) : t === 'code_block' ? (
|
|
53
|
-
<CodeBlockNode node={node as
|
|
56
|
+
<CodeBlockNode node={node as CodeBlockElement} components={components} />
|
|
54
57
|
) : t === 'text' ? (
|
|
55
58
|
<Leaf node={node as TextElement} />
|
|
56
59
|
) : t === 'mdxJsxFlowElement' || t === 'mdxJsxTextElement' ? (
|
|
@@ -60,7 +63,11 @@ const containerTypes = new Set([
|
|
|
60
63
|
) : t === 'break' ? (
|
|
61
64
|
Override ? <Override /> : <br />
|
|
62
65
|
) : t === 'html' || t === 'html_inline' ? (
|
|
63
|
-
|
|
66
|
+
Override ? (
|
|
67
|
+
<Override value={(node as { value: string }).value} />
|
|
68
|
+
) : (
|
|
69
|
+
(node as { value: string }).value
|
|
70
|
+
)
|
|
64
71
|
) : t === 'invalid_markdown' ? (
|
|
65
72
|
<pre>{(node as { value: string }).value}</pre>
|
|
66
73
|
) : null}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { adminOrigins } from './internal/admin-origin';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
name: string;
|
|
6
|
+
wrapper: { tag: string; className?: string };
|
|
7
|
+
params?: Record<string, string | number>;
|
|
8
|
+
/** Mark at most one `<TinaIsland>` per page as the primary editable
|
|
9
|
+
* region so the admin opens it on load instead of the multi-document
|
|
10
|
+
* picker. SSR pages don't need this — the first `requestWithMetadata()`
|
|
11
|
+
* is treated as primary automatically. */
|
|
12
|
+
primary?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { name, wrapper, params, primary } = Astro.props;
|
|
16
|
+
const search = params
|
|
17
|
+
? `?${new URLSearchParams(
|
|
18
|
+
Object.fromEntries(
|
|
19
|
+
Object.entries(params).map(([key, value]) => [key, String(value)])
|
|
20
|
+
)
|
|
21
|
+
).toString()}`
|
|
22
|
+
: '';
|
|
23
|
+
const marker = `/tina-island/${name}${search}`;
|
|
24
|
+
const Tag = wrapper.tag as keyof HTMLElementTagNameMap;
|
|
25
|
+
const adminOrigin = adminOrigins();
|
|
26
|
+
---
|
|
27
|
+
<!-- In-iframe bridge bootstrap for prerendered pages (the middleware only
|
|
28
|
+
injects on SSR responses). No-op outside the admin iframe; the
|
|
29
|
+
window flag dedupes across multiple <TinaIsland> on one page. -->
|
|
30
|
+
<script is:inline type="module" define:vars={{ adminOrigin }}>
|
|
31
|
+
if (window.self !== window.top && !window.__tinaBootstrap) {
|
|
32
|
+
window.__tinaBootstrap = 1;
|
|
33
|
+
const { init, refreshForms } = await import('/admin/bridge.js');
|
|
34
|
+
init(adminOrigin ? { adminOrigin } : undefined);
|
|
35
|
+
document.addEventListener('astro:page-load', refreshForms);
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
<Tag
|
|
39
|
+
class={wrapper.className}
|
|
40
|
+
data-tina-island={marker}
|
|
41
|
+
data-tina-island-primary={primary ? '' : undefined}
|
|
42
|
+
>
|
|
43
|
+
<slot />
|
|
44
|
+
</Tag>
|
package/src/TinaMarkdown.astro
CHANGED
|
@@ -8,7 +8,15 @@
|
|
|
8
8
|
* (mdxJsxFlowElement / mdxJsxTextElement). Default tags (p, h1-h6, ul, ol,
|
|
9
9
|
* li, blockquote, etc.) can be overridden by registering the tag name on
|
|
10
10
|
* the same map. Recurses via this same component for nested rich-text.
|
|
11
|
+
*
|
|
12
|
+
* This file is also the package's main runtime entry — Astro projects
|
|
13
|
+
* import `TinaMarkdown` (default) plus the `tina` / `tinaField` helpers
|
|
14
|
+
* from the same subpath. Re-exporting from the .astro file lets named
|
|
15
|
+
* imports resolve through the same path that serves the component.
|
|
11
16
|
*/
|
|
17
|
+
export { requestWithMetadata } from './data';
|
|
18
|
+
export { tinaField } from './tina-field';
|
|
19
|
+
|
|
12
20
|
import Node from './Node.astro';
|
|
13
21
|
import type { CustomComponentsMap, TinaRichTextContent } from './types';
|
|
14
22
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
|
|
2
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import TinaIsland from '../TinaIsland.astro';
|
|
4
|
+
|
|
5
|
+
let container: AstroContainer;
|
|
6
|
+
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
container = await AstroContainer.create();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const wrapper = { tag: 'article', className: 'prose' };
|
|
12
|
+
|
|
13
|
+
describe('TinaIsland', () => {
|
|
14
|
+
it('emits the data-tina-island marker with encoded params', async () => {
|
|
15
|
+
const html = await container.renderToString(TinaIsland, {
|
|
16
|
+
props: { name: 'post', wrapper, params: { slug: 'hello-world' } },
|
|
17
|
+
slots: { default: 'BODY' },
|
|
18
|
+
});
|
|
19
|
+
expect(html).toContain(
|
|
20
|
+
'data-tina-island="/tina-island/post?slug=hello-world"'
|
|
21
|
+
);
|
|
22
|
+
expect(html).toContain('BODY');
|
|
23
|
+
expect(html).toContain('class="prose"');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('emits the guarded in-iframe bootstrap script', async () => {
|
|
27
|
+
const html = await container.renderToString(TinaIsland, {
|
|
28
|
+
props: { name: 'post', wrapper },
|
|
29
|
+
slots: { default: 'BODY' },
|
|
30
|
+
});
|
|
31
|
+
expect(html).toContain('window.__tinaBootstrap');
|
|
32
|
+
expect(html).toContain("import('/admin/bridge.js')");
|
|
33
|
+
expect(html).toContain('window.self !== window.top');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('threads the resolved admin origin through define:vars', async () => {
|
|
37
|
+
const html = await container.renderToString(TinaIsland, {
|
|
38
|
+
props: { name: 'post', wrapper },
|
|
39
|
+
slots: { default: 'BODY' },
|
|
40
|
+
});
|
|
41
|
+
// `define:vars` emits the value resolved from PUBLIC_TINA_ADMIN_ORIGIN —
|
|
42
|
+
// `null` here since it's unset in the test env.
|
|
43
|
+
expect(html).toContain('adminOrigin = null');
|
|
44
|
+
expect(html).toContain('init(adminOrigin ? { adminOrigin } : undefined)');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('marks the wrapper data-tina-island-primary only when primary is set', async () => {
|
|
48
|
+
const withoutPrimary = await container.renderToString(TinaIsland, {
|
|
49
|
+
props: { name: 'post', wrapper },
|
|
50
|
+
slots: { default: 'BODY' },
|
|
51
|
+
});
|
|
52
|
+
expect(withoutPrimary).not.toContain('data-tina-island-primary');
|
|
53
|
+
|
|
54
|
+
const withPrimary = await container.renderToString(TinaIsland, {
|
|
55
|
+
props: { name: 'post', wrapper, primary: true },
|
|
56
|
+
slots: { default: 'BODY' },
|
|
57
|
+
});
|
|
58
|
+
expect(withPrimary).toContain('data-tina-island-primary');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
|
|
2
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import TinaMarkdown from '../TinaMarkdown.astro';
|
|
4
|
+
import basicKitchenSink from './fixtures/basic-kitchen-sink.json';
|
|
5
|
+
import codeBlock from './fixtures/code-block.json';
|
|
6
|
+
import leafMarks from './fixtures/leaf-marks.json';
|
|
7
|
+
import mdxJsxFlow from './fixtures/mdx-jsx-flow.json';
|
|
8
|
+
import mdxJsxText from './fixtures/mdx-jsx-text.json';
|
|
9
|
+
|
|
10
|
+
let container: AstroContainer;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
container = await AstroContainer.create();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Astro injects dev-only `data-astro-source-file` / `data-astro-source-loc`
|
|
18
|
+
* attributes for editor source-mapping. Strip them so snapshots and string
|
|
19
|
+
* assertions stay stable across Astro versions and dev/prod modes.
|
|
20
|
+
*/
|
|
21
|
+
const stripDevAttrs = (html: string) =>
|
|
22
|
+
html.replace(/\s+data-astro-source-(?:file|loc)="[^"]*"/g, '');
|
|
23
|
+
|
|
24
|
+
const render = async (props: Parameters<typeof container.renderToString>[1]) =>
|
|
25
|
+
stripDevAttrs(await container.renderToString(TinaMarkdown, props));
|
|
26
|
+
|
|
27
|
+
describe('TinaMarkdown', () => {
|
|
28
|
+
it('renders the basic kitchen-sink fixture', async () => {
|
|
29
|
+
const html = await render({ props: { content: basicKitchenSink } });
|
|
30
|
+
expect(html).toMatchSnapshot();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('renders nested leaf-mark formatting', async () => {
|
|
34
|
+
const html = await render({ props: { content: leafMarks } });
|
|
35
|
+
expect(html).toMatchSnapshot();
|
|
36
|
+
expect(html).toContain('<strong>');
|
|
37
|
+
expect(html).toContain('<em>');
|
|
38
|
+
expect(html).toContain('<code>inline code</code>');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('renders code blocks with language class', async () => {
|
|
42
|
+
const html = await render({ props: { content: codeBlock } });
|
|
43
|
+
expect(html).toMatchSnapshot();
|
|
44
|
+
expect(html).toMatch(/<pre><code class="language-javascript">/);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('falls back to a placeholder when an mdxJsx component is unregistered', async () => {
|
|
48
|
+
const html = await render({ props: { content: mdxJsxFlow } });
|
|
49
|
+
expect(html).toContain('No component provided for someFeature');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('falls back to a placeholder for unregistered inline mdxJsx', async () => {
|
|
53
|
+
const html = await render({ props: { content: mdxJsxText } });
|
|
54
|
+
expect(html).toMatch(/No component provided for/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('renders an empty array when content is null', async () => {
|
|
58
|
+
const html = await render({ props: { content: null } });
|
|
59
|
+
expect(html.trim()).toBe('');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('accepts a bare array of nodes (not wrapped in root)', async () => {
|
|
63
|
+
const nodes = [{ type: 'p', children: [{ type: 'text', text: 'hello' }] }];
|
|
64
|
+
const html = await render({ props: { content: nodes } });
|
|
65
|
+
expect(html).toContain('<p>hello</p>');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('renders sanitized hrefs on link nodes', async () => {
|
|
69
|
+
const nodes = [
|
|
70
|
+
{
|
|
71
|
+
type: 'p',
|
|
72
|
+
children: [
|
|
73
|
+
{
|
|
74
|
+
type: 'a',
|
|
75
|
+
url: 'javascript:alert(1)',
|
|
76
|
+
children: [{ type: 'text', text: 'click' }],
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
const html = await render({ props: { content: nodes } });
|
|
82
|
+
expect(html).not.toContain('javascript:');
|
|
83
|
+
expect(html).toContain('href="#"');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('TinaMarkdown — components map', () => {
|
|
88
|
+
it('dispatches a registered mdxJsx component by name', async () => {
|
|
89
|
+
const MyFeature = await import('./fixtures/MyFeature.astro');
|
|
90
|
+
const html = await render({
|
|
91
|
+
props: {
|
|
92
|
+
content: mdxJsxFlow,
|
|
93
|
+
components: { someFeature: MyFeature.default },
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
expect(html).toContain('<section data-test="my-feature">');
|
|
97
|
+
expect(html).not.toContain('No component provided for someFeature');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('overrides a default tag (h1) when registered on the components map', async () => {
|
|
101
|
+
const FancyHeading = await import('./fixtures/FancyHeading.astro');
|
|
102
|
+
const nodes = [{ type: 'h1', children: [{ type: 'text', text: 'hello' }] }];
|
|
103
|
+
const html = await render({
|
|
104
|
+
props: {
|
|
105
|
+
content: nodes,
|
|
106
|
+
components: { h1: FancyHeading.default },
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
expect(html).toContain('class="fancy"');
|
|
110
|
+
expect(html).toContain('hello');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`TinaMarkdown > renders code blocks with language class 1`] = `"<pre><code class="language-javascript">const test = 123</code></pre><pre><code>some random code</code></pre>"`;
|
|
4
|
+
|
|
5
|
+
exports[`TinaMarkdown > renders nested leaf-mark formatting 1`] = `"<p>Some <strong>bold</strong> text</p><p>Some <strong><em>bold and emphasized</em></strong> text</p><p>Marks with <em>emphasized text nesting </em><strong><em>bold</em></strong><em> text</em></p><p><strong>Hello </strong><strong><em>world</em></strong><strong>, again</strong> <em>here</em></p><p>Some <code>inline code</code> examples</p><p><em>Hello </em><em><code>some code</code></em><em>, again</em></p><p><strong>Hello </strong><a href="https://example.com"><strong>world</strong></a></p><p><strong><em>Hello </em></strong><a href="https://example.com"><strong><em>world</em></strong></a><em> And some other text, which has a </em><a href="https://something.com"><em>link to something</em></a></p>"`;
|
|
6
|
+
|
|
7
|
+
exports[`TinaMarkdown > renders the basic kitchen-sink fixture 1`] = `"<h1>Heading</h1><p>A paragraph.</p><blockquote>A quote.</blockquote><ul><li><div>An item</div></li></ul><hr><p>A <a href="http://example.com">link</a>.</p><p><img src="https://get.svg.workers.dev" alt="Alt Text"></p>"`;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "root",
|
|
3
|
+
"children": [
|
|
4
|
+
{
|
|
5
|
+
"type": "h1",
|
|
6
|
+
"children": [{ "type": "text", "text": "Heading" }]
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"type": "p",
|
|
10
|
+
"children": [{ "type": "text", "text": "A paragraph." }]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "blockquote",
|
|
14
|
+
"children": [{ "type": "text", "text": "A quote." }]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"type": "ul",
|
|
18
|
+
"children": [
|
|
19
|
+
{
|
|
20
|
+
"type": "li",
|
|
21
|
+
"children": [
|
|
22
|
+
{
|
|
23
|
+
"type": "lic",
|
|
24
|
+
"children": [{ "type": "text", "text": "An item" }]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "hr",
|
|
32
|
+
"children": [{ "type": "text", "text": "" }]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"type": "p",
|
|
36
|
+
"children": [
|
|
37
|
+
{ "type": "text", "text": "A " },
|
|
38
|
+
{
|
|
39
|
+
"type": "a",
|
|
40
|
+
"url": "http://example.com",
|
|
41
|
+
"title": "Example",
|
|
42
|
+
"children": [{ "type": "text", "text": "link" }]
|
|
43
|
+
},
|
|
44
|
+
{ "type": "text", "text": "." }
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"type": "p",
|
|
49
|
+
"children": [
|
|
50
|
+
{
|
|
51
|
+
"type": "img",
|
|
52
|
+
"url": "https://get.svg.workers.dev",
|
|
53
|
+
"alt": "Alt Text",
|
|
54
|
+
"caption": "Image Title",
|
|
55
|
+
"children": [{ "type": "text", "text": "" }]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "root",
|
|
3
|
+
"children": [
|
|
4
|
+
{
|
|
5
|
+
"type": "code_block",
|
|
6
|
+
"lang": "javascript",
|
|
7
|
+
"value": "const test = 123",
|
|
8
|
+
"children": [
|
|
9
|
+
{
|
|
10
|
+
"type": "code_line",
|
|
11
|
+
"children": [
|
|
12
|
+
{
|
|
13
|
+
"text": "const test = 123"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "code_block",
|
|
21
|
+
"value": "some random code",
|
|
22
|
+
"children": [
|
|
23
|
+
{
|
|
24
|
+
"type": "code_line",
|
|
25
|
+
"children": [
|
|
26
|
+
{
|
|
27
|
+
"text": "some random code"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "root",
|
|
3
|
+
"children": [
|
|
4
|
+
{
|
|
5
|
+
"type": "p",
|
|
6
|
+
"children": [
|
|
7
|
+
{
|
|
8
|
+
"type": "text",
|
|
9
|
+
"text": "Some "
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "text",
|
|
13
|
+
"text": "bold",
|
|
14
|
+
"bold": true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"type": "text",
|
|
18
|
+
"text": " text"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"type": "p",
|
|
24
|
+
"children": [
|
|
25
|
+
{
|
|
26
|
+
"type": "text",
|
|
27
|
+
"text": "Some "
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"type": "text",
|
|
31
|
+
"text": "bold and emphasized",
|
|
32
|
+
"italic": true,
|
|
33
|
+
"bold": true
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"type": "text",
|
|
37
|
+
"text": " text"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "p",
|
|
43
|
+
"children": [
|
|
44
|
+
{
|
|
45
|
+
"type": "text",
|
|
46
|
+
"text": "Marks with "
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"type": "text",
|
|
50
|
+
"text": "emphasized text nesting ",
|
|
51
|
+
"italic": true
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"type": "text",
|
|
55
|
+
"text": "bold",
|
|
56
|
+
"italic": true,
|
|
57
|
+
"bold": true
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"type": "text",
|
|
61
|
+
"text": " text",
|
|
62
|
+
"italic": true
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"type": "p",
|
|
68
|
+
"children": [
|
|
69
|
+
{
|
|
70
|
+
"type": "text",
|
|
71
|
+
"text": "Hello ",
|
|
72
|
+
"bold": true
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"type": "text",
|
|
76
|
+
"text": "world",
|
|
77
|
+
"bold": true,
|
|
78
|
+
"italic": true
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"type": "text",
|
|
82
|
+
"text": ", again",
|
|
83
|
+
"bold": true
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"type": "text",
|
|
87
|
+
"text": " "
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"type": "text",
|
|
91
|
+
"text": "here",
|
|
92
|
+
"italic": true
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"type": "p",
|
|
98
|
+
"children": [
|
|
99
|
+
{
|
|
100
|
+
"type": "text",
|
|
101
|
+
"text": "Some "
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"type": "text",
|
|
105
|
+
"text": "inline code",
|
|
106
|
+
"code": true
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"type": "text",
|
|
110
|
+
"text": " examples"
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"type": "p",
|
|
116
|
+
"children": [
|
|
117
|
+
{
|
|
118
|
+
"type": "text",
|
|
119
|
+
"text": "Hello ",
|
|
120
|
+
"italic": true
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"type": "text",
|
|
124
|
+
"text": "some code",
|
|
125
|
+
"code": true,
|
|
126
|
+
"italic": true
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"type": "text",
|
|
130
|
+
"text": ", again",
|
|
131
|
+
"italic": true
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"type": "p",
|
|
137
|
+
"children": [
|
|
138
|
+
{
|
|
139
|
+
"type": "text",
|
|
140
|
+
"text": "Hello ",
|
|
141
|
+
"bold": true
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"type": "a",
|
|
145
|
+
"url": "https://example.com",
|
|
146
|
+
"title": "Example Site",
|
|
147
|
+
"children": [
|
|
148
|
+
{
|
|
149
|
+
"type": "text",
|
|
150
|
+
"text": "world",
|
|
151
|
+
"bold": true
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"type": "p",
|
|
159
|
+
"children": [
|
|
160
|
+
{
|
|
161
|
+
"type": "text",
|
|
162
|
+
"text": "Hello ",
|
|
163
|
+
"italic": true,
|
|
164
|
+
"bold": true
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"type": "a",
|
|
168
|
+
"url": "https://example.com",
|
|
169
|
+
"title": "Example Site",
|
|
170
|
+
"children": [
|
|
171
|
+
{
|
|
172
|
+
"type": "text",
|
|
173
|
+
"text": "world",
|
|
174
|
+
"italic": true,
|
|
175
|
+
"bold": true
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"type": "text",
|
|
181
|
+
"text": " And some other text, which has a ",
|
|
182
|
+
"italic": true
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"type": "a",
|
|
186
|
+
"url": "https://something.com",
|
|
187
|
+
"title": null,
|
|
188
|
+
"children": [
|
|
189
|
+
{
|
|
190
|
+
"type": "text",
|
|
191
|
+
"text": "link to something",
|
|
192
|
+
"italic": true
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
}
|