@drawnagency/primitives 0.1.114 → 0.1.115
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-OAGJBCSH.js → chunk-4WQ36ZP7.js} +19 -3
- package/dist/{chunk-PTY2RG2K.js → chunk-67P34WNW.js} +75 -130
- package/dist/chunk-APIJPTFG.js +64 -0
- package/dist/chunk-CDBURDPX.js +90 -0
- package/dist/chunk-DFKTF3CH.js +0 -0
- package/dist/chunk-DJABADOO.js +131 -0
- package/dist/{chunk-IP2SEBAJ.js → chunk-DJOGPSRU.js} +31 -72
- package/dist/{chunk-N3G7DOU3.js → chunk-FHTKSELX.js} +6 -6
- package/dist/chunk-GNF6KFQ7.js +18 -0
- package/dist/{chunk-QHMKIODQ.js → chunk-HHNPSKPT.js} +2 -89
- package/dist/{chunk-PZPGJZRG.js → chunk-JXATNKLV.js} +5 -5
- package/dist/{chunk-PLANHXS7.js → chunk-NX7TFHA4.js} +9 -9
- package/dist/{chunk-PNSCDBU7.js → chunk-O37AV27P.js} +6 -49
- package/dist/{chunk-5KSAS4VY.js → chunk-RIITGTV3.js} +1 -1
- package/dist/chunk-S7QRHUWO.js +8 -0
- package/dist/{chunk-7SAAJVDE.js → chunk-T26HAKW3.js} +37 -35
- package/dist/chunk-VY3IFGE3.js +70 -0
- package/dist/{chunk-VUYCRBJK.js → chunk-WYOFQHKI.js} +6 -4
- package/dist/{chunk-Y5QECJX7.js → chunk-YV3YSZEB.js} +5 -5
- package/dist/collab/rebase.d.ts.map +1 -1
- package/dist/collab/rebase.js +15 -15
- package/dist/components/editor/index.js +13 -13
- package/dist/components/primitives/index.js +16 -11
- package/dist/components/sections/brand-guide.js +13 -8
- package/dist/components/sections/register-schemas.js +20 -15
- package/dist/components/sections/register.js +20 -15
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/deck-builder/index.js +9 -9
- package/dist/hooks/useEditorPublish.d.ts +4 -1
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
- package/dist/index.js +85 -56
- package/dist/lib/dexie.d.ts +7 -1
- package/dist/lib/dexie.d.ts.map +1 -1
- package/dist/lib/dexie.js +3 -2
- package/dist/lib/image-variant-plan.d.ts +42 -0
- package/dist/lib/image-variant-plan.d.ts.map +1 -0
- package/dist/lib/image-variant-plan.js +53 -0
- package/dist/lib/index-ops.d.ts.map +1 -1
- package/dist/lib/index-ops.js +20 -16
- package/dist/lib/index.d.ts +2 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +43 -22
- package/dist/lib/loader.d.ts +16 -17
- package/dist/lib/loader.d.ts.map +1 -1
- package/dist/lib/media-ops.d.ts +73 -0
- package/dist/lib/media-ops.d.ts.map +1 -0
- package/dist/lib/media-ops.js +60 -0
- package/dist/lib/nav-tree.d.ts +6 -0
- package/dist/lib/nav-tree.d.ts.map +1 -1
- package/dist/lib/pages.d.ts +2 -1
- package/dist/lib/pages.d.ts.map +1 -1
- package/dist/lib/sidecar-diagnostics.d.ts +20 -0
- package/dist/lib/sidecar-diagnostics.d.ts.map +1 -0
- package/dist/lib/sidecars.d.ts +118 -0
- package/dist/lib/sidecars.d.ts.map +1 -0
- package/dist/lib/sidecars.js +23 -0
- package/dist/lib/slug.d.ts +2 -0
- package/dist/lib/slug.d.ts.map +1 -0
- package/dist/lib/storage-tier.d.ts +17 -0
- package/dist/lib/storage-tier.d.ts.map +1 -0
- package/dist/lib/storage-tier.js +29 -0
- package/dist/lib/tags.d.ts +6 -0
- package/dist/lib/tags.d.ts.map +1 -1
- package/dist/media/index.js +15 -15
- package/dist/media/queue.d.ts +2 -0
- package/dist/media/queue.d.ts.map +1 -1
- package/dist/media/worker.d.ts +1 -35
- package/dist/media/worker.d.ts.map +1 -1
- package/dist/schemas/index.js +18 -12
- package/dist/schemas/media.d.ts +4 -0
- package/dist/schemas/media.d.ts.map +1 -1
- package/dist/storage/index.js +2 -1
- package/dist/storage/memory.d.ts +21 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +129 -0
- package/dist/storage/types.d.ts +9 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/package.json +21 -1
- package/src/collab/rebase.ts +12 -16
- package/src/components/shell/EditorShell.tsx +7 -4
- package/src/hooks/useEditorPublish.ts +9 -4
- package/src/hooks/useMediaPipeline.ts +29 -23
- package/src/lib/dexie.ts +9 -1
- package/src/lib/image-variant-plan.ts +92 -0
- package/src/lib/index-ops.ts +9 -18
- package/src/lib/index.ts +15 -0
- package/src/lib/loader.ts +18 -77
- package/src/lib/media-ops.ts +112 -0
- package/src/lib/nav-tree.ts +33 -4
- package/src/lib/pages.ts +2 -3
- package/src/lib/sidecar-diagnostics.ts +29 -0
- package/src/lib/sidecars.ts +221 -0
- package/src/lib/slug.ts +6 -0
- package/src/lib/storage-tier.ts +38 -0
- package/src/lib/tags.ts +20 -3
- package/src/media/queue.ts +3 -0
- package/src/media/utils.ts +1 -1
- package/src/media/worker.ts +17 -8
- package/src/schemas/media.ts +7 -2
- package/src/storage/memory.ts +178 -0
- package/src/storage/types.ts +15 -2
- /package/dist/{chunk-45FA7Z55.js → chunk-UUUSJDIG.js} +0 -0
package/dist/media/worker.d.ts
CHANGED
|
@@ -1,36 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
type: "process";
|
|
3
|
-
id: string;
|
|
4
|
-
buffer: ArrayBuffer;
|
|
5
|
-
originalName: string;
|
|
6
|
-
mimeType: string;
|
|
7
|
-
hash: string;
|
|
8
|
-
kind: "image" | "animated" | "video";
|
|
9
|
-
sizes: number[];
|
|
10
|
-
quality: number;
|
|
11
|
-
}
|
|
12
|
-
interface ProgressMessage {
|
|
13
|
-
type: "progress";
|
|
14
|
-
id: string;
|
|
15
|
-
percent: number;
|
|
16
|
-
}
|
|
17
|
-
interface CompleteMessage {
|
|
18
|
-
type: "complete";
|
|
19
|
-
id: string;
|
|
20
|
-
variants: {
|
|
21
|
-
width: number;
|
|
22
|
-
height: number;
|
|
23
|
-
size: number;
|
|
24
|
-
blob: Blob;
|
|
25
|
-
}[];
|
|
26
|
-
primaryBlob: Blob;
|
|
27
|
-
posterBlob?: Blob;
|
|
28
|
-
width: number;
|
|
29
|
-
height: number;
|
|
30
|
-
}
|
|
31
|
-
interface ErrorMessage {
|
|
32
|
-
type: "error";
|
|
33
|
-
id: string;
|
|
34
|
-
message: string;
|
|
35
|
-
}
|
|
1
|
+
export {};
|
|
36
2
|
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/media/worker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/media/worker.ts"],"names":[],"mappings":""}
|
package/dist/schemas/index.js
CHANGED
|
@@ -6,21 +6,11 @@ import {
|
|
|
6
6
|
getSectionSchema,
|
|
7
7
|
getSectionSchemaForType
|
|
8
8
|
} from "../chunk-DMCGN5AS.js";
|
|
9
|
-
import {
|
|
10
|
-
NavEntrySchema,
|
|
11
|
-
NavFileSchema,
|
|
12
|
-
TagEntrySchema,
|
|
13
|
-
TagsFileSchema
|
|
14
|
-
} from "../chunk-4Y2JQQZL.js";
|
|
15
9
|
import {
|
|
16
10
|
ChatbotConfigSchema,
|
|
17
11
|
ChatbotTaskSchema,
|
|
18
12
|
DeckBuilderConfigSchema,
|
|
19
|
-
ImageManifestSchema,
|
|
20
13
|
IndexSchema,
|
|
21
|
-
MediaConfigSchema,
|
|
22
|
-
MediaFolderSchema,
|
|
23
|
-
MediaItemSchema,
|
|
24
14
|
PageSchema,
|
|
25
15
|
PageStatusSchema,
|
|
26
16
|
RESERVED_SLUGS,
|
|
@@ -28,10 +18,24 @@ import {
|
|
|
28
18
|
SectionMetaSchema,
|
|
29
19
|
SiteConfigSchema,
|
|
30
20
|
StatusSchema,
|
|
31
|
-
VariantSchema,
|
|
32
21
|
normalizeSiteIndex
|
|
33
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-67P34WNW.js";
|
|
34
23
|
import "../chunk-4TZJZU2T.js";
|
|
24
|
+
import {
|
|
25
|
+
NavEntrySchema,
|
|
26
|
+
NavFileSchema,
|
|
27
|
+
TagEntrySchema,
|
|
28
|
+
TagsFileSchema
|
|
29
|
+
} from "../chunk-4Y2JQQZL.js";
|
|
30
|
+
import {
|
|
31
|
+
ImageManifestSchema,
|
|
32
|
+
MEDIA_BUCKET_MAX_BYTES,
|
|
33
|
+
MEDIA_GIT_MAX_BYTES,
|
|
34
|
+
MediaConfigSchema,
|
|
35
|
+
MediaFolderSchema,
|
|
36
|
+
MediaItemSchema,
|
|
37
|
+
VariantSchema
|
|
38
|
+
} from "../chunk-APIJPTFG.js";
|
|
35
39
|
import "../chunk-FTPETWXE.js";
|
|
36
40
|
import {
|
|
37
41
|
AudienceSchema,
|
|
@@ -100,6 +104,8 @@ export {
|
|
|
100
104
|
IndexSchema,
|
|
101
105
|
LinkTargetSchema,
|
|
102
106
|
LinkValueSchema,
|
|
107
|
+
MEDIA_BUCKET_MAX_BYTES,
|
|
108
|
+
MEDIA_GIT_MAX_BYTES,
|
|
103
109
|
MediaConfigSchema,
|
|
104
110
|
MediaFolderSchema,
|
|
105
111
|
MediaGridOptionsSchema,
|
package/dist/schemas/media.d.ts
CHANGED
|
@@ -76,6 +76,10 @@ export declare const ImageManifestSchema: z.ZodObject<{
|
|
|
76
76
|
}, z.core.$strip>>>;
|
|
77
77
|
}, z.core.$strip>;
|
|
78
78
|
export type MediaManifest = z.infer<typeof ImageManifestSchema>;
|
|
79
|
+
/** Default git-tier cap for media (bytes); a site may override it as `media.maxFileSize`. */
|
|
80
|
+
export declare const MEDIA_GIT_MAX_BYTES = 5242880;
|
|
81
|
+
/** Default bucket-tier cap for media (bytes); a site may override it as `media.maxAssetSize`. */
|
|
82
|
+
export declare const MEDIA_BUCKET_MAX_BYTES = 209715200;
|
|
79
83
|
export declare const MediaConfigSchema: z.ZodObject<{
|
|
80
84
|
sizes: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
|
|
81
85
|
maxFileSize: z.ZodDefault<z.ZodNumber>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/schemas/media.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa;;;;iBAIxB,CAAC;AAEH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAIpD,eAAO,MAAM,iBAAiB;;;iBAG5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2B1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAG9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,iBAAiB;;;;;iBAO5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/schemas/media.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa;;;;iBAIxB,CAAC;AAEH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAIpD,eAAO,MAAM,iBAAiB;;;iBAG5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2B1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAG9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,6FAA6F;AAC7F,eAAO,MAAM,mBAAmB,UAAY,CAAC;AAC7C,iGAAiG;AACjG,eAAO,MAAM,sBAAsB,YAAc,CAAC;AAElD,eAAO,MAAM,iBAAiB;;;;;iBAO5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
|
package/dist/storage/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../chunk-DFKTF3CH.js";
|
|
1
2
|
import {
|
|
2
3
|
ASSET_CACHE_CONTROL,
|
|
3
4
|
DOCUMENT_CACHE_CONTROL,
|
|
@@ -7,7 +8,7 @@ import {
|
|
|
7
8
|
NothingToPublishError,
|
|
8
9
|
StorageConflictError,
|
|
9
10
|
withIdempotencyTrailer
|
|
10
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-UUUSJDIG.js";
|
|
11
12
|
import "../chunk-JSBRDJBE.js";
|
|
12
13
|
export {
|
|
13
14
|
ASSET_CACHE_CONTROL,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type StorageProvider } from "./types";
|
|
2
|
+
type Files = Record<string, string | Uint8Array | null>;
|
|
3
|
+
export type MemoryStorage = StorageProvider & {
|
|
4
|
+
/**
|
|
5
|
+
* Commit straight to a branch as another writer would — a concurrent editor
|
|
6
|
+
* save on the draft (created from main if absent), or a merge onto main.
|
|
7
|
+
* Returns the new commit's sha.
|
|
8
|
+
*/
|
|
9
|
+
commit(branch: "main" | "draft", files: Files, message?: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Run `fn` once at the start of the next `writeFiles`, before its
|
|
12
|
+
* `baseVersion` check: a writer landing between a caller's read and its write.
|
|
13
|
+
*/
|
|
14
|
+
beforeNextWrite(fn: () => void): void;
|
|
15
|
+
};
|
|
16
|
+
export declare function createMemoryStorage(seed?: {
|
|
17
|
+
main?: Files;
|
|
18
|
+
draft?: Files;
|
|
19
|
+
}): MemoryStorage;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/storage/memory.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,SAAS,CAAC;AAWjB,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC,CAAC;AAMxD,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG;IAC5C;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzE;;;OAGG;IACH,eAAe,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAO,GAAG,aAAa,CAoI7F"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IDEMPOTENCY_TRAILER,
|
|
3
|
+
NothingToPublishError,
|
|
4
|
+
StorageConflictError
|
|
5
|
+
} from "../chunk-UUUSJDIG.js";
|
|
6
|
+
import "../chunk-JSBRDJBE.js";
|
|
7
|
+
|
|
8
|
+
// src/storage/memory.ts
|
|
9
|
+
var PUBLISHED_PREFIXES = ["src/content/", "assets/"];
|
|
10
|
+
function createMemoryStorage(seed = {}) {
|
|
11
|
+
const commits = /* @__PURE__ */ new Map();
|
|
12
|
+
let counter = 0;
|
|
13
|
+
let mainHead;
|
|
14
|
+
let draftHead = null;
|
|
15
|
+
let pendingHook = null;
|
|
16
|
+
function newCommit(tree, parent, message) {
|
|
17
|
+
counter += 1;
|
|
18
|
+
const sha = counter.toString(16).padStart(40, "0");
|
|
19
|
+
commits.set(sha, { tree, parent, message });
|
|
20
|
+
return sha;
|
|
21
|
+
}
|
|
22
|
+
function applyFiles(base, files) {
|
|
23
|
+
const tree = new Map(base);
|
|
24
|
+
for (const [path, content] of Object.entries(files)) {
|
|
25
|
+
if (content === null) tree.delete(path);
|
|
26
|
+
else tree.set(path, toBytes(content));
|
|
27
|
+
}
|
|
28
|
+
return tree;
|
|
29
|
+
}
|
|
30
|
+
function commitTo(branch, files, message = "commit") {
|
|
31
|
+
if (branch === "main") {
|
|
32
|
+
mainHead = newCommit(applyFiles(commits.get(mainHead).tree, files), mainHead, message);
|
|
33
|
+
return mainHead;
|
|
34
|
+
}
|
|
35
|
+
const parent = draftHead ?? mainHead;
|
|
36
|
+
draftHead = newCommit(applyFiles(commits.get(parent).tree, files), parent, message);
|
|
37
|
+
return draftHead;
|
|
38
|
+
}
|
|
39
|
+
function treeFor(options) {
|
|
40
|
+
if (options?.at !== void 0) return commits.get(options.at)?.tree ?? null;
|
|
41
|
+
if (options?.draft) return draftHead === null ? null : commits.get(draftHead).tree;
|
|
42
|
+
return commits.get(mainHead).tree;
|
|
43
|
+
}
|
|
44
|
+
function checkBase(options) {
|
|
45
|
+
if (options && "baseVersion" in options) {
|
|
46
|
+
const expected = options.baseVersion ?? null;
|
|
47
|
+
if (expected !== draftHead) throw new StorageConflictError(draftHead);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
mainHead = newCommit(applyFiles(/* @__PURE__ */ new Map(), seed.main ?? {}), null, "initial");
|
|
51
|
+
if (seed.draft) commitTo("draft", seed.draft, "seed draft");
|
|
52
|
+
return {
|
|
53
|
+
commit: commitTo,
|
|
54
|
+
beforeNextWrite(fn) {
|
|
55
|
+
pendingHook = fn;
|
|
56
|
+
},
|
|
57
|
+
async writeFiles(files, message, options) {
|
|
58
|
+
const hook = pendingHook;
|
|
59
|
+
pendingHook = null;
|
|
60
|
+
hook?.();
|
|
61
|
+
checkBase(options);
|
|
62
|
+
if (files.length === 0) return { version: draftHead ?? mainHead };
|
|
63
|
+
const parentTree = commits.get(draftHead ?? mainHead).tree;
|
|
64
|
+
const changes = {};
|
|
65
|
+
for (const file of files) {
|
|
66
|
+
if (file.content === null && !parentTree.has(file.path)) {
|
|
67
|
+
throw new Error(`Cannot delete ${file.path}: it is not on the branch`);
|
|
68
|
+
}
|
|
69
|
+
changes[file.path] = file.content === null || file.content instanceof Uint8Array || file.encoding !== "base64" ? file.content : fromBase64(file.content);
|
|
70
|
+
}
|
|
71
|
+
return { version: commitTo("draft", changes, message) };
|
|
72
|
+
},
|
|
73
|
+
async readFile(path, options) {
|
|
74
|
+
return treeFor(options)?.get(path) ?? null;
|
|
75
|
+
},
|
|
76
|
+
async listDirectory(path, options) {
|
|
77
|
+
const tree = treeFor(options);
|
|
78
|
+
if (!tree) return [];
|
|
79
|
+
const prefix = path.endsWith("/") ? path : `${path}/`;
|
|
80
|
+
const names = [];
|
|
81
|
+
for (const key of tree.keys()) {
|
|
82
|
+
if (!key.startsWith(prefix)) continue;
|
|
83
|
+
const rest = key.slice(prefix.length);
|
|
84
|
+
if (!rest.includes("/")) names.push(rest);
|
|
85
|
+
}
|
|
86
|
+
return names;
|
|
87
|
+
},
|
|
88
|
+
async getVersion() {
|
|
89
|
+
return mainHead;
|
|
90
|
+
},
|
|
91
|
+
async getDraftVersion() {
|
|
92
|
+
return draftHead;
|
|
93
|
+
},
|
|
94
|
+
async promoteDraft() {
|
|
95
|
+
if (draftHead === null) throw new NothingToPublishError();
|
|
96
|
+
const draft = commits.get(draftHead).tree;
|
|
97
|
+
const main = commits.get(mainHead).tree;
|
|
98
|
+
const published = (p) => PUBLISHED_PREFIXES.some((prefix) => p.startsWith(prefix));
|
|
99
|
+
if (![...draft.keys(), ...main.keys()].some(published)) {
|
|
100
|
+
throw new Error("No editor-managed content found on the saved branch \u2014 refusing to publish.");
|
|
101
|
+
}
|
|
102
|
+
const tree = new Map([...main].filter(([p]) => !published(p)));
|
|
103
|
+
for (const [p, bytes] of draft) if (published(p)) tree.set(p, bytes);
|
|
104
|
+
mainHead = newCommit(tree, mainHead, "Publish content via Brand Portal");
|
|
105
|
+
draftHead = null;
|
|
106
|
+
return { version: mainHead };
|
|
107
|
+
},
|
|
108
|
+
async findCommitByIdempotencyKey(key, options) {
|
|
109
|
+
const limit = Math.min(Math.max(options?.limit ?? 30, 1), 100);
|
|
110
|
+
const trailer = `${IDEMPOTENCY_TRAILER}: ${key}`;
|
|
111
|
+
let sha = draftHead;
|
|
112
|
+
for (let seen = 0; sha !== null && seen < limit; seen += 1) {
|
|
113
|
+
const commit = commits.get(sha);
|
|
114
|
+
if (commit.message.split("\n").some((line) => line.trim() === trailer)) return sha;
|
|
115
|
+
sha = commit.parent;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function toBytes(content) {
|
|
122
|
+
return typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
123
|
+
}
|
|
124
|
+
function fromBase64(content) {
|
|
125
|
+
return Uint8Array.from(atob(content), (c) => c.charCodeAt(0));
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
createMemoryStorage
|
|
129
|
+
};
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -25,13 +25,22 @@ export interface StorageProvider {
|
|
|
25
25
|
}>;
|
|
26
26
|
readFile(path: string, options?: {
|
|
27
27
|
draft?: boolean;
|
|
28
|
+
at?: string;
|
|
28
29
|
}): Promise<Uint8Array | null>;
|
|
29
30
|
promoteDraft(): Promise<{
|
|
30
31
|
version: string;
|
|
31
32
|
}>;
|
|
33
|
+
/** The published branch's head. Never a valid `baseVersion`. */
|
|
32
34
|
getVersion(): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* The draft branch's head, or null when no draft exists — exactly the value
|
|
37
|
+
* `writeFiles` accepts as `baseVersion`.
|
|
38
|
+
*/
|
|
39
|
+
getDraftVersion(): Promise<string | null>;
|
|
40
|
+
/** File basenames directly under `path` (no subdirectories); `[]` when absent. Same options as `readFile`. */
|
|
33
41
|
listDirectory(path: string, options?: {
|
|
34
42
|
draft?: boolean;
|
|
43
|
+
at?: string;
|
|
35
44
|
}): Promise<string[]>;
|
|
36
45
|
/**
|
|
37
46
|
* Find a recent commit on the draft branch whose message carries the given
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,cAAc,EAAE,MAAM,GAAG,IAAI;gBAA7B,cAAc,EAAE,MAAM,GAAG,IAAI;CAI1D;AAED;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;;CAK/C;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CACR,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,EAIf,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drawnagency/primitives",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.115",
|
|
4
4
|
"description": "Browser-safe foundation of the Brand Portal framework: Zod schemas, section components, editor shell, hooks, pure libs, collab contract, document engine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -81,6 +81,22 @@
|
|
|
81
81
|
"types": "./dist/lib/registry.d.ts",
|
|
82
82
|
"default": "./dist/lib/registry.js"
|
|
83
83
|
},
|
|
84
|
+
"./lib/storage-tier": {
|
|
85
|
+
"types": "./dist/lib/storage-tier.d.ts",
|
|
86
|
+
"default": "./dist/lib/storage-tier.js"
|
|
87
|
+
},
|
|
88
|
+
"./lib/image-variant-plan": {
|
|
89
|
+
"types": "./dist/lib/image-variant-plan.d.ts",
|
|
90
|
+
"default": "./dist/lib/image-variant-plan.js"
|
|
91
|
+
},
|
|
92
|
+
"./lib/media-ops": {
|
|
93
|
+
"types": "./dist/lib/media-ops.d.ts",
|
|
94
|
+
"default": "./dist/lib/media-ops.js"
|
|
95
|
+
},
|
|
96
|
+
"./lib/sidecars": {
|
|
97
|
+
"types": "./dist/lib/sidecars.d.ts",
|
|
98
|
+
"default": "./dist/lib/sidecars.js"
|
|
99
|
+
},
|
|
84
100
|
"./lib/index-ops": {
|
|
85
101
|
"types": "./dist/lib/index-ops.d.ts",
|
|
86
102
|
"default": "./dist/lib/index-ops.js"
|
|
@@ -165,6 +181,10 @@
|
|
|
165
181
|
"types": "./dist/storage/index.d.ts",
|
|
166
182
|
"default": "./dist/storage/index.js"
|
|
167
183
|
},
|
|
184
|
+
"./storage/memory": {
|
|
185
|
+
"types": "./dist/storage/memory.d.ts",
|
|
186
|
+
"default": "./dist/storage/memory.js"
|
|
187
|
+
},
|
|
168
188
|
"./types/database": {
|
|
169
189
|
"types": "./dist/types/database.d.ts",
|
|
170
190
|
"default": "./dist/types/database.js"
|
package/src/collab/rebase.ts
CHANGED
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
import type { LoadedSection } from "../lib/loader";
|
|
5
5
|
import type { SiteIndex } from "../schemas/site-config";
|
|
6
6
|
import { IndexSchema } from "../schemas/site-config";
|
|
7
|
-
import {
|
|
8
|
-
import { TagsFileSchema } from "../schemas/tags";
|
|
9
|
-
import { mergeTags } from "../lib/tags";
|
|
7
|
+
import { liftSidecars, NAV, TAGS } from "../lib/sidecars";
|
|
10
8
|
|
|
11
9
|
export interface RebaseArgs {
|
|
12
10
|
localSections: LoadedSection[];
|
|
@@ -110,19 +108,17 @@ export function reconcileRemoteIndex(remoteIndex: SiteIndex, localIndex: SiteInd
|
|
|
110
108
|
* intact rather than flattening it. The `tags` sidecar is lifted the same way,
|
|
111
109
|
* for the same reason. */
|
|
112
110
|
export function applyRemoteIndexUpdate(rawRemoteIndex: unknown, localIndex: SiteIndex): SiteIndex | null {
|
|
113
|
-
const
|
|
114
|
-
|
|
111
|
+
const lifted = liftSidecars(rawRemoteIndex);
|
|
112
|
+
// safeParse, not parse: this arrives off the network, and a throw inside a
|
|
113
|
+
// collab callback would take down the session rather than one bad message.
|
|
115
114
|
const parsed = IndexSchema.safeParse(rawRemoteIndex);
|
|
116
115
|
if (!parsed.success) return null;
|
|
117
116
|
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
const nav =
|
|
122
|
-
|
|
123
|
-
// here — siteTags reconciles on read, and the next tags write reconciles too.
|
|
124
|
-
const parsedTags = rawTags === undefined ? null : TagsFileSchema.safeParse({ tags: rawTags });
|
|
125
|
-
const tags = parsedTags?.success ? parsedTags.data.tags : localIndex.tags;
|
|
117
|
+
// A peer's Degraded read is never adopted: installing a lossy tree would arm
|
|
118
|
+
// our next save to write it over the file. Tags' section ids are not pruned
|
|
119
|
+
// to our index here — siteTags reconciles on read, and tags writes reconcile.
|
|
120
|
+
const nav = lifted.nav && !lifted.nav.degraded ? lifted.nav.entries : localIndex.nav;
|
|
121
|
+
const tags = lifted.tags && !lifted.tags.degraded ? lifted.tags.entries : localIndex.tags;
|
|
126
122
|
|
|
127
123
|
const reconciled = reconcileRemoteIndex(parsed.data, localIndex);
|
|
128
124
|
return {
|
|
@@ -237,16 +233,16 @@ export function rebaseContent(args: RebaseArgs): RebaseResult {
|
|
|
237
233
|
// conflict dialog, so persisted-beats-unsaved is the safe rule. A remote with
|
|
238
234
|
// no nav (an older server that doesn't attach the sidecar) leaves ours intact
|
|
239
235
|
// rather than flattening it; readers repair any drift via reconcileNav.
|
|
240
|
-
const nav = args.
|
|
236
|
+
const nav = NAV.rebase({ base: args.baselineIndex?.nav, local: args.localIndex.nav, remote: args.remoteIndex.nav });
|
|
241
237
|
if (nav !== undefined) index = { ...index, nav };
|
|
242
238
|
|
|
243
|
-
// Tags: three-way, not nav's "persisted wins" — see mergeTags for why and for
|
|
239
|
+
// Tags: three-way, not nav's "persisted wins" — see TAGS.rebase (mergeTags) for why and for
|
|
244
240
|
// the exact rules. Only the dirty branch merges: the clean branch above took
|
|
245
241
|
// the remote's tags as-is, including their absence from an older server,
|
|
246
242
|
// which leaves tags unloaded (so no write touches tags.json) rather than
|
|
247
243
|
// reviving a local copy nothing local edited.
|
|
248
244
|
if (args.localIndexDirty) {
|
|
249
|
-
const tags =
|
|
245
|
+
const tags = TAGS.rebase({ base: args.baselineIndex?.tags, local: args.localIndex.tags, remote: args.remoteIndex.tags });
|
|
250
246
|
// undefined only when neither local nor remote carries tags, in which case
|
|
251
247
|
// the localIndex-derived index above carries none either.
|
|
252
248
|
if (tags !== undefined) index = { ...index, tags };
|
|
@@ -689,6 +689,7 @@ export default function EditorShell({
|
|
|
689
689
|
media: remote.mediaManifest ?? mediaManifestRef.current,
|
|
690
690
|
documents: remote.documentManifest ?? documentManifestRef.current ?? {},
|
|
691
691
|
},
|
|
692
|
+
remote.tagsDegraded,
|
|
692
693
|
{
|
|
693
694
|
savedBranchSha: remote.savedBranchSha,
|
|
694
695
|
changedSectionIds: remote.changedSectionIds,
|
|
@@ -775,6 +776,7 @@ export default function EditorShell({
|
|
|
775
776
|
}
|
|
776
777
|
setSections(result.sections);
|
|
777
778
|
setSiteIndex(result.index);
|
|
779
|
+
setTagsDegraded(remote.tagsDegraded);
|
|
778
780
|
setSavedSha(remote.savedBranchSha);
|
|
779
781
|
setChangedSectionIds(new Set(remote.changedSectionIds));
|
|
780
782
|
setMainIndex(remote.mainIndex);
|
|
@@ -800,6 +802,7 @@ export default function EditorShell({
|
|
|
800
802
|
media: remote.mediaManifest ?? mediaManifestRef.current,
|
|
801
803
|
documents: remote.documentManifest ?? documentManifestRef.current ?? {},
|
|
802
804
|
},
|
|
805
|
+
remote.tagsDegraded,
|
|
803
806
|
{
|
|
804
807
|
savedBranchSha: remote.savedBranchSha,
|
|
805
808
|
changedSectionIds: remote.changedSectionIds,
|
|
@@ -957,6 +960,7 @@ export default function EditorShell({
|
|
|
957
960
|
clearConfigDirty: persistence.clearConfigDirty,
|
|
958
961
|
siteIndexRef,
|
|
959
962
|
siteConfig,
|
|
963
|
+
tagsDegraded,
|
|
960
964
|
sections,
|
|
961
965
|
deletedSectionIds: Array.from(deletedSections),
|
|
962
966
|
onSaveWarnings: setSaveWarnings,
|
|
@@ -1069,6 +1073,7 @@ export default function EditorShell({
|
|
|
1069
1073
|
// onMediaPublished / onDocumentsPublished callbacks only refresh through a
|
|
1070
1074
|
// state effect and can still lag by a render at this point.
|
|
1071
1075
|
{ media: committedManifest, documents: committedDocumentManifest },
|
|
1076
|
+
tagsDegraded,
|
|
1072
1077
|
{
|
|
1073
1078
|
savedBranchSha: sha,
|
|
1074
1079
|
changedSectionIds: [...changed],
|
|
@@ -1318,12 +1323,10 @@ export default function EditorShell({
|
|
|
1318
1323
|
loadedIndex = data.index;
|
|
1319
1324
|
loadedConfig = SiteConfigSchema.parse(data.siteConfig);
|
|
1320
1325
|
if (data.mediaManifest) loadedManifest = data.mediaManifest;
|
|
1321
|
-
// A degraded tags read is cached as "not loaded", so a later cache-hit
|
|
1322
|
-
// load stays read-only for tags instead of trusting the partial list.
|
|
1323
|
-
const { tags: _partialTags, ...untaggedIndex } = data.index;
|
|
1324
1326
|
await cacheContent(
|
|
1325
|
-
data.sha, data.sections, data.
|
|
1327
|
+
data.sha, data.sections, data.index, data.siteConfig,
|
|
1326
1328
|
{ media: loadedManifest, documents: data.documentManifest ?? {} },
|
|
1329
|
+
data.tagsDegraded,
|
|
1327
1330
|
{
|
|
1328
1331
|
savedBranchSha: data.savedBranchSha,
|
|
1329
1332
|
changedSectionIds: data.changedSectionIds,
|
|
@@ -10,6 +10,7 @@ import { getDirtySectionRows, hasLocalChanges, discardSavedChanges, cacheContent
|
|
|
10
10
|
import { packUploads, packDocumentUploads, type MediaUploadPayload, type DocumentUploadPayload } from "../lib/save-chunks";
|
|
11
11
|
import type { DocumentManifest, DocumentManifestEntry } from "../schemas/documents";
|
|
12
12
|
import { assembleSavedManifest } from "../lib/media-folders";
|
|
13
|
+
import { imageVariantPath } from "../lib/image-variant-plan";
|
|
13
14
|
import { buildCommittedBaseline } from "../collab/recovery";
|
|
14
15
|
|
|
15
16
|
function blobToBase64(blob: Blob): Promise<string> {
|
|
@@ -63,6 +64,9 @@ interface PublishDeps {
|
|
|
63
64
|
clearConfigDirty: () => void;
|
|
64
65
|
siteIndexRef: React.RefObject<SiteIndex>;
|
|
65
66
|
siteConfig: SiteConfig | null;
|
|
67
|
+
/** This session's tags read was degraded; the cache re-primes record tags
|
|
68
|
+
* as not loaded (see cacheContent). */
|
|
69
|
+
tagsDegraded: boolean;
|
|
66
70
|
sections: LoadedSection[];
|
|
67
71
|
deletedSectionIds?: string[];
|
|
68
72
|
/** Receives the ids that were actually committed this save (so the caller can
|
|
@@ -148,6 +152,7 @@ export function useEditorPublish({
|
|
|
148
152
|
clearConfigDirty,
|
|
149
153
|
siteIndexRef,
|
|
150
154
|
siteConfig,
|
|
155
|
+
tagsDegraded,
|
|
151
156
|
sections,
|
|
152
157
|
deletedSectionIds,
|
|
153
158
|
onSuccess,
|
|
@@ -208,7 +213,7 @@ export function useEditorPublish({
|
|
|
208
213
|
const ext = mimeExt[item.mimeType] ?? "bin";
|
|
209
214
|
blobPayloads.push({ path: `assets/images/${item.folder}/original.${ext}`, base64 });
|
|
210
215
|
} else {
|
|
211
|
-
blobPayloads.push({ path:
|
|
216
|
+
blobPayloads.push({ path: imageVariantPath(item.folder, key), base64 });
|
|
212
217
|
}
|
|
213
218
|
}
|
|
214
219
|
|
|
@@ -815,7 +820,7 @@ export function useEditorPublish({
|
|
|
815
820
|
await cacheContent(saved.sha, baselineSections, saved.sentIndex, siteConfig!, {
|
|
816
821
|
media: saved.committedManifest,
|
|
817
822
|
documents: saved.committedDocumentManifest,
|
|
818
|
-
});
|
|
823
|
+
}, tagsDegraded);
|
|
819
824
|
finishSave(saved.uploadedItems, saved.savedSections.map((r) => r.sectionId));
|
|
820
825
|
if (saved.committedDocumentIds.length > 0) onDocumentsPublished?.(saved.committedDocumentIds);
|
|
821
826
|
if (saved.committedDocumentDeletions.length > 0) onDocumentsDeleted?.(saved.committedDocumentDeletions);
|
|
@@ -857,7 +862,7 @@ export function useEditorPublish({
|
|
|
857
862
|
await cacheContent(sha, baselineSections, sentIndex, siteConfig!, {
|
|
858
863
|
media: committedManifest!,
|
|
859
864
|
documents: committedDocumentManifest!,
|
|
860
|
-
});
|
|
865
|
+
}, tagsDegraded);
|
|
861
866
|
} catch (cleanupError) {
|
|
862
867
|
console.error("Publish succeeded but the cache re-key failed:", cleanupError);
|
|
863
868
|
onShasUpdated(null, sha);
|
|
@@ -877,7 +882,7 @@ export function useEditorPublish({
|
|
|
877
882
|
setPublishAction("idle");
|
|
878
883
|
setSaveProgress(null);
|
|
879
884
|
}
|
|
880
|
-
}, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, manifestDirty, clearManifestDirty, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback, onPublishComplete, recoverConflict, saveChunkBudget, getEditGeneration, pendingDocumentDeletions]);
|
|
885
|
+
}, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, manifestDirty, clearManifestDirty, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback, onPublishComplete, recoverConflict, saveChunkBudget, getEditGeneration, pendingDocumentDeletions, tagsDegraded]);
|
|
881
886
|
|
|
882
887
|
return { publishAction, publishFeedback, saveProgress, handleSave, handlePublish, handleSaveAndPublish };
|
|
883
888
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
|
2
|
-
import { hashFileBuffer
|
|
2
|
+
import { hashFileBuffer } from "../media/utils";
|
|
3
|
+
import { mediaIdentity, imageManifestEntry } from "../lib/image-variant-plan";
|
|
3
4
|
import { ProcessingQueue, type QueueItem, type QueueEvent } from "../media/queue";
|
|
4
5
|
import type { MediaManifest, MediaItem as LibraryMediaItem } from "../media/types";
|
|
5
6
|
import type { SiteConfig } from "../schemas/site-config";
|
|
@@ -235,7 +236,16 @@ export function useMediaPipeline({
|
|
|
235
236
|
if (event.type === "complete" && event.item.result) {
|
|
236
237
|
const result = event.item.result;
|
|
237
238
|
const kind = event.item.kind;
|
|
238
|
-
|
|
239
|
+
// A still image's manifest entry comes only from the plan the worker
|
|
240
|
+
// rendered. One without a plan (a stale cached worker after a deploy)
|
|
241
|
+
// is a failed upload, never a quiet entry with no variants.
|
|
242
|
+
if (kind === "image" && !result.plan) {
|
|
243
|
+
uploadTargetsRef.current.delete(event.item.hash);
|
|
244
|
+
hiddenUploadsRef.current.delete(event.item.hash);
|
|
245
|
+
uploadCallbacksRef.current.delete(event.item.hash);
|
|
246
|
+
onUploadErrorRef.current?.(`"${event.item.originalName}" couldn't be processed — reload the editor and try again.`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
239
249
|
|
|
240
250
|
const finalize = async (
|
|
241
251
|
localUrls: Record<string, string>,
|
|
@@ -245,23 +255,23 @@ export function useMediaPipeline({
|
|
|
245
255
|
) => {
|
|
246
256
|
if (destroyedRef.current) return;
|
|
247
257
|
const targetFolderId = uploadTargetsRef.current.get(event.item.hash) ?? null;
|
|
258
|
+
// A still image's entry comes from the plan the worker rendered;
|
|
259
|
+
// animated and video items have no variants, only the identity.
|
|
260
|
+
const base: LibraryMediaItem = result.plan
|
|
261
|
+
? imageManifestEntry(result.plan, result.variants, result.primaryBlob.size)
|
|
262
|
+
: {
|
|
263
|
+
...mediaIdentity(event.item.hash, event.item.originalName),
|
|
264
|
+
kind,
|
|
265
|
+
width,
|
|
266
|
+
height,
|
|
267
|
+
mimeType: event.item.mimeType,
|
|
268
|
+
size: result.primaryBlob.size,
|
|
269
|
+
variants: [],
|
|
270
|
+
alt: "",
|
|
271
|
+
storage: "github",
|
|
272
|
+
};
|
|
248
273
|
const item: LibraryMediaItem = {
|
|
249
|
-
|
|
250
|
-
hash: event.item.hash,
|
|
251
|
-
kind,
|
|
252
|
-
originalName: sanitizedName,
|
|
253
|
-
width,
|
|
254
|
-
height,
|
|
255
|
-
mimeType: kind === "image" ? "image/webp" : event.item.mimeType,
|
|
256
|
-
size: result.primaryBlob.size,
|
|
257
|
-
folder: `${sanitizedName}-${event.item.hash}`,
|
|
258
|
-
variants: result.variants.map((v) => ({
|
|
259
|
-
width: v.width,
|
|
260
|
-
height: v.height,
|
|
261
|
-
size: v.size,
|
|
262
|
-
})),
|
|
263
|
-
alt: "",
|
|
264
|
-
storage: "github",
|
|
274
|
+
...base,
|
|
265
275
|
...(targetFolderId ? { folderId: targetFolderId } : {}),
|
|
266
276
|
...(hiddenUploadsRef.current.get(event.item.hash) ? { hidden: true } : {}),
|
|
267
277
|
};
|
|
@@ -408,17 +418,13 @@ export function useMediaPipeline({
|
|
|
408
418
|
|
|
409
419
|
try {
|
|
410
420
|
const { posterBlob, width, height, duration } = await generateVideoPoster(file, mediaConfig.quality);
|
|
411
|
-
const sanitizedName = sanitizeMediaName(file.name);
|
|
412
421
|
const item: LibraryMediaItem = {
|
|
413
|
-
|
|
414
|
-
hash,
|
|
422
|
+
...mediaIdentity(hash, file.name),
|
|
415
423
|
kind: "video",
|
|
416
|
-
originalName: sanitizedName,
|
|
417
424
|
width,
|
|
418
425
|
height,
|
|
419
426
|
mimeType: file.type,
|
|
420
427
|
size: file.size,
|
|
421
|
-
folder: `${sanitizedName}-${hash}`,
|
|
422
428
|
variants: [],
|
|
423
429
|
alt: "",
|
|
424
430
|
storage: "assets",
|