@drawnagency/primitives 0.1.25 → 0.1.27

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 (118) hide show
  1. package/dist/{chunk-UMSFICAC.js → chunk-DKOUFIP6.js} +0 -1
  2. package/dist/{chunk-KX7NRYQD.js → chunk-HXXZBTPF.js} +12 -5
  3. package/dist/{chunk-IP6ODLXX.js → chunk-JHSYLVKI.js} +19 -84
  4. package/dist/{chunk-P24YUT3O.js → chunk-MNK7XA6S.js} +1 -1
  5. package/dist/{chunk-EAEX6DS7.js → chunk-V43WVSVS.js} +3 -2
  6. package/dist/components/editor/SectionOrderingModal.d.ts +10 -0
  7. package/dist/components/editor/SectionOrderingModal.d.ts.map +1 -0
  8. package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
  9. package/dist/components/primitives/EditableRichText.d.ts.map +1 -1
  10. package/dist/components/sections/Button/index.d.ts.map +1 -1
  11. package/dist/components/sections/Colors/index.d.ts.map +1 -1
  12. package/dist/components/sections/DoDontMediaGrid/index.d.ts.map +1 -1
  13. package/dist/components/sections/IconList/index.d.ts.map +1 -1
  14. package/dist/components/sections/LinkHeading/index.d.ts.map +1 -1
  15. package/dist/components/sections/MediaGrid/index.d.ts.map +1 -1
  16. package/dist/components/sections/Prose/index.d.ts.map +1 -1
  17. package/dist/components/sections/SplitContent/index.d.ts.map +1 -1
  18. package/dist/components/sections/SubHeading/index.d.ts.map +1 -1
  19. package/dist/components/sections/SubSubHeading/index.d.ts.map +1 -1
  20. package/dist/components/sections/ViewRenderer.d.ts +0 -1
  21. package/dist/components/sections/ViewRenderer.d.ts.map +1 -1
  22. package/dist/components/sections/register-schemas.d.ts.map +1 -1
  23. package/dist/components/sections/register.d.ts.map +1 -1
  24. package/dist/components/shared/HistoryPopover.d.ts.map +1 -1
  25. package/dist/components/shared/Navigation.d.ts.map +1 -1
  26. package/dist/components/shell/EditorShell.d.ts +0 -1
  27. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  28. package/dist/components/shell/HistoryToolbar.d.ts.map +1 -1
  29. package/dist/components/shell/RestoreModal.d.ts.map +1 -1
  30. package/dist/deploy/index.d.ts +2 -0
  31. package/dist/deploy/index.d.ts.map +1 -0
  32. package/dist/deploy/types.d.ts +12 -0
  33. package/dist/deploy/types.d.ts.map +1 -0
  34. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  35. package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +10 -8
  39. package/dist/lib/dexie.d.ts +4 -1
  40. package/dist/lib/dexie.d.ts.map +1 -1
  41. package/dist/lib/dexie.js +319 -0
  42. package/dist/lib/index.js +3 -3
  43. package/dist/lib/nav.d.ts +2 -6
  44. package/dist/lib/nav.d.ts.map +1 -1
  45. package/dist/lib/registry.d.ts +14 -0
  46. package/dist/lib/registry.d.ts.map +1 -1
  47. package/dist/lib/text.d.ts +3 -0
  48. package/dist/lib/text.d.ts.map +1 -0
  49. package/dist/lib/timestamp.d.ts +2 -0
  50. package/dist/lib/timestamp.d.ts.map +1 -1
  51. package/dist/media/index.d.ts +4 -2
  52. package/dist/media/index.d.ts.map +1 -1
  53. package/dist/media/index.js +8 -6
  54. package/dist/media/provider.d.ts +7 -0
  55. package/dist/media/provider.d.ts.map +1 -0
  56. package/dist/media/resolve.d.ts +3 -2
  57. package/dist/media/resolve.d.ts.map +1 -1
  58. package/dist/media/types.d.ts +0 -9
  59. package/dist/media/types.d.ts.map +1 -1
  60. package/dist/schemas/index.js +3 -3
  61. package/dist/schemas/media.d.ts +0 -3
  62. package/dist/schemas/media.d.ts.map +1 -1
  63. package/dist/schemas/site-config.d.ts +1 -3
  64. package/dist/schemas/site-config.d.ts.map +1 -1
  65. package/dist/storage/index.d.ts +2 -0
  66. package/dist/storage/index.d.ts.map +1 -0
  67. package/dist/storage/types.d.ts +21 -0
  68. package/dist/storage/types.d.ts.map +1 -0
  69. package/package.json +5 -1
  70. package/src/components/editor/DragHandle.tsx +1 -1
  71. package/src/components/editor/SectionOrderingModal.tsx +215 -0
  72. package/src/components/editor/SectionWrapper.tsx +3 -1
  73. package/src/components/primitives/EditableRichText.tsx +4 -2
  74. package/src/components/sections/Button/index.tsx +1 -0
  75. package/src/components/sections/Colors/index.tsx +8 -0
  76. package/src/components/sections/DoDontMediaGrid/index.tsx +8 -0
  77. package/src/components/sections/IconList/index.tsx +4 -0
  78. package/src/components/sections/LinkHeading/index.tsx +2 -0
  79. package/src/components/sections/MediaGrid/index.tsx +8 -0
  80. package/src/components/sections/Prose/index.tsx +2 -0
  81. package/src/components/sections/SplitContent/index.tsx +16 -2
  82. package/src/components/sections/SubHeading/index.tsx +2 -0
  83. package/src/components/sections/SubSubHeading/index.tsx +2 -0
  84. package/src/components/sections/ViewRenderer.tsx +3 -1
  85. package/src/components/sections/register-schemas.ts +0 -2
  86. package/src/components/sections/register.ts +0 -2
  87. package/src/components/shared/HistoryPopover.tsx +2 -33
  88. package/src/components/shared/Navigation.tsx +2 -5
  89. package/src/components/shell/EditorShell.tsx +40 -9
  90. package/src/components/shell/HistoryToolbar.tsx +2 -7
  91. package/src/components/shell/RestoreModal.tsx +2 -7
  92. package/src/deploy/index.ts +1 -0
  93. package/src/deploy/types.ts +12 -0
  94. package/src/hooks/useEditorPublish.ts +18 -43
  95. package/src/hooks/useMediaPipeline.ts +41 -11
  96. package/src/hooks/useResolvedMedia.ts +3 -3
  97. package/src/index.ts +2 -0
  98. package/src/lib/dexie.ts +28 -1
  99. package/src/lib/nav.ts +16 -9
  100. package/src/lib/registry.ts +10 -0
  101. package/src/lib/text.ts +8 -0
  102. package/src/lib/timestamp.ts +23 -0
  103. package/src/media/index.ts +13 -4
  104. package/src/media/provider.ts +7 -0
  105. package/src/media/resolve.ts +9 -6
  106. package/src/media/types.ts +0 -9
  107. package/src/schemas/media.ts +0 -1
  108. package/src/schemas/site-config.ts +1 -0
  109. package/src/storage/index.ts +1 -0
  110. package/src/storage/types.ts +23 -0
  111. package/dist/components/sections/SplitContent/SplitContentSettings.d.ts +0 -9
  112. package/dist/components/sections/SplitContent/SplitContentSettings.d.ts.map +0 -1
  113. package/dist/media/github.d.ts +0 -3
  114. package/dist/media/github.d.ts.map +0 -1
  115. package/src/components/sections/SplitContent/SplitContentSettings.d.ts +0 -9
  116. package/src/components/sections/SplitContent/SplitContentSettings.d.ts.map +0 -1
  117. package/src/components/sections/SplitContent/SplitContentSettings.tsx +0 -42
  118. package/src/media/github.ts +0 -72
