@drawnagency/primitives 0.1.1 → 0.2.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 (139) hide show
  1. package/dist/auth/index.js +26 -3
  2. package/dist/chunk-2VTPWODA.js +60 -0
  3. package/dist/chunk-CS7F6IOY.js +39 -0
  4. package/dist/chunk-HOJAF4VD.js +264 -0
  5. package/dist/chunk-IP6ODLXX.js +341 -0
  6. package/dist/chunk-T4BJ6RSB.js +58 -0
  7. package/dist/chunk-UKEVUCIZ.js +200 -0
  8. package/dist/chunk-UMSFICAC.js +36 -0
  9. package/dist/index.js +156 -4
  10. package/dist/lib/index.js +62 -12
  11. package/dist/lib/sanitize.d.ts.map +1 -1
  12. package/dist/media/index.js +36 -9
  13. package/dist/schemas/index.js +52 -7
  14. package/package.json +4 -4
  15. package/src/lib/sanitize.ts +6 -2
  16. package/dist/auth/cookies.js +0 -44
  17. package/dist/auth/errors.js +0 -10
  18. package/dist/auth/security.js +0 -48
  19. package/dist/auth/types.js +0 -1
  20. package/dist/components/brandguide/ColorSwatchSettings.js +0 -10
  21. package/dist/components/brandguide/Colors.js +0 -79
  22. package/dist/components/brandguide/DoDontList.js +0 -22
  23. package/dist/components/brandguide/DoDontMediaGrid.js +0 -5
  24. package/dist/components/editor/AudiencePicker.js +0 -24
  25. package/dist/components/editor/DeleteButton.js +0 -6
  26. package/dist/components/editor/DragHandle.js +0 -8
  27. package/dist/components/editor/InsertButton.js +0 -7
  28. package/dist/components/editor/SectionWrapper.js +0 -135
  29. package/dist/components/editor/SettingsButton.js +0 -6
  30. package/dist/components/editor/SettingsForm.js +0 -64
  31. package/dist/components/editor/StatusBadge.js +0 -10
  32. package/dist/components/editor/StatusPicker.js +0 -30
  33. package/dist/components/editor/index.js +0 -7
  34. package/dist/components/primitives/CustomParagraph.js +0 -24
  35. package/dist/components/primitives/EditableGrid.js +0 -90
  36. package/dist/components/primitives/EditableList.js +0 -54
  37. package/dist/components/primitives/EditablePlainText.js +0 -52
  38. package/dist/components/primitives/EditableRichText.js +0 -86
  39. package/dist/components/primitives/HeadingSection.js +0 -7
  40. package/dist/components/primitives/IconPicker.js +0 -21
  41. package/dist/components/primitives/LinkPopover.js +0 -48
  42. package/dist/components/primitives/MediaSettingsForms.js +0 -42
  43. package/dist/components/primitives/ResolvedMedia.js +0 -9
  44. package/dist/components/primitives/RichTextToolbar.js +0 -26
  45. package/dist/components/primitives/tiptap-presets.js +0 -44
  46. package/dist/components/primitives/useEditableCollection.js +0 -61
  47. package/dist/components/primitives/useEditablePlainText.js +0 -27
  48. package/dist/components/primitives/useEditableRichText.js +0 -52
  49. package/dist/components/sections/Button/CTAButton.js +0 -18
  50. package/dist/components/sections/Button/index.js +0 -28
  51. package/dist/components/sections/Colors/index.js +0 -34
  52. package/dist/components/sections/DoDontList/index.js +0 -33
  53. package/dist/components/sections/DoDontMediaGrid/index.js +0 -41
  54. package/dist/components/sections/IconList/IconList.js +0 -131
  55. package/dist/components/sections/IconList/IconListSettings.js +0 -22
  56. package/dist/components/sections/IconList/index.js +0 -27
  57. package/dist/components/sections/LinkHeading/index.js +0 -15
  58. package/dist/components/sections/MediaGrid/MediaGrid.js +0 -62
  59. package/dist/components/sections/MediaGrid/index.js +0 -35
  60. package/dist/components/sections/Prose/Prose.js +0 -11
  61. package/dist/components/sections/Prose/index.js +0 -15
  62. package/dist/components/sections/SectionLayout.js +0 -15
  63. package/dist/components/sections/SplitContent/SplitContent.js +0 -31
  64. package/dist/components/sections/SplitContent/SplitContentSettings.js +0 -17
  65. package/dist/components/sections/SplitContent/index.js +0 -27
  66. package/dist/components/sections/SubHeading/index.js +0 -18
  67. package/dist/components/sections/SubSubHeading/index.js +0 -18
  68. package/dist/components/sections/ViewRenderer.js +0 -13
  69. package/dist/components/sections/register-schemas.js +0 -15
  70. package/dist/components/sections/register.js +0 -15
  71. package/dist/components/shared/Button.js +0 -27
  72. package/dist/components/shared/Checkbox.js +0 -10
  73. package/dist/components/shared/ColorPicker.js +0 -5
  74. package/dist/components/shared/ErrorBoundary.js +0 -30
  75. package/dist/components/shared/FontPicker.js +0 -190
  76. package/dist/components/shared/FormLabel.js +0 -5
  77. package/dist/components/shared/IconButton.js +0 -16
  78. package/dist/components/shared/Input.js +0 -8
  79. package/dist/components/shared/Navigation.js +0 -71
  80. package/dist/components/shared/PasswordInput.js +0 -11
  81. package/dist/components/shared/Popover.js +0 -33
  82. package/dist/components/shared/PopoverItem.js +0 -6
  83. package/dist/components/shared/Select.js +0 -9
  84. package/dist/components/shared/Textarea.js +0 -8
  85. package/dist/components/shared/Toggle.js +0 -5
  86. package/dist/components/shared/Tooltip.js +0 -8
  87. package/dist/components/shared/icons.js +0 -23
  88. package/dist/components/shell/AudienceAddForm.js +0 -43
  89. package/dist/components/shell/AudienceRow.js +0 -74
  90. package/dist/components/shell/EditorContext.js +0 -24
  91. package/dist/components/shell/EditorLoginForm.js +0 -46
  92. package/dist/components/shell/EditorModal.js +0 -43
  93. package/dist/components/shell/EditorModalContext.js +0 -20
  94. package/dist/components/shell/EditorShell.js +0 -483
  95. package/dist/components/shell/MediaLibraryContext.js +0 -5
  96. package/dist/components/shell/MediaLibraryModal.js +0 -145
  97. package/dist/components/shell/ProcessingIndicator.js +0 -15
  98. package/dist/components/shell/SectionSkeleton.js +0 -22
  99. package/dist/components/shell/SectionTypePicker.js +0 -15
  100. package/dist/components/shell/SiteSettingsDisplay.js +0 -28
  101. package/dist/components/shell/SiteSettingsModal.js +0 -40
  102. package/dist/components/shell/SiteSettingsUsers.js +0 -87
  103. package/dist/components/shell/SiteSettingsViewerAccess.js +0 -94
  104. package/dist/components/shell/ViewerLoginForm.js +0 -40
  105. package/dist/data/google-fonts.json +0 -7718
  106. package/dist/hooks/index.js +0 -6
  107. package/dist/hooks/useActiveHeadings.js +0 -99
  108. package/dist/hooks/useEditorPersistence.js +0 -73
  109. package/dist/hooks/useEditorPublish.js +0 -145
  110. package/dist/hooks/useFocusTrap.js +0 -51
  111. package/dist/hooks/useMediaPipeline.js +0 -253
  112. package/dist/hooks/useResolvedMedia.js +0 -39
  113. package/dist/lib/cn.js +0 -5
  114. package/dist/lib/contrast.js +0 -11
  115. package/dist/lib/dexie.js +0 -236
  116. package/dist/lib/events.js +0 -15
  117. package/dist/lib/google-fonts.js +0 -11
  118. package/dist/lib/grid.js +0 -7
  119. package/dist/lib/icons.js +0 -27
  120. package/dist/lib/loader.js +0 -57
  121. package/dist/lib/nav.js +0 -58
  122. package/dist/lib/registry.js +0 -64
  123. package/dist/lib/safeRedirect.js +0 -11
  124. package/dist/lib/sanitize.js +0 -6
  125. package/dist/lib/timestamp.js +0 -28
  126. package/dist/media/github.js +0 -60
  127. package/dist/media/queue.js +0 -116
  128. package/dist/media/resolve.js +0 -50
  129. package/dist/media/types.js +0 -1
  130. package/dist/media/utils.js +0 -41
  131. package/dist/media/videoPoster.js +0 -44
  132. package/dist/media/worker.js +0 -73
  133. package/dist/schemas/audience.js +0 -19
  134. package/dist/schemas/auth.js +0 -22
  135. package/dist/schemas/media-grid-options.js +0 -7
  136. package/dist/schemas/media.js +0 -28
  137. package/dist/schemas/sections.js +0 -12
  138. package/dist/schemas/shared.js +0 -71
  139. package/dist/schemas/site-config.js +0 -26
