@drawnagency/primitives 0.1.76 → 0.1.78

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 (122) hide show
  1. package/dist/{chunk-Q3T23HVW.js → chunk-2DDWWTRX.js} +5 -5
  2. package/dist/chunk-2G2S36SY.js +13 -0
  3. package/dist/{chunk-S55OZ7F2.js → chunk-3LNKOLZP.js} +4 -0
  4. package/dist/{chunk-SWIHUIFT.js → chunk-4SIWBWKZ.js} +12 -2
  5. package/dist/{chunk-SWEVWC5K.js → chunk-BAQYZSFF.js} +22 -22
  6. package/dist/{chunk-IFHRHV7S.js → chunk-EMGLZQCM.js} +1 -1
  7. package/dist/{chunk-NKVZGPR6.js → chunk-K4JD6FD3.js} +1 -1
  8. package/dist/{chunk-SFKYUKVN.js → chunk-MK4POMLZ.js} +1 -1
  9. package/dist/{chunk-7T2WQSWF.js → chunk-NS3UTJ3E.js} +11 -11
  10. package/dist/chunk-REM2NH7K.js +44 -0
  11. package/dist/{chunk-T7MP7ANP.js → chunk-UA6OCJJK.js} +2 -2
  12. package/dist/{chunk-ZRWGV5O6.js → chunk-Z5IYWUW3.js} +3 -3
  13. package/dist/collab/provider-registry.d.ts +4 -0
  14. package/dist/collab/provider-registry.d.ts.map +1 -0
  15. package/dist/collab/provider-registry.js +9 -0
  16. package/dist/collab/rebase.d.ts +37 -0
  17. package/dist/collab/rebase.d.ts.map +1 -0
  18. package/dist/collab/rebase.js +64 -0
  19. package/dist/collab/recovery.d.ts +89 -0
  20. package/dist/collab/recovery.d.ts.map +1 -0
  21. package/dist/collab/types.d.ts +93 -0
  22. package/dist/collab/types.d.ts.map +1 -0
  23. package/dist/collab/types.js +19 -0
  24. package/dist/components/editor/AudienceIndicator.d.ts +3 -1
  25. package/dist/components/editor/AudienceIndicator.d.ts.map +1 -1
  26. package/dist/components/editor/ConflictDialog.d.ts +14 -0
  27. package/dist/components/editor/ConflictDialog.d.ts.map +1 -0
  28. package/dist/components/editor/IndicatorPill.d.ts +4 -1
  29. package/dist/components/editor/IndicatorPill.d.ts.map +1 -1
  30. package/dist/components/editor/LockBadge.d.ts +7 -0
  31. package/dist/components/editor/LockBadge.d.ts.map +1 -0
  32. package/dist/components/editor/PresenceAvatars.d.ts +9 -0
  33. package/dist/components/editor/PresenceAvatars.d.ts.map +1 -0
  34. package/dist/components/editor/SectionWrapper.d.ts +1 -1
  35. package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
  36. package/dist/components/editor/StatusIndicator.d.ts +3 -1
  37. package/dist/components/editor/StatusIndicator.d.ts.map +1 -1
  38. package/dist/components/editor/index.js +95 -47
  39. package/dist/components/primitives/index.js +5 -5
  40. package/dist/components/sections/brand-guide.js +5 -5
  41. package/dist/components/sections/register-schemas.js +11 -11
  42. package/dist/components/sections/register.js +11 -11
  43. package/dist/components/shell/EditorShell.d.ts +3 -1
  44. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  45. package/dist/components/shell/MediaBreadcrumb.d.ts +10 -0
  46. package/dist/components/shell/MediaBreadcrumb.d.ts.map +1 -0
  47. package/dist/components/shell/MediaFolderTile.d.ts +15 -0
  48. package/dist/components/shell/MediaFolderTile.d.ts.map +1 -0
  49. package/dist/components/shell/MediaFolderTree.d.ts +20 -0
  50. package/dist/components/shell/MediaFolderTree.d.ts.map +1 -0
  51. package/dist/components/shell/MediaLibraryModal.d.ts +15 -3
  52. package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
  53. package/dist/components/shell/MoveToFolderDialog.d.ts +16 -0
  54. package/dist/components/shell/MoveToFolderDialog.d.ts.map +1 -0
  55. package/dist/hooks/useCollabSession.d.ts +23 -0
  56. package/dist/hooks/useCollabSession.d.ts.map +1 -0
  57. package/dist/hooks/useEditorPersistence.d.ts +21 -1
  58. package/dist/hooks/useEditorPersistence.d.ts.map +1 -1
  59. package/dist/hooks/useEditorPublish.d.ts +39 -2
  60. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  61. package/dist/hooks/useMediaFolderDnd.d.ts +27 -0
  62. package/dist/hooks/useMediaFolderDnd.d.ts.map +1 -0
  63. package/dist/hooks/useMediaPipeline.d.ts +14 -2
  64. package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
  65. package/dist/index.d.ts +2 -0
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +51 -25
  68. package/dist/lib/dexie.d.ts +12 -0
  69. package/dist/lib/dexie.d.ts.map +1 -1
  70. package/dist/lib/dexie.js +27 -0
  71. package/dist/lib/index.js +12 -12
  72. package/dist/lib/media-dnd.d.ts +26 -0
  73. package/dist/lib/media-dnd.d.ts.map +1 -0
  74. package/dist/lib/media-folders.d.ts +59 -0
  75. package/dist/lib/media-folders.d.ts.map +1 -0
  76. package/dist/lib/registry.d.ts +10 -0
  77. package/dist/lib/registry.d.ts.map +1 -1
  78. package/dist/media/folder-drop.d.ts +22 -0
  79. package/dist/media/folder-drop.d.ts.map +1 -0
  80. package/dist/media/index.js +6 -2
  81. package/dist/media/types.d.ts +1 -1
  82. package/dist/media/types.d.ts.map +1 -1
  83. package/dist/media/utils.d.ts +7 -0
  84. package/dist/media/utils.d.ts.map +1 -1
  85. package/dist/schemas/index.js +5 -3
  86. package/dist/schemas/media.d.ts +11 -0
  87. package/dist/schemas/media.d.ts.map +1 -1
  88. package/dist/types/database.d.ts +70 -0
  89. package/dist/types/database.d.ts.map +1 -1
  90. package/package.json +13 -1
  91. package/src/collab/provider-registry.ts +14 -0
  92. package/src/collab/rebase.ts +140 -0
  93. package/src/collab/recovery.ts +149 -0
  94. package/src/collab/types.ts +96 -0
  95. package/src/components/editor/AudienceIndicator.tsx +4 -1
  96. package/src/components/editor/ConflictDialog.tsx +61 -0
  97. package/src/components/editor/IndicatorPill.tsx +12 -6
  98. package/src/components/editor/LockBadge.tsx +31 -0
  99. package/src/components/editor/PresenceAvatars.tsx +53 -0
  100. package/src/components/editor/SectionWrapper.tsx +60 -29
  101. package/src/components/editor/StatusIndicator.tsx +4 -0
  102. package/src/components/shell/EditorShell.tsx +581 -22
  103. package/src/components/shell/MediaBreadcrumb.tsx +77 -0
  104. package/src/components/shell/MediaFolderTile.tsx +141 -0
  105. package/src/components/shell/MediaFolderTree.tsx +127 -0
  106. package/src/components/shell/MediaLibraryModal.tsx +611 -229
  107. package/src/components/shell/MoveToFolderDialog.tsx +78 -0
  108. package/src/hooks/useCollabSession.ts +220 -0
  109. package/src/hooks/useEditorPersistence.ts +104 -1
  110. package/src/hooks/useEditorPublish.ts +216 -71
  111. package/src/hooks/useMediaFolderDnd.ts +129 -0
  112. package/src/hooks/useMediaPipeline.ts +162 -6
  113. package/src/index.ts +2 -0
  114. package/src/lib/dexie.ts +44 -0
  115. package/src/lib/media-dnd.ts +62 -0
  116. package/src/lib/media-folders.ts +232 -0
  117. package/src/lib/registry.ts +8 -0
  118. package/src/media/folder-drop.ts +62 -0
  119. package/src/media/types.ts +2 -0
  120. package/src/media/utils.ts +10 -0
  121. package/src/schemas/media.ts +13 -0
  122. package/src/types/database.ts +55 -0
