@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.
- package/dist/{chunk-Q3T23HVW.js → chunk-2DDWWTRX.js} +5 -5
- package/dist/chunk-2G2S36SY.js +13 -0
- package/dist/{chunk-S55OZ7F2.js → chunk-3LNKOLZP.js} +4 -0
- package/dist/{chunk-SWIHUIFT.js → chunk-4SIWBWKZ.js} +12 -2
- package/dist/{chunk-SWEVWC5K.js → chunk-BAQYZSFF.js} +22 -22
- package/dist/{chunk-IFHRHV7S.js → chunk-EMGLZQCM.js} +1 -1
- package/dist/{chunk-NKVZGPR6.js → chunk-K4JD6FD3.js} +1 -1
- package/dist/{chunk-SFKYUKVN.js → chunk-MK4POMLZ.js} +1 -1
- package/dist/{chunk-7T2WQSWF.js → chunk-NS3UTJ3E.js} +11 -11
- package/dist/chunk-REM2NH7K.js +44 -0
- package/dist/{chunk-T7MP7ANP.js → chunk-UA6OCJJK.js} +2 -2
- package/dist/{chunk-ZRWGV5O6.js → chunk-Z5IYWUW3.js} +3 -3
- package/dist/collab/provider-registry.d.ts +4 -0
- package/dist/collab/provider-registry.d.ts.map +1 -0
- package/dist/collab/provider-registry.js +9 -0
- package/dist/collab/rebase.d.ts +37 -0
- package/dist/collab/rebase.d.ts.map +1 -0
- package/dist/collab/rebase.js +64 -0
- package/dist/collab/recovery.d.ts +89 -0
- package/dist/collab/recovery.d.ts.map +1 -0
- package/dist/collab/types.d.ts +93 -0
- package/dist/collab/types.d.ts.map +1 -0
- package/dist/collab/types.js +19 -0
- package/dist/components/editor/AudienceIndicator.d.ts +3 -1
- package/dist/components/editor/AudienceIndicator.d.ts.map +1 -1
- package/dist/components/editor/ConflictDialog.d.ts +14 -0
- package/dist/components/editor/ConflictDialog.d.ts.map +1 -0
- package/dist/components/editor/IndicatorPill.d.ts +4 -1
- package/dist/components/editor/IndicatorPill.d.ts.map +1 -1
- package/dist/components/editor/LockBadge.d.ts +7 -0
- package/dist/components/editor/LockBadge.d.ts.map +1 -0
- package/dist/components/editor/PresenceAvatars.d.ts +9 -0
- package/dist/components/editor/PresenceAvatars.d.ts.map +1 -0
- package/dist/components/editor/SectionWrapper.d.ts +1 -1
- package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
- package/dist/components/editor/StatusIndicator.d.ts +3 -1
- package/dist/components/editor/StatusIndicator.d.ts.map +1 -1
- package/dist/components/editor/index.js +95 -47
- package/dist/components/primitives/index.js +5 -5
- package/dist/components/sections/brand-guide.js +5 -5
- package/dist/components/sections/register-schemas.js +11 -11
- package/dist/components/sections/register.js +11 -11
- package/dist/components/shell/EditorShell.d.ts +3 -1
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/MediaBreadcrumb.d.ts +10 -0
- package/dist/components/shell/MediaBreadcrumb.d.ts.map +1 -0
- package/dist/components/shell/MediaFolderTile.d.ts +15 -0
- package/dist/components/shell/MediaFolderTile.d.ts.map +1 -0
- package/dist/components/shell/MediaFolderTree.d.ts +20 -0
- package/dist/components/shell/MediaFolderTree.d.ts.map +1 -0
- package/dist/components/shell/MediaLibraryModal.d.ts +15 -3
- package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
- package/dist/components/shell/MoveToFolderDialog.d.ts +16 -0
- package/dist/components/shell/MoveToFolderDialog.d.ts.map +1 -0
- package/dist/hooks/useCollabSession.d.ts +23 -0
- package/dist/hooks/useCollabSession.d.ts.map +1 -0
- package/dist/hooks/useEditorPersistence.d.ts +21 -1
- package/dist/hooks/useEditorPersistence.d.ts.map +1 -1
- package/dist/hooks/useEditorPublish.d.ts +39 -2
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/hooks/useMediaFolderDnd.d.ts +27 -0
- package/dist/hooks/useMediaFolderDnd.d.ts.map +1 -0
- package/dist/hooks/useMediaPipeline.d.ts +14 -2
- package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -25
- package/dist/lib/dexie.d.ts +12 -0
- package/dist/lib/dexie.d.ts.map +1 -1
- package/dist/lib/dexie.js +27 -0
- package/dist/lib/index.js +12 -12
- package/dist/lib/media-dnd.d.ts +26 -0
- package/dist/lib/media-dnd.d.ts.map +1 -0
- package/dist/lib/media-folders.d.ts +59 -0
- package/dist/lib/media-folders.d.ts.map +1 -0
- package/dist/lib/registry.d.ts +10 -0
- package/dist/lib/registry.d.ts.map +1 -1
- package/dist/media/folder-drop.d.ts +22 -0
- package/dist/media/folder-drop.d.ts.map +1 -0
- package/dist/media/index.js +6 -2
- package/dist/media/types.d.ts +1 -1
- package/dist/media/types.d.ts.map +1 -1
- package/dist/media/utils.d.ts +7 -0
- package/dist/media/utils.d.ts.map +1 -1
- package/dist/schemas/index.js +5 -3
- package/dist/schemas/media.d.ts +11 -0
- package/dist/schemas/media.d.ts.map +1 -1
- package/dist/types/database.d.ts +70 -0
- package/dist/types/database.d.ts.map +1 -1
- package/package.json +13 -1
- package/src/collab/provider-registry.ts +14 -0
- package/src/collab/rebase.ts +140 -0
- package/src/collab/recovery.ts +149 -0
- package/src/collab/types.ts +96 -0
- package/src/components/editor/AudienceIndicator.tsx +4 -1
- package/src/components/editor/ConflictDialog.tsx +61 -0
- package/src/components/editor/IndicatorPill.tsx +12 -6
- package/src/components/editor/LockBadge.tsx +31 -0
- package/src/components/editor/PresenceAvatars.tsx +53 -0
- package/src/components/editor/SectionWrapper.tsx +60 -29
- package/src/components/editor/StatusIndicator.tsx +4 -0
- package/src/components/shell/EditorShell.tsx +581 -22
- package/src/components/shell/MediaBreadcrumb.tsx +77 -0
- package/src/components/shell/MediaFolderTile.tsx +141 -0
- package/src/components/shell/MediaFolderTree.tsx +127 -0
- package/src/components/shell/MediaLibraryModal.tsx +611 -229
- package/src/components/shell/MoveToFolderDialog.tsx +78 -0
- package/src/hooks/useCollabSession.ts +220 -0
- package/src/hooks/useEditorPersistence.ts +104 -1
- package/src/hooks/useEditorPublish.ts +216 -71
- package/src/hooks/useMediaFolderDnd.ts +129 -0
- package/src/hooks/useMediaPipeline.ts +162 -6
- package/src/index.ts +2 -0
- package/src/lib/dexie.ts +44 -0
- package/src/lib/media-dnd.ts +62 -0
- package/src/lib/media-folders.ts +232 -0
- package/src/lib/registry.ts +8 -0
- package/src/media/folder-drop.ts +62 -0
- package/src/media/types.ts +2 -0
- package/src/media/utils.ts +10 -0
- package/src/schemas/media.ts +13 -0
- 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
|
+
}
|
package/src/media/types.ts
CHANGED
|
@@ -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";
|
package/src/media/utils.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/schemas/media.ts
CHANGED
|
@@ -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>;
|
package/src/types/database.ts
CHANGED
|
@@ -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
|