@@ -1,116 +0,0 @@
1
- export class ProcessingQueue {
2
- items = new Map();
3
- pending = [];
4
- activeWorkers = new Map();
5
- options;
6
- nextId = 0;
7
- constructor(options) {
8
- this.options = options;
9
- }
10
- add(input) {
11
- const id = `media-${this.nextId++}`;
12
- const item = {
13
- id,
14
- originalName: input.originalName,
15
- mimeType: input.mimeType,
16
- hash: input.hash,
17
- kind: input.kind,
18
- percent: 0,
19
- state: "queued",
20
- };
21
- this.items.set(id, item);
22
- this.pending.push({ input, id });
23
- this.options.onEvent({ type: "queued", item: { ...item } });
24
- this.processNext();
25
- return id;
26
- }
27
- getStatus() {
28
- let active = 0;
29
- let queued = 0;
30
- for (const item of this.items.values()) {
31
- if (item.state === "active")
32
- active++;
33
- if (item.state === "queued")
34
- queued++;
35
- }
36
- return {
37
- active,
38
- queued,
39
- total: this.items.size,
40
- items: Array.from(this.items.values()),
41
- };
42
- }
43
- processNext() {
44
- while (this.activeWorkers.size < this.options.maxConcurrent && this.pending.length > 0) {
45
- const next = this.pending.shift();
46
- this.startProcessing(next.id, next.input);
47
- }
48
- }
49
- startProcessing(id, input) {
50
- const item = this.items.get(id);
51
- item.state = "active";
52
- this.options.onEvent({ type: "started", item: { ...item } });
53
- const worker = this.options.createWorker();
54
- this.activeWorkers.set(id, worker);
55
- worker.onmessage = (e) => {
56
- const msg = e.data;
57
- if (msg.id !== id)
58
- return;
59
- if (msg.type === "progress") {
60
- item.percent = msg.percent;
61
- this.options.onEvent({ type: "progress", item: { ...item } });
62
- }
63
- else if (msg.type === "complete") {
64
- item.state = "complete";
65
- item.percent = 100;
66
- item.result = {
67
- variants: msg.variants,
68
- primaryBlob: msg.primaryBlob,
69
- posterBlob: msg.posterBlob,
70
- width: msg.width,
71
- height: msg.height,
72
- };
73
- this.options.onEvent({ type: "complete", item: { ...item } });
74
- this.cleanupWorker(id, worker);
75
- this.processNext();
76
- }
77
- else if (msg.type === "error") {
78
- item.state = "error";
79
- item.error = msg.message;
80
- this.options.onEvent({ type: "error", item: { ...item } });
81
- this.cleanupWorker(id, worker);
82
- this.processNext();
83
- }
84
- };
85
- worker.onerror = (e) => {
86
- item.state = "error";
87
- item.error = e.message || "Worker error";
88
- this.options.onEvent({ type: "error", item: { ...item } });
89
- this.cleanupWorker(id, worker);
90
- this.processNext();
91
- };
92
- worker.postMessage({
93
- type: "process",
94
- id,
95
- buffer: input.buffer,
96
- originalName: input.originalName,
97
- mimeType: input.mimeType,
98
- hash: input.hash,
99
- kind: input.kind,
100
- sizes: this.options.sizes,
101
- quality: this.options.quality,
102
- });
103
- }
104
- cleanupWorker(id, worker) {
105
- worker.terminate();
106
- this.activeWorkers.delete(id);
107
- }
108
- destroy() {
109
- for (const [id, worker] of this.activeWorkers) {
110
- worker.terminate();
111
- this.activeWorkers.delete(id);
112
- }
113
- this.pending = [];
114
- this.items.clear();
115
- }
116
- }
@@ -1,50 +0,0 @@
1
- import { createMediaAdapter } from "./index";
2
- export function resolveMedia(item, manifest, sizes) {
3
- if (!item.imageId)
4
- return null;
5
- const adapter = createMediaAdapter(manifest);
6
- return adapter.resolve(item.imageId, sizes);
7
- }
8
- /**
9
- * Deep-walk section content and resolve any { imageId } references to
10
- * { src, srcset } values using the media manifest.
11
- *
12
- * This lets view-mode section components use `item.src` and `item.srcset`
13
- * without needing to know about the manifest themselves.
14
- */
15
- export function resolveManifestReferences(content, manifest, sizes) {
16
- if (Array.isArray(content)) {
17
- return content.map((item) => resolveManifestReferences(item, manifest, sizes));
18
- }
19
- if (content !== null && typeof content === "object") {
20
- const obj = content;
21
- // If this object has an imageId, resolve it and merge resolved fields
22
- if (typeof obj.imageId === "string") {
23
- const manifestItem = manifest.images[obj.imageId];
24
- const resolved = resolveMedia({ imageId: obj.imageId }, manifest, sizes);
25
- const patch = {
26
- alt: manifestItem?.alt ?? "",
27
- };
28
- if (resolved && resolved.tag === "img") {
29
- patch.src = resolved.src;
30
- if ("srcSet" in resolved && resolved.srcSet) {
31
- patch.srcset = resolved.srcSet;
32
- }
33
- }
34
- else if (resolved && resolved.tag === "video") {
35
- patch.src = resolved.src;
36
- if (resolved.poster) {
37
- patch.poster = resolved.poster;
38
- }
39
- }
40
- return { ...obj, ...patch };
41
- }
42
- // Recurse into all values
43
- const result = {};
44
- for (const [key, value] of Object.entries(obj)) {
45
- result[key] = resolveManifestReferences(value, manifest, sizes);
46
- }
47
- return result;
48
- }
49
- return content;
50
- }
@@ -1 +0,0 @@
1
- export { VariantSchema, MediaItemSchema, ImageManifestSchema, MediaConfigSchema, } from "../schemas/media";
@@ -1,41 +0,0 @@
1
- export async function hashFileBuffer(buffer) {
2
- if (typeof crypto !== "undefined" && crypto.subtle) {
3
- const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
4
- const hashArray = new Uint8Array(hashBuffer);
5
- return Array.from(hashArray.slice(0, 8))
6
- .map((b) => b.toString(16).padStart(2, "0"))
7
- .join("");
8
- }
9
- // FNV-1a fallback: two passes with different seeds, concatenated for 16 hex chars
10
- const bytes = new Uint8Array(buffer);
11
- const seeds = [0x811c9dc5, 0x050c5d1f];
12
- let result = "";
13
- for (const seed of seeds) {
14
- let h = seed;
15
- for (let i = 0; i < bytes.length; i++) {
16
- h ^= bytes[i];
17
- h = Math.imul(h, 0x01000193);
18
- }
19
- result += (h >>> 0).toString(16).padStart(8, "0");
20
- }
21
- return result;
22
- }
23
- export function sanitizeMediaName(name) {
24
- return name.replace(/\.[^.]+$/, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
25
- }
26
- export const MIME_TO_EXT = {
27
- "image/gif": "gif", "image/apng": "apng", "image/png": "png",
28
- "image/jpeg": "jpg", "image/webp": "webp", "image/svg+xml": "svg",
29
- "video/mp4": "mp4", "video/webm": "webm",
30
- };
31
- export const EXT_TO_MIME = {
32
- ...Object.fromEntries(Object.entries(MIME_TO_EXT).map(([mime, ext]) => [`.${ext}`, mime])),
33
- ".jpeg": "image/jpeg",
34
- };
35
- export function mimeToExt(mime) {
36
- return MIME_TO_EXT[mime] ?? "bin";
37
- }
38
- export function displayFilenameExt(mime) {
39
- const ext = MIME_TO_EXT[mime];
40
- return ext ? `.${ext}` : "";
41
- }
@@ -1,44 +0,0 @@
1
- export function generateVideoPoster(blob, quality) {
2
- return new Promise((resolve, reject) => {
3
- const video = document.createElement("video");
4
- const url = URL.createObjectURL(blob);
5
- video.muted = true;
6
- video.preload = "auto";
7
- video.src = url;
8
- const cleanup = () => {
9
- URL.revokeObjectURL(url);
10
- video.removeAttribute("src");
11
- video.load();
12
- };
13
- video.addEventListener("loadeddata", () => {
14
- video.addEventListener("seeked", () => {
15
- try {
16
- const w = video.videoWidth;
17
- const h = video.videoHeight;
18
- const canvas = document.createElement("canvas");
19
- canvas.width = w;
20
- canvas.height = h;
21
- const ctx = canvas.getContext("2d");
22
- ctx.drawImage(video, 0, 0);
23
- canvas.toBlob((posterBlob) => {
24
- cleanup();
25
- if (!posterBlob) {
26
- reject(new Error("Failed to create poster blob"));
27
- return;
28
- }
29
- resolve({ posterBlob, width: w, height: h });
30
- }, "image/webp", quality / 100);
31
- }
32
- catch (err) {
33
- cleanup();
34
- reject(err);
35
- }
36
- }, { once: true });
37
- video.currentTime = isFinite(video.duration) ? Math.min(0.1, video.duration / 2) : 0;
38
- }, { once: true });
39
- video.addEventListener("error", () => {
40
- cleanup();
41
- reject(new Error("Failed to load video for poster generation"));
42
- }, { once: true });
43
- });
44
- }
@@ -1,73 +0,0 @@
1
- "use strict";
2
- self.onmessage = async (e) => {
3
- const msg = e.data;
4
- if (msg.type !== "process")
5
- return;
6
- try {
7
- const post = (data) => self.postMessage(data);
8
- post({ type: "progress", id: msg.id, percent: 5 });
9
- if (msg.kind === "animated" || msg.kind === "video") {
10
- const primaryBlob = new Blob([msg.buffer], { type: msg.mimeType });
11
- let posterBlob;
12
- let width = 0;
13
- let height = 0;
14
- if (msg.kind === "animated") {
15
- const bitmap = await createImageBitmap(primaryBlob);
16
- width = bitmap.width;
17
- height = bitmap.height;
18
- const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
19
- const ctx = canvas.getContext("2d");
20
- ctx.drawImage(bitmap, 0, 0);
21
- posterBlob = await canvas.convertToBlob({ type: "image/webp", quality: msg.quality / 100 });
22
- bitmap.close();
23
- }
24
- if (msg.kind === "video") {
25
- width = 0;
26
- height = 0;
27
- }
28
- post({ type: "progress", id: msg.id, percent: 100 });
29
- post({ type: "complete", id: msg.id, variants: [], primaryBlob, posterBlob, width, height });
30
- return;
31
- }
32
- // Static image processing
33
- const originalBlob = new Blob([msg.buffer], { type: msg.mimeType });
34
- const bitmap = await createImageBitmap(originalBlob);
35
- const origWidth = bitmap.width;
36
- const origHeight = bitmap.height;
37
- post({ type: "progress", id: msg.id, percent: 25 });
38
- const applicableSizes = msg.sizes
39
- .filter((s) => s <= origWidth)
40
- .sort((a, b) => b - a);
41
- if (applicableSizes.length === 0) {
42
- applicableSizes.push(origWidth);
43
- }
44
- const variants = [];
45
- const totalVariants = applicableSizes.length;
46
- for (let i = 0; i < applicableSizes.length; i++) {
47
- const targetWidth = applicableSizes[i];
48
- const scale = targetWidth / origWidth;
49
- const targetHeight = Math.round(origHeight * scale);
50
- const canvas = new OffscreenCanvas(targetWidth, targetHeight);
51
- const ctx = canvas.getContext("2d");
52
- ctx.drawImage(bitmap, 0, 0, targetWidth, targetHeight);
53
- const blob = await canvas.convertToBlob({ type: "image/webp", quality: msg.quality / 100 });
54
- variants.push({ width: targetWidth, height: targetHeight, size: blob.size, blob });
55
- const percent = 25 + Math.round(((i + 1) / totalVariants) * 75);
56
- post({ type: "progress", id: msg.id, percent });
57
- }
58
- bitmap.close();
59
- const primaryBlob = variants[0]?.blob ?? originalBlob;
60
- post({
61
- type: "complete",
62
- id: msg.id,
63
- variants,
64
- primaryBlob,
65
- width: origWidth,
66
- height: origHeight,
67
- });
68
- }
69
- catch (err) {
70
- const message = err instanceof Error ? err.message : "Processing failed";
71
- self.postMessage({ type: "error", id: msg.id, message });
72
- }
73
- };
@@ -1,19 +0,0 @@
1
- import { z } from "zod";
2
- import { HexColorSchema } from "./shared";
3
- export const AudienceNameSchema = z
4
- .string()
5
- .min(1)
6
- .max(32)
7
- .regex(/^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$/, "lowercase alphanumeric, dashes and underscores allowed");
8
- export const AudienceColorSchema = HexColorSchema.nullable();
9
- /**
10
- * Convert a friendly display string into an audience slug.
11
- * Lowercases, replaces non-alphanumeric runs with single dashes,
12
- * strips leading/trailing dashes. Result must pass AudienceNameSchema.
13
- */
14
- export function slugifyAudienceName(input) {
15
- return input
16
- .toLowerCase()
17
- .replace(/[^a-z0-9]+/g, "-")
18
- .replace(/^-+|-+$/g, "");
19
- }
@@ -1,22 +0,0 @@
1
- import { z } from "zod";
2
- export const RoleSchema = z.enum(["owner", "editor"]);
3
- export const SessionSchema = z.object({
4
- userId: z.string().nullable(),
5
- email: z.string().nullable(),
6
- role: RoleSchema,
7
- siteId: z.string(),
8
- });
9
- export const SiteUserSchema = z.object({
10
- id: z.string(),
11
- email: z.string(),
12
- role: RoleSchema,
13
- createdAt: z.string(),
14
- });
15
- export const AudienceSchema = z.object({
16
- name: z.string(),
17
- displayName: z.string(),
18
- color: z.string().nullable(),
19
- readonly: z.boolean(),
20
- hasPassword: z.boolean(),
21
- isDefault: z.boolean(),
22
- });
@@ -1,7 +0,0 @@
1
- import { z } from "zod";
2
- export const MediaGridOptionsSchema = z.object({
3
- square: z.boolean().optional(),
4
- border: z.boolean().optional(),
5
- crop: z.boolean().optional(),
6
- showCaptions: z.boolean().optional(),
7
- }).default({});
@@ -1,28 +0,0 @@
1
- import { z } from "zod";
2
- export const VariantSchema = z.object({
3
- width: z.number().int().positive(),
4
- height: z.number().int().positive(),
5
- size: z.number().int().nonnegative(),
6
- });
7
- export const MediaItemSchema = z.object({
8
- id: z.string(),
9
- hash: z.string(),
10
- kind: z.enum(["image", "animated", "video"]),
11
- originalName: z.string(),
12
- width: z.number().int().positive(),
13
- height: z.number().int().positive(),
14
- mimeType: z.string(),
15
- size: z.number().int().nonnegative(),
16
- folder: z.string(),
17
- variants: z.array(VariantSchema),
18
- alt: z.string().default(""),
19
- });
20
- export const ImageManifestSchema = z.object({
21
- images: z.record(z.string(), MediaItemSchema),
22
- });
23
- export const MediaConfigSchema = z.object({
24
- adapter: z.enum(["github"]).default("github"),
25
- sizes: z.array(z.number()).default([640, 1080, 1920]),
26
- maxFileSize: z.number().default(5242880),
27
- quality: z.number().min(1).max(100).default(85),
28
- });
@@ -1,12 +0,0 @@
1
- import { z } from "zod";
2
- import { getAllSchemas } from "../lib/registry";
3
- export function getSectionContentSchema() {
4
- const schemas = getAllSchemas();
5
- if (schemas.length < 2) {
6
- throw new Error("At least 2 section schemas must be registered before validation");
7
- }
8
- return z.union(schemas);
9
- }
10
- export function getSectionSchema() {
11
- return z.object({ id: z.string() }).and(getSectionContentSchema());
12
- }
@@ -1,71 +0,0 @@
1
- import { z } from "zod";
2
- // --- Text ---
3
- const TextHeadingLine = z.object({
4
- type: z.literal("heading"),
5
- text: z.string(),
6
- });
7
- const TextParagraphLine = z.object({
8
- type: z.enum(["paragraph", "paragraph_large"]),
9
- text: z.string(),
10
- });
11
- const TextLabelValueLine = z.object({
12
- type: z.literal("label_value"),
13
- label: z.string(),
14
- text: z.string(),
15
- });
16
- const TextListItemLine = z.object({
17
- type: z.enum(["list_item_unordered", "list_item_ordered"]),
18
- text: z.string(),
19
- });
20
- export const TextLineSchema = z.discriminatedUnion("type", [
21
- TextHeadingLine,
22
- TextParagraphLine,
23
- TextLabelValueLine,
24
- TextListItemLine,
25
- ]);
26
- // --- Media References ---
27
- const BaseMediaRef = z.object({
28
- imageId: z.string().default(""),
29
- caption: z.union([z.string(), z.array(z.string())]).optional(),
30
- background: z.string().optional(),
31
- invertFrom: z.string().optional(),
32
- border: z.boolean().optional(),
33
- objectFit: z.enum(["cover", "contain"]).optional(),
34
- });
35
- const ImageRef = BaseMediaRef.extend({
36
- type: z.literal("image"),
37
- });
38
- const VideoRef = BaseMediaRef.extend({
39
- type: z.literal("video"),
40
- poster: z.string().optional(),
41
- autoplay: z.boolean().optional(),
42
- loop: z.boolean().optional(),
43
- muted: z.boolean().optional(),
44
- });
45
- const DoDontImageRef = BaseMediaRef.extend({
46
- type: z.literal("doDontImage"),
47
- doDont: z.enum(["do", "dont"]),
48
- });
49
- const LinkedImageRef = BaseMediaRef.extend({
50
- type: z.literal("linkedImage"),
51
- href: z.string(),
52
- target: z.string().optional(),
53
- linkText: z.string().optional(),
54
- });
55
- export const MediaReferenceSchema = z.discriminatedUnion("type", [
56
- ImageRef, VideoRef, DoDontImageRef, LinkedImageRef,
57
- ]);
58
- // --- Colors ---
59
- export const HexColorSchema = z
60
- .string()
61
- .regex(/^#[0-9a-fA-F]{6}$/, "must be a 6-digit hex color");
62
- export const ColorSpaceSchema = z.object({
63
- hex: HexColorSchema.optional(),
64
- rgb: z.string().optional(),
65
- cmyk: z.string().optional(),
66
- pantone: z.string().optional(),
67
- }).refine((data) => data.hex !== undefined || data.rgb !== undefined || data.cmyk !== undefined || data.pantone !== undefined, { message: "At least one color space must be defined" });
68
- export const ColorItemSchema = z.object({
69
- name: z.string().optional(),
70
- spaces: z.array(ColorSpaceSchema).min(1),
71
- });
@@ -1,26 +0,0 @@
1
- import { z } from "zod";
2
- import { MediaConfigSchema } from "./media";
3
- import { HexColorSchema } from "./shared";
4
- export const SectionMetaSchema = z.object({
5
- type: z.string(),
6
- status: z.enum(["draft", "published", "archived"]),
7
- access: z.array(z.string()),
8
- });
9
- export const IndexSchema = z.object({
10
- siteId: z.string(),
11
- order: z.array(z.string()),
12
- sections: z.record(z.string(), SectionMetaSchema),
13
- }).refine((data) => data.order.every((id) => id in data.sections), { message: "All order entries must have a corresponding section in sections" });
14
- export const SiteConfigSchema = z.object({
15
- siteName: z.string().default("Brand Portal"),
16
- primaryColor: HexColorSchema.default("#009ca6"),
17
- primaryContrast: HexColorSchema.default("#f0f0f0"),
18
- darkMode: z.enum(["light", "dark", "optional"]).default("light"),
19
- headingFont: z.string().default("system-ui"),
20
- bodyFont: z.string().default("system-ui"),
21
- googleFontsUrl: z.string()
22
- .refine(url => url.startsWith("https://fonts.googleapis.com/"), "Must be a Google Fonts URL")
23
- .nullable()
24
- .default(null),
25
- media: MediaConfigSchema.default(MediaConfigSchema.parse({})),
26
- });