@@ -0,0 +1,62 @@
1
+ export interface DroppedFile {
2
+ file: File;
3
+ relativePath: string;
4
+ }
5
+
6
+ /** Structural subset of FileSystemEntry — lets tests fake the tree. */
7
+ export interface EntryLike {
8
+ isFile: boolean;
9
+ isDirectory: boolean;
10
+ name: string;
11
+ fullPath: string;
12
+ file?: (cb: (f: File) => void, err: (e: unknown) => void) => void;
13
+ createReader?: () => {
14
+ readEntries: (cb: (entries: EntryLike[]) => void, err: (e: unknown) => void) => void;
15
+ };
16
+ }
17
+
18
+ const readAll = (reader: NonNullable<ReturnType<NonNullable<EntryLike["createReader"]>>>): Promise<EntryLike[]> =>
19
+ new Promise((resolve, reject) => {
20
+ const all: EntryLike[] = [];
21
+ const step = () =>
22
+ reader.readEntries((batch) => {
23
+ if (batch.length === 0) return resolve(all);
24
+ all.push(...batch);
25
+ // Defer rather than call step() synchronously: readers return results
26
+ // in batches and loop until empty, but a synchronous re-entrant call
27
+ // here would run before any state a caller sets after this callback
28
+ // returns (e.g. a "served" flag in a synchronous test fake) — causing
29
+ // infinite recursion. Real FileSystemDirectoryReader impls are async
30
+ // anyway, so the microtask tick is free in the browser.
31
+ queueMicrotask(step);
32
+ }, reject);
33
+ step();
34
+ });
35
+
36
+ export async function traverseEntries(entries: EntryLike[]): Promise<DroppedFile[]> {
37
+ const out: DroppedFile[] = [];
38
+ for (const entry of entries) {
39
+ if (entry.isFile && entry.file) {
40
+ const file = await new Promise<File>((resolve, reject) => entry.file!(resolve, reject));
41
+ out.push({ file, relativePath: entry.fullPath.replace(/^\//, "") });
42
+ } else if (entry.isDirectory && entry.createReader) {
43
+ out.push(...(await traverseEntries(await readAll(entry.createReader()))));
44
+ }
45
+ }
46
+ return out;
47
+ }
48
+
49
+ /**
50
+ * Dropped-payload traversal. Returns null when nothing in the drop is a
51
+ * directory — the caller should fall back to the plain flat-file path.
52
+ */
53
+ export async function collectDroppedFiles(dataTransfer: DataTransfer): Promise<DroppedFile[] | null> {
54
+ const entries: EntryLike[] = [];
55
+ for (const item of Array.from(dataTransfer.items)) {
56
+ const getEntry = (item as DataTransferItem & { webkitGetAsEntry?: () => EntryLike | null }).webkitGetAsEntry;
57
+ const entry = typeof getEntry === "function" ? getEntry.call(item) : null;
58
+ if (entry) entries.push(entry);
59
+ }
60
+ if (!entries.some((e) => e.isDirectory)) return null;
61
+ return traverseEntries(entries);
62
+ }
@@ -3,10 +3,12 @@ export {
3
3
  MediaItemSchema,
4
4
  ImageManifestSchema,
5
5
  MediaConfigSchema,
6
+ MediaFolderSchema,
6
7
  type Variant,
7
8
  type MediaItem,
8
9
  type MediaManifest,
9
10
  type MediaConfig,
11
+ type MediaFolder,
10
12
  } from "../schemas/media";
11
13
 
12
14
  import type { MediaItem } from "../schemas/media";
@@ -1,4 +1,5 @@
1
1
  import { slugifyPageSlug } from "../lib/pages";
2
+ import type { MediaItem } from "./types";
2
3
 
3
4
  export async function hashFileBuffer(buffer: ArrayBuffer): Promise<string> {
4
5
  if (typeof crypto !== "undefined" && crypto.subtle) {
@@ -65,3 +66,12 @@ export function formatFileSize(bytes: number): string {
65
66
  if (bytes >= 1024) return `${Math.round(bytes / 1024)}KB`;
66
67
  return `${bytes}B`;
67
68
  }
69
+
70
+ /**
71
+ * Display name for a media item. v2 batch-rename seam: will read a future
72
+ * optional `displayName` field with originalName fallback — UI must call this
73
+ * instead of touching originalName directly.
74
+ */
75
+ export function displayName(item: Pick<MediaItem, "originalName">): string {
76
+ return item.originalName;
77
+ }
@@ -8,6 +8,15 @@ export const VariantSchema = z.object({
8
8
 
9
9
  export type Variant = z.infer<typeof VariantSchema>;
10
10
 
11
+ // Display folders for the media library. Pure metadata: a folder assignment
12
+ // never moves files in git (that's MediaItem.folder, the storage dir).
13
+ export const MediaFolderSchema = z.object({
14
+ name: z.string().min(1),
15
+ parentId: z.string().nullable(), // null = root
16
+ });
17
+
18
+ export type MediaFolder = z.infer<typeof MediaFolderSchema>;
19
+
11
20
  export const MediaItemSchema = z.object({
12
21
  id: z.string(),
13
22
  hash: z.string(),
@@ -29,12 +38,16 @@ export const MediaItemSchema = z.object({
29
38
  url: z.string().optional(),
30
39
  // Seconds; videos only. Informational (library UI).
31
40
  duration: z.number().positive().optional(),
41
+ // Display folder (MediaFolderSchema key). Absent = root/unfiled. NOT the
42
+ // git storage dir — that's `folder` above, which never changes.
43
+ folderId: z.string().optional(),
32
44
  });
33
45
 
34
46
  export type MediaItem = z.infer<typeof MediaItemSchema>;
35
47
 
36
48
  export const ImageManifestSchema = z.object({
37
49
  images: z.record(z.string(), MediaItemSchema),
50
+ folders: z.record(z.string(), MediaFolderSchema).default({}),
38
51
  });
39
52
 
40
53
  export type MediaManifest = z.infer<typeof ImageManifestSchema>;
@@ -223,6 +223,41 @@ export type Database = {
223
223
  },
224
224
  ]
225
225
  }
226
+ editor_locks: {
227
+ Row: {
228
+ acquired_at: string
229
+ email: string
230
+ heartbeat_at: string
231
+ section_id: string
232
+ site_id: string
233
+ user_id: string
234
+ }
235
+ Insert: {
236
+ acquired_at?: string
237
+ email: string
238
+ heartbeat_at?: string
239
+ section_id: string
240
+ site_id: string
241
+ user_id: string
242
+ }
243
+ Update: {
244
+ acquired_at?: string
245
+ email?: string
246
+ heartbeat_at?: string
247
+ section_id?: string
248
+ site_id?: string
249
+ user_id?: string
250
+ }
251
+ Relationships: [
252
+ {
253
+ foreignKeyName: "editor_locks_site_id_fkey"
254
+ columns: ["site_id"]
255
+ isOneToOne: false
256
+ referencedRelation: "sites"
257
+ referencedColumns: ["id"]
258
+ },
259
+ ]
260
+ }
226
261
  github_installations: {
227
262
  Row: {
228
263
  created_at: string | null
@@ -680,6 +715,17 @@ export type Database = {
680
715
  [_ in never]: never
681
716
  }
682
717
  Functions: {
718
+ acquire_section_lock: {
719
+ Args: { p_section_id: string; p_site_id: string; p_steal?: boolean }
720
+ Returns: {
721
+ acquired_at: string
722
+ email: string
723
+ heartbeat_at: string
724
+ section_id: string
725
+ site_id: string
726
+ user_id: string
727
+ }[]
728
+ }
683
729
  chat_usage_hit: {
684
730
  Args: {
685
731
  p_day: string
@@ -690,7 +736,12 @@ export type Database = {
690
736
  }
691
737
  Returns: undefined
692
738
  }
739
+ collab_site_member: { Args: { p_site_id: string }; Returns: boolean }
693
740
  get_user_id_by_email: { Args: { lookup_email: string }; Returns: string }
741
+ heartbeat_section_locks: {
742
+ Args: { p_section_ids: string[]; p_site_id: string }
743
+ Returns: undefined
744
+ }
694
745
  is_site_member: { Args: { p_site_id: string }; Returns: boolean }
695
746
  is_site_owner: { Args: { p_site_id: string }; Returns: boolean }
696
747
  oauth_cleanup: { Args: { p_max_age_ms: number }; Returns: undefined }
@@ -716,6 +767,10 @@ export type Database = {
716
767
  retry_after_ms: number
717
768
  }[]
718
769
  }
770
+ release_section_locks: {
771
+ Args: { p_section_ids: string[]; p_site_id: string }
772
+ Returns: undefined
773
+ }
719
774
  revoke_site_user: {
720
775
  Args: { p_site_id: string; p_user_id: string }
721
776
  Returns: undefined