@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
package/src/sanitize.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitizes a CMS-supplied href, returning a safe URL or the fallback.
|
|
3
|
+
* Blocks dangerous schemes (javascript:, data:, vbscript:) and
|
|
4
|
+
* protocol-relative URLs (//evil.com). Allows relative paths, http(s),
|
|
5
|
+
* and mailto:.
|
|
6
|
+
*/
|
|
7
|
+
export function sanitizeHref(value: unknown, fallback = '#'): string {
|
|
8
|
+
if (typeof value !== 'string') return fallback;
|
|
9
|
+
const trimmed = value.trim();
|
|
10
|
+
if (!trimmed) return fallback;
|
|
11
|
+
const lower = trimmed.toLowerCase();
|
|
12
|
+
if (
|
|
13
|
+
lower.startsWith('javascript:') ||
|
|
14
|
+
lower.startsWith('data:') ||
|
|
15
|
+
lower.startsWith('vbscript:')
|
|
16
|
+
) {
|
|
17
|
+
return fallback;
|
|
18
|
+
}
|
|
19
|
+
if (
|
|
20
|
+
(trimmed.startsWith('/') && !trimmed.startsWith('//')) ||
|
|
21
|
+
trimmed.startsWith('./') ||
|
|
22
|
+
trimmed.startsWith('../') ||
|
|
23
|
+
trimmed.startsWith('#')
|
|
24
|
+
) {
|
|
25
|
+
return trimmed;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(trimmed);
|
|
29
|
+
if (
|
|
30
|
+
url.protocol === 'http:' ||
|
|
31
|
+
url.protocol === 'https:' ||
|
|
32
|
+
url.protocol === 'mailto:'
|
|
33
|
+
) {
|
|
34
|
+
return trimmed;
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
return fallback;
|
|
38
|
+
}
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validates a CMS-supplied image src, returning the src string if safe or ''
|
|
44
|
+
* if it is empty, not a string, or uses a non-http(s)/relative scheme.
|
|
45
|
+
*/
|
|
46
|
+
export function sanitizeImageSrc(src: unknown): string {
|
|
47
|
+
if (typeof src !== 'string') return '';
|
|
48
|
+
const trimmed = src.trim();
|
|
49
|
+
if (!trimmed) return '';
|
|
50
|
+
if (
|
|
51
|
+
trimmed.startsWith('./') ||
|
|
52
|
+
trimmed.startsWith('../') ||
|
|
53
|
+
(trimmed.startsWith('/') && !trimmed.startsWith('//'))
|
|
54
|
+
) {
|
|
55
|
+
return trimmed;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const url = new URL(trimmed);
|
|
59
|
+
if (url.protocol === 'http:' || url.protocol === 'https:') return trimmed;
|
|
60
|
+
} catch {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@tinacms/bridge/tina-field';
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plate AST node types Tina returns from rich-text fields. Mirrors the shape
|
|
3
|
+
* documented in `packages/@tinacms/mdx/src/parse/plate.ts` — kept inline so
|
|
4
|
+
* the Astro renderer can stay framework-free and not pull @tinacms/mdx into
|
|
5
|
+
* the page bundle.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Astro doesn't publicly export `AstroComponentFactory`, so we use a
|
|
10
|
+
* structural placeholder. The consumer's Astro pipeline performs the real
|
|
11
|
+
* compile-time check that whatever they pass is a valid component.
|
|
12
|
+
*/
|
|
13
|
+
export type AstroComponent = (...args: never[]) => unknown;
|
|
14
|
+
|
|
15
|
+
export type TinaRichTextRoot = {
|
|
16
|
+
type: 'root';
|
|
17
|
+
children: TinaRichTextNode[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type TinaRichTextNode =
|
|
21
|
+
| BlockElement
|
|
22
|
+
| InlineElement
|
|
23
|
+
| TextElement
|
|
24
|
+
| MdxElement;
|
|
25
|
+
|
|
26
|
+
export type LinkElement = {
|
|
27
|
+
type: 'a';
|
|
28
|
+
url: string;
|
|
29
|
+
title?: string;
|
|
30
|
+
children: InlineElement[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ImageElement = {
|
|
34
|
+
type: 'img';
|
|
35
|
+
url: string;
|
|
36
|
+
alt?: string;
|
|
37
|
+
caption?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type CodeBlockElement = {
|
|
41
|
+
type: 'code_block';
|
|
42
|
+
lang?: string;
|
|
43
|
+
value?: string;
|
|
44
|
+
children?: { children: TextElement[] }[];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type BlockElement =
|
|
48
|
+
| { type: 'p'; children: InlineElement[] }
|
|
49
|
+
| {
|
|
50
|
+
type: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
51
|
+
children: InlineElement[];
|
|
52
|
+
}
|
|
53
|
+
| { type: 'blockquote'; children: TinaRichTextNode[] }
|
|
54
|
+
| { type: 'ul' | 'ol'; children: TinaRichTextNode[] }
|
|
55
|
+
| { type: 'li'; children: TinaRichTextNode[] }
|
|
56
|
+
| { type: 'lic'; children: InlineElement[] }
|
|
57
|
+
| { type: 'hr' }
|
|
58
|
+
| { type: 'break' }
|
|
59
|
+
| ImageElement
|
|
60
|
+
| CodeBlockElement
|
|
61
|
+
| { type: 'maybe_mdx' }
|
|
62
|
+
| { type: 'html'; value: string }
|
|
63
|
+
| { type: 'invalid_markdown'; value: string };
|
|
64
|
+
|
|
65
|
+
export type InlineElement =
|
|
66
|
+
| TextElement
|
|
67
|
+
| LinkElement
|
|
68
|
+
| { type: 'html_inline'; value: string }
|
|
69
|
+
| MdxElement;
|
|
70
|
+
|
|
71
|
+
export type TextElement = {
|
|
72
|
+
type: 'text';
|
|
73
|
+
text: string;
|
|
74
|
+
bold?: boolean;
|
|
75
|
+
italic?: boolean;
|
|
76
|
+
underline?: boolean;
|
|
77
|
+
strikethrough?: boolean;
|
|
78
|
+
code?: boolean;
|
|
79
|
+
highlight?: boolean;
|
|
80
|
+
highlightColor?: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type MdxElement = {
|
|
84
|
+
type: 'mdxJsxFlowElement' | 'mdxJsxTextElement';
|
|
85
|
+
name: string;
|
|
86
|
+
props: Record<string, unknown>;
|
|
87
|
+
children?: TinaRichTextNode[];
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type TinaRichTextContent =
|
|
91
|
+
| TinaRichTextRoot
|
|
92
|
+
| TinaRichTextNode[]
|
|
93
|
+
| null
|
|
94
|
+
| undefined;
|
|
95
|
+
|
|
96
|
+
/** A map of mdxJsx name (or default tag override) → Astro component. */
|
|
97
|
+
export type CustomComponentsMap = Record<string, AstroComponent>;
|
package/src/vite.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dev-only Vite plugin that redirects `/admin` (and `/admin/`) to
|
|
5
|
+
* `/admin/index.html`.
|
|
6
|
+
*
|
|
7
|
+
* In `astro dev`, the admin SPA is served straight from `public/admin/` and
|
|
8
|
+
* Vite does not resolve a directory index for it, so a bare `/admin` request
|
|
9
|
+
* 404s. This plugin makes `/admin` land on the SPA the same way it does in a
|
|
10
|
+
* production build. It only applies to the dev server (`apply: 'serve'`); a
|
|
11
|
+
* built site serves `public/admin/index.html` itself.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // astro.config.mjs
|
|
15
|
+
* import { tinaAdminDevRedirect } from '@tinacms/astro/vite';
|
|
16
|
+
*
|
|
17
|
+
* export default defineConfig({
|
|
18
|
+
* vite: { plugins: [tinaAdminDevRedirect()] },
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
export function tinaAdminDevRedirect(): Plugin {
|
|
22
|
+
return {
|
|
23
|
+
name: 'tina-admin-dev-redirect',
|
|
24
|
+
apply: 'serve',
|
|
25
|
+
configureServer(server) {
|
|
26
|
+
server.middlewares.use((req, res, next) => {
|
|
27
|
+
const path = (req.url || '').split('?')[0];
|
|
28
|
+
if (path === '/admin' || path === '/admin/') {
|
|
29
|
+
res.statusCode = 302;
|
|
30
|
+
res.setHeader('Location', '/admin/index.html');
|
|
31
|
+
res.end();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
next();
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default tinaAdminDevRedirect;
|
package/dist/preview.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "../src/preview"
|
package/dist/preview.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "@tinacms/bridge/preview";
|