@@ -0,0 +1,8 @@
1
+ export function stripHtmlToPlainText(html: string): string {
2
+ return html.replace(/<\/[^>]+>/g, " ").replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
3
+ }
4
+
5
+ export function truncate(text: string, maxLength: number): string {
6
+ if (text.length <= maxLength) return text;
7
+ return text.slice(0, maxLength) + "...";
8
+ }
@@ -1,3 +1,26 @@
1
+ export function formatDate(iso: string): string {
2
+ return new Date(iso).toLocaleDateString("en-US", {
3
+ month: "short",
4
+ day: "numeric",
5
+ year: "numeric",
6
+ });
7
+ }
8
+
9
+ export function formatDateTime(iso: string): string {
10
+ const date = new Date(iso);
11
+ const datePart = date.toLocaleDateString("en-US", {
12
+ month: "short",
13
+ day: "numeric",
14
+ year: "numeric",
15
+ });
16
+ const timePart = date.toLocaleTimeString("en-US", {
17
+ hour: "numeric",
18
+ minute: "2-digit",
19
+ hour12: true,
20
+ });
21
+ return `${datePart} | ${timePart}`;
22
+ }
23
+
1
24
  export function formatTimestamp(iso: string): string {
2
25
  const date = new Date(iso);
3
26
  const now = new Date();
@@ -1,12 +1,21 @@
1
- import type { MediaAdapter, MediaManifest } from "./types";
2
- import { createGitHubMediaAdapter } from "./github";
1
+ import type { MediaProvider } from "./provider";
3
2
 
4
3
  export * from "./types";
4
+ export * from "./provider";
5
5
  export * from "./utils";
6
6
  export * from "./queue";
7
7
  export * from "./resolve";
8
8
  export { generateVideoPoster } from "./videoPoster";
9
9
 
10
- export function createMediaAdapter(manifest: MediaManifest): MediaAdapter {
11
- return createGitHubMediaAdapter(manifest);
10
+ let _provider: MediaProvider | null = null;
11
+
12
+ export function setMediaProvider(p: MediaProvider): void {
13
+ _provider = p;
14
+ }
15
+
16
+ export function getMediaProvider(): MediaProvider {
17
+ if (!_provider) {
18
+ throw new Error("No MediaProvider registered. Call setMediaProvider() before using getMediaProvider().");
19
+ }
20
+ return _provider;
12
21
  }
@@ -0,0 +1,7 @@
1
+ import type { MediaManifest, MediaItem, ResolvedMedia } from "./types";
2
+
3
+ export interface MediaProvider {
4
+ getManifest(): Promise<MediaManifest>;
5
+ resolve(item: MediaItem, sizes: number[]): ResolvedMedia | null;
6
+ exists(hash: string, manifest: MediaManifest): boolean;
7
+ }
@@ -1,14 +1,16 @@
1
1
  import type { MediaManifest, ResolvedMedia } from "./types";
2
- import { createMediaAdapter } from "./index";
2
+ import type { MediaProvider } from "./provider";
3
3
 
4
4
  export function resolveMedia(
5
5
  item: { imageId?: string; src?: string },
6
6
  manifest: MediaManifest,
7
7
  sizes: number[],
8
+ provider: MediaProvider,
8
9
  ): ResolvedMedia | null {
9
10
  if (!item.imageId) return null;
10
- const adapter = createMediaAdapter(manifest);
11
- return adapter.resolve(item.imageId, sizes);
11
+ const mediaItem = manifest.images[item.imageId];
12
+ if (!mediaItem) return null;
13
+ return provider.resolve(mediaItem, sizes);
12
14
  }
13
15
 
14
16
  /**
@@ -22,9 +24,10 @@ export function resolveManifestReferences(
22
24
  content: unknown,
23
25
  manifest: MediaManifest,
24
26
  sizes: number[],
27
+ provider: MediaProvider,
25
28
  ): unknown {
26
29
  if (Array.isArray(content)) {
27
- return content.map((item) => resolveManifestReferences(item, manifest, sizes));
30
+ return content.map((item) => resolveManifestReferences(item, manifest, sizes, provider));
28
31
  }
29
32
 
30
33
  if (content !== null && typeof content === "object") {
@@ -33,7 +36,7 @@ export function resolveManifestReferences(
33
36
  // If this object has an imageId, resolve it and merge resolved fields
34
37
  if (typeof obj.imageId === "string") {
35
38
  const manifestItem = manifest.images[obj.imageId];
36
- const resolved = resolveMedia({ imageId: obj.imageId }, manifest, sizes);
39
+ const resolved = resolveMedia({ imageId: obj.imageId }, manifest, sizes, provider);
37
40
  const patch: Record<string, unknown> = {
38
41
  alt: manifestItem?.alt ?? "",
39
42
  };
@@ -54,7 +57,7 @@ export function resolveManifestReferences(
54
57
  // Recurse into all values
55
58
  const result: Record<string, unknown> = {};
56
59
  for (const [key, value] of Object.entries(obj)) {
57
- result[key] = resolveManifestReferences(value, manifest, sizes);
60
+ result[key] = resolveManifestReferences(value, manifest, sizes, provider);
58
61
  }
59
62
  return result;
60
63
  }
@@ -47,12 +47,3 @@ export interface ResolvedVideo {
47
47
  }
48
48
 
49
49
  export type ResolvedMedia = ResolvedImage | ResolvedAnimated | ResolvedVideo;
50
-
51
- export interface MediaAdapter {
52
- hash(file: MediaFile): Promise<string>;
53
- exists(hash: string): Promise<boolean>;
54
- process(file: MediaFile, hash: string, sizes: number[]): Promise<MediaItem>;
55
- delete(id: string): Promise<void>;
56
- resolve(id: string, sizes: number[]): ResolvedMedia | null;
57
- list(): Promise<MediaItem[]>;
58
- }
@@ -31,7 +31,6 @@ export const ImageManifestSchema = z.object({
31
31
  export type MediaManifest = z.infer<typeof ImageManifestSchema>;
32
32
 
33
33
  export const MediaConfigSchema = z.object({
34
- adapter: z.enum(["github"]).default("github"),
35
34
  sizes: z.array(z.number()).default([640, 1080, 1920]),
36
35
  maxFileSize: z.number().default(5242880),
37
36
  quality: z.number().min(1).max(100).default(85),
@@ -18,6 +18,7 @@ export const IndexSchema = z.object({
18
18
  siteId: z.string(),
19
19
  order: z.array(z.string()),
20
20
  sections: z.record(z.string(), SectionMetaSchema),
21
+ lastModified: z.string().nullable().optional(),
21
22
  }).refine(
22
23
  (data) => data.order.every((id) => id in data.sections),
23
24
  { message: "All order entries must have a corresponding section in sections" }
@@ -0,0 +1 @@
1
+ export type { StorageProvider, FileWrite } from "./types";
@@ -0,0 +1,23 @@
1
+ export interface StorageProvider {
2
+ writeFiles(
3
+ files: FileWrite[],
4
+ message: string,
5
+ ): Promise<{ version: string }>;
6
+
7
+ readFile(
8
+ path: string,
9
+ options?: { draft?: boolean },
10
+ ): Promise<Uint8Array | null>;
11
+
12
+ promoteDraft(): Promise<{ version: string }>;
13
+
14
+ getVersion(): Promise<string>;
15
+
16
+ listDirectory(path: string, options?: { draft?: boolean }): Promise<string[]>;
17
+ }
18
+
19
+ export type FileWrite = {
20
+ path: string;
21
+ content: string | Uint8Array | null;
22
+ encoding?: "utf-8" | "base64";
23
+ };
@@ -1,9 +0,0 @@
1
- export declare function SplitContentSettings({ imagePosition: initialPos, border: initialBorder, onChange, }: {
2
- imagePosition: "left" | "right";
3
- border: boolean;
4
- onChange: (values: {
5
- imagePosition: "left" | "right";
6
- border: boolean;
7
- }) => void;
8
- }): import("react/jsx-runtime").JSX.Element;
9
- //# sourceMappingURL=SplitContentSettings.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SplitContentSettings.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/SplitContent/SplitContentSettings.tsx"],"names":[],"mappings":"AAIA,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EAAE,UAAU,EACzB,MAAM,EAAE,aAAa,EACrB,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CAClF,2CA6BA"}
@@ -1,3 +0,0 @@
1
- import type { MediaAdapter, MediaManifest } from "./types";
2
- export declare function createGitHubMediaAdapter(manifest: MediaManifest): MediaAdapter;
3
- //# sourceMappingURL=github.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/media/github.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAwB,aAAa,EAAiB,MAAM,SAAS,CAAC;AAGhG,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,aAAa,GAAG,YAAY,CAoE9E"}
@@ -1,9 +0,0 @@
1
- export declare function SplitContentSettings({ imagePosition: initialPos, border: initialBorder, onChange, }: {
2
- imagePosition: "left" | "right";
3
- border: boolean;
4
- onChange: (values: {
5
- imagePosition: "left" | "right";
6
- border: boolean;
7
- }) => void;
8
- }): import("react/jsx-runtime").JSX.Element;
9
- //# sourceMappingURL=SplitContentSettings.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SplitContentSettings.d.ts","sourceRoot":"","sources":["SplitContentSettings.tsx"],"names":[],"mappings":"AAIA,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EAAE,UAAU,EACzB,MAAM,EAAE,aAAa,EACrB,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CAClF,2CA6BA"}
@@ -1,42 +0,0 @@
1
- import { useState } from "react";
2
- import { Select } from "../../shared/Select";
3
- import { Checkbox } from "../../shared/Checkbox";
4
-
5
- export function SplitContentSettings({
6
- imagePosition: initialPos,
7
- border: initialBorder,
8
- onChange,
9
- }: {
10
- imagePosition: "left" | "right";
11
- border: boolean;
12
- onChange: (values: { imagePosition: "left" | "right"; border: boolean }) => void;
13
- }) {
14
- const [imagePosition, setImagePosition] = useState(initialPos);
15
- const [border, setBorder] = useState(initialBorder);
16
-
17
- const emit = (overrides: Partial<{ imagePosition: "left" | "right"; border: boolean }>) =>
18
- onChange({ imagePosition, border, ...overrides });
19
-
20
- return (
21
- <div className="space-y-4">
22
- <Select
23
- label="Image Position"
24
- value={imagePosition}
25
- onChange={(val) => {
26
- const v = val as "left" | "right";
27
- setImagePosition(v);
28
- emit({ imagePosition: v });
29
- }}
30
- options={[
31
- { value: "left", label: "Left" },
32
- { value: "right", label: "Right" },
33
- ]}
34
- />
35
- <Checkbox
36
- checked={border}
37
- onChange={(v) => { setBorder(v); emit({ border: v }); }}
38
- label="Show border"
39
- />
40
- </div>
41
- );
42
- }
@@ -1,72 +0,0 @@
1
- import type { MediaAdapter, MediaFile, MediaItem, MediaManifest, ResolvedMedia } from "./types";
2
- import { sanitizeMediaName, mimeToExt, hashFileBuffer } from "./utils";
3
-
4
- export function createGitHubMediaAdapter(manifest: MediaManifest): MediaAdapter {
5
- return {
6
- async hash(file: MediaFile): Promise<string> {
7
- return hashFileBuffer(file.buffer);
8
- },
9
-
10
- async exists(hash: string): Promise<boolean> {
11
- return hash in manifest.images;
12
- },
13
-
14
- async process(file: MediaFile, hash: string, _sizes: number[]): Promise<MediaItem> {
15
- const name = sanitizeMediaName(file.originalName);
16
- const folder = `${name}-${hash}`;
17
- return {
18
- id: hash, hash, kind: "image", originalName: name,
19
- width: 0, height: 0, mimeType: "image/webp", size: 0, folder, variants: [], alt: "",
20
- };
21
- },
22
-
23
- async delete(id: string): Promise<void> {
24
- delete manifest.images[id];
25
- },
26
-
27
- resolve(id: string, sizes: number[]): ResolvedMedia | null {
28
- const item = manifest.images[id];
29
- if (!item) return null;
30
-
31
- if (item.kind === "video") {
32
- return {
33
- tag: "video",
34
- src: `/api/media/${id}/original.${mimeToExt(item.mimeType)}`,
35
- poster: `/api/media/${id}/poster.webp`,
36
- width: item.width, height: item.height,
37
- autoplay: true, loop: true, muted: true,
38
- };
39
- }
40
-
41
- if (item.kind === "animated") {
42
- return {
43
- tag: "img",
44
- src: `/api/media/${id}/original.${mimeToExt(item.mimeType)}`,
45
- width: item.width, height: item.height,
46
- };
47
- }
48
-
49
- // Static image with srcset
50
- const availableVariants = item.variants.filter((v) =>
51
- sizes.length === 0 || sizes.includes(v.width),
52
- );
53
- const sorted = [...availableVariants].sort((a, b) => a.width - b.width);
54
- const midIndex = Math.floor(sorted.length / 2);
55
- const defaultVariant = sorted[midIndex] ?? sorted[0];
56
- if (!defaultVariant) return null;
57
-
58
- const srcSet = sorted.map((v) => `/api/media/${id}/${v.width}.webp ${v.width}w`).join(", ");
59
-
60
- return {
61
- tag: "img",
62
- src: `/api/media/${id}/${defaultVariant.width}.webp`,
63
- srcSet,
64
- width: item.width, height: item.height,
65
- };
66
- },
67
-
68
- async list(): Promise<MediaItem[]> {
69
- return Object.values(manifest.images);
70
- },
71
- };
72
- }