@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.
Files changed (75) hide show
  1. package/README.md +50 -20
  2. package/dist/bridge.d.ts +7 -1
  3. package/dist/bridge.js +1 -0
  4. package/dist/data.d.ts +19 -0
  5. package/dist/data.js +64 -0
  6. package/dist/data.test-d.d.ts +1 -0
  7. package/dist/experimental.d.ts +10 -0
  8. package/dist/experimental.js +87 -0
  9. package/dist/index.d.ts +36 -1
  10. package/dist/index.js +92 -2
  11. package/dist/integration.d.ts +5 -0
  12. package/dist/integration.js +69 -0
  13. package/dist/internal/admin-origin.d.ts +6 -0
  14. package/dist/internal/escape.d.ts +8 -0
  15. package/dist/internal/forms-store.d.ts +12 -0
  16. package/dist/internal/request-context.d.ts +16 -0
  17. package/dist/is-edit-mode.d.ts +32 -0
  18. package/dist/is-edit-mode.js +37 -0
  19. package/dist/island-route.d.ts +18 -0
  20. package/dist/island-route.js +87 -0
  21. package/dist/middleware.d.ts +3 -0
  22. package/dist/middleware.js +123 -0
  23. package/dist/sanitize.d.ts +12 -1
  24. package/dist/sanitize.js +6 -10
  25. package/dist/tina-field.d.ts +1 -1
  26. package/dist/tina-field.js +1 -0
  27. package/dist/types.d.ts +92 -1
  28. package/dist/types.js +0 -1
  29. package/dist/vite.d.ts +21 -0
  30. package/dist/vite.js +24 -0
  31. package/package.json +88 -17
  32. package/src/CodeBlockNode.astro +28 -0
  33. package/src/Container.astro +56 -0
  34. package/src/ImageNode.astro +17 -0
  35. package/src/LinkNode.astro +22 -0
  36. package/src/MdxNode.astro +24 -0
  37. package/src/Node.astro +11 -4
  38. package/src/TinaIsland.astro +44 -0
  39. package/src/TinaMarkdown.astro +8 -0
  40. package/src/__tests__/IslandStub.astro +8 -0
  41. package/src/__tests__/TinaIsland.test.ts +60 -0
  42. package/src/__tests__/TinaMarkdown.test.ts +112 -0
  43. package/src/__tests__/__snapshots__/TinaMarkdown.test.ts.snap +7 -0
  44. package/src/__tests__/fixtures/FancyHeading.astro +3 -0
  45. package/src/__tests__/fixtures/MyFeature.astro +4 -0
  46. package/src/__tests__/fixtures/basic-kitchen-sink.json +60 -0
  47. package/src/__tests__/fixtures/code-block.json +34 -0
  48. package/src/__tests__/fixtures/leaf-marks.json +199 -0
  49. package/src/__tests__/fixtures/mdx-jsx-flow.json +40 -0
  50. package/src/__tests__/fixtures/mdx-jsx-text.json +53 -0
  51. package/src/__tests__/forms-store.test.ts +70 -0
  52. package/src/__tests__/integration.test.ts +124 -0
  53. package/src/__tests__/island-route.test.ts +119 -0
  54. package/src/__tests__/middleware.test.ts +102 -0
  55. package/src/__tests__/sanitize.test.ts +75 -0
  56. package/src/__tests__/vite.test.ts +67 -0
  57. package/src/bridge.ts +7 -0
  58. package/src/data.test-d.ts +53 -0
  59. package/src/data.ts +73 -0
  60. package/src/experimental.ts +14 -0
  61. package/src/index.ts +54 -0
  62. package/src/integration.ts +94 -0
  63. package/src/internal/admin-origin.ts +19 -0
  64. package/src/internal/escape.ts +15 -0
  65. package/src/internal/forms-store.ts +50 -0
  66. package/src/internal/request-context.ts +23 -0
  67. package/src/is-edit-mode.ts +68 -0
  68. package/src/island-route.ts +109 -0
  69. package/src/middleware.ts +89 -0
  70. package/src/sanitize.ts +64 -0
  71. package/src/tina-field.ts +1 -0
  72. package/src/types.ts +97 -0
  73. package/src/vite.ts +40 -0
  74. package/dist/preview.d.ts +0 -1
  75. package/dist/preview.js +0 -1
@@ -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";