@templatical/core 0.34.3 → 0.36.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.
- package/dist/cloud/index.d.ts +121 -18
- package/dist/cloud/index.js +405 -92
- package/dist/cloud/index.js.map +1 -1
- package/dist/index.d.ts +27 -2
- package/dist/index.js +122 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cloud/index.js
CHANGED
|
@@ -108,14 +108,14 @@ var AuthManager = class AuthManager {
|
|
|
108
108
|
const response = await fetch(this.url, fetchOptions);
|
|
109
109
|
if (!response.ok) throw new SdkError(`Token refresh failed: ${response.status}`, response.status);
|
|
110
110
|
const data = await response.json();
|
|
111
|
-
if (!data.token || !data.
|
|
111
|
+
if (!data.token || !data.expiresAt || !data.projectId || !data.tenant) throw new Error("Invalid token response: missing token, expiresAt, projectId, or tenant");
|
|
112
112
|
this.accessToken = data.token;
|
|
113
|
-
this.expiresAt = /* @__PURE__ */ new Date(data.
|
|
114
|
-
this._projectId = data.
|
|
113
|
+
this.expiresAt = /* @__PURE__ */ new Date(data.expiresAt * 1e3);
|
|
114
|
+
this._projectId = data.projectId;
|
|
115
115
|
this._tenantSlug = data.tenant;
|
|
116
|
-
if (data.
|
|
117
|
-
allowedEmails: data.
|
|
118
|
-
signature: data.
|
|
116
|
+
if (data.testEmail?.allowedEmails && data.testEmail?.signature) this._testEmailConfig = {
|
|
117
|
+
allowedEmails: data.testEmail.allowedEmails,
|
|
118
|
+
signature: data.testEmail.signature
|
|
119
119
|
};
|
|
120
120
|
else this._testEmailConfig = null;
|
|
121
121
|
if (data.user?.id && data.user?.name && data.user?.signature) this._userConfig = {
|
|
@@ -440,12 +440,184 @@ var ApiClient = class {
|
|
|
440
440
|
}
|
|
441
441
|
};
|
|
442
442
|
//#endregion
|
|
443
|
+
//#region src/cloud/media-api.ts
|
|
444
|
+
/**
|
|
445
|
+
* Cloud's HTTP client for media. Auth and project/tenant scoping come from
|
|
446
|
+
* {@link AuthManager}. The BYO contract lives on
|
|
447
|
+
* `createCloudMediaProvider`, which maps this client's rows.
|
|
448
|
+
*/
|
|
449
|
+
var MediaApiClient = class {
|
|
450
|
+
authManager;
|
|
451
|
+
constructor(authManager) {
|
|
452
|
+
this.authManager = authManager;
|
|
453
|
+
}
|
|
454
|
+
get projectId() {
|
|
455
|
+
return this.authManager.projectId;
|
|
456
|
+
}
|
|
457
|
+
get tenantSlug() {
|
|
458
|
+
return this.authManager.tenantSlug;
|
|
459
|
+
}
|
|
460
|
+
get baseParams() {
|
|
461
|
+
return {
|
|
462
|
+
project: this.projectId,
|
|
463
|
+
tenant: this.tenantSlug
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
async request(path, options = {}) {
|
|
467
|
+
const response = await this.authManager.authenticatedFetch(path, {
|
|
468
|
+
...options,
|
|
469
|
+
headers: {
|
|
470
|
+
"Content-Type": "application/json",
|
|
471
|
+
Accept: "application/json",
|
|
472
|
+
...options.headers
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
if (!response.ok) {
|
|
476
|
+
const error = await response.json().catch(() => ({ message: `HTTP error ${response.status}` }));
|
|
477
|
+
throw new Error(error.message, { cause: error });
|
|
478
|
+
}
|
|
479
|
+
if (response.status === 204) return;
|
|
480
|
+
return (await response.json()).data;
|
|
481
|
+
}
|
|
482
|
+
async browseMedia(params) {
|
|
483
|
+
const query = new URLSearchParams();
|
|
484
|
+
if (params.folderId) query.set("folderId", params.folderId);
|
|
485
|
+
if (params.search) query.set("search", params.search);
|
|
486
|
+
if (params.category) query.set("category", params.category);
|
|
487
|
+
if (params.sort) query.set("sort", params.sort);
|
|
488
|
+
if (params.cursor) query.set("cursor", params.cursor);
|
|
489
|
+
const queryString = query.toString();
|
|
490
|
+
const url = `${buildUrl(API_ROUTES["media.browse"], this.baseParams)}${queryString ? `?${queryString}` : ""}`;
|
|
491
|
+
const response = await this.authManager.authenticatedFetch(url, { headers: { Accept: "application/json" } });
|
|
492
|
+
if (!response.ok) {
|
|
493
|
+
const error = await response.json().catch(() => ({ message: `HTTP error ${response.status}` }));
|
|
494
|
+
throw new Error(error.message, { cause: error });
|
|
495
|
+
}
|
|
496
|
+
return response.json();
|
|
497
|
+
}
|
|
498
|
+
async uploadMedia(file, folderId) {
|
|
499
|
+
const formData = new FormData();
|
|
500
|
+
formData.append("file", file);
|
|
501
|
+
if (folderId) formData.append("folderId", folderId);
|
|
502
|
+
const url = buildUrl(API_ROUTES["media.upload"], this.baseParams);
|
|
503
|
+
const response = await this.authManager.authenticatedFetch(url, {
|
|
504
|
+
method: "POST",
|
|
505
|
+
headers: { Accept: "application/json" },
|
|
506
|
+
body: formData
|
|
507
|
+
});
|
|
508
|
+
if (!response.ok) {
|
|
509
|
+
const error = await response.json().catch(() => ({ message: `HTTP error ${response.status}` }));
|
|
510
|
+
throw new Error(error.message, { cause: error });
|
|
511
|
+
}
|
|
512
|
+
return (await response.json()).data;
|
|
513
|
+
}
|
|
514
|
+
async updateMedia(mediaId, filename, alt) {
|
|
515
|
+
const body = {};
|
|
516
|
+
if (filename !== void 0) body.filename = filename;
|
|
517
|
+
if (alt !== void 0) body.alt = alt;
|
|
518
|
+
return this.request(buildUrl(API_ROUTES["media.update"], {
|
|
519
|
+
...this.baseParams,
|
|
520
|
+
media: mediaId
|
|
521
|
+
}), {
|
|
522
|
+
method: "PUT",
|
|
523
|
+
body: JSON.stringify(body)
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
async deleteMedia(ids) {
|
|
527
|
+
return this.request(buildUrl(API_ROUTES["media.delete"], this.baseParams), {
|
|
528
|
+
method: "POST",
|
|
529
|
+
body: JSON.stringify({ ids })
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
async moveMedia(ids, folderId) {
|
|
533
|
+
return this.request(buildUrl(API_ROUTES["media.move"], this.baseParams), {
|
|
534
|
+
method: "POST",
|
|
535
|
+
body: JSON.stringify({
|
|
536
|
+
ids,
|
|
537
|
+
folderId
|
|
538
|
+
})
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
async getMediaFolders() {
|
|
542
|
+
return this.request(buildUrl(API_ROUTES["folders.index"], this.baseParams));
|
|
543
|
+
}
|
|
544
|
+
async createMediaFolder(name, parentId) {
|
|
545
|
+
return this.request(buildUrl(API_ROUTES["folders.store"], this.baseParams), {
|
|
546
|
+
method: "POST",
|
|
547
|
+
body: JSON.stringify({
|
|
548
|
+
name,
|
|
549
|
+
parentId: parentId ?? null
|
|
550
|
+
})
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
async renameMediaFolder(folderId, name) {
|
|
554
|
+
return this.request(buildUrl(API_ROUTES["folders.update"], {
|
|
555
|
+
...this.baseParams,
|
|
556
|
+
mediaFolder: folderId
|
|
557
|
+
}), {
|
|
558
|
+
method: "PUT",
|
|
559
|
+
body: JSON.stringify({ name })
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
async deleteMediaFolder(folderId) {
|
|
563
|
+
return this.request(buildUrl(API_ROUTES["folders.destroy"], {
|
|
564
|
+
...this.baseParams,
|
|
565
|
+
mediaFolder: folderId
|
|
566
|
+
}), { method: "DELETE" });
|
|
567
|
+
}
|
|
568
|
+
async checkMediaUsage(ids) {
|
|
569
|
+
const response = await this.authManager.authenticatedFetch(buildUrl(API_ROUTES["media.checkUsage"], this.baseParams), {
|
|
570
|
+
method: "POST",
|
|
571
|
+
headers: {
|
|
572
|
+
"Content-Type": "application/json",
|
|
573
|
+
Accept: "application/json"
|
|
574
|
+
},
|
|
575
|
+
body: JSON.stringify({ ids })
|
|
576
|
+
});
|
|
577
|
+
if (!response.ok) {
|
|
578
|
+
const error = await response.json().catch(() => ({ message: `HTTP error ${response.status}` }));
|
|
579
|
+
throw new Error(error.message, { cause: error });
|
|
580
|
+
}
|
|
581
|
+
return response.json();
|
|
582
|
+
}
|
|
583
|
+
async getFrequentlyUsed() {
|
|
584
|
+
return this.request(buildUrl(API_ROUTES["media.frequentlyUsed"], this.baseParams));
|
|
585
|
+
}
|
|
586
|
+
async importFromUrl(url, folderId) {
|
|
587
|
+
return this.request(buildUrl(API_ROUTES["media.importFromUrl"], this.baseParams), {
|
|
588
|
+
method: "POST",
|
|
589
|
+
body: JSON.stringify({
|
|
590
|
+
url,
|
|
591
|
+
folderId: folderId ?? null
|
|
592
|
+
})
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
async replaceMedia(mediaId, file) {
|
|
596
|
+
const formData = new FormData();
|
|
597
|
+
formData.append("file", file);
|
|
598
|
+
const url = buildUrl(API_ROUTES["media.replace"], {
|
|
599
|
+
...this.baseParams,
|
|
600
|
+
media: mediaId
|
|
601
|
+
});
|
|
602
|
+
const response = await this.authManager.authenticatedFetch(url, {
|
|
603
|
+
method: "POST",
|
|
604
|
+
headers: { Accept: "application/json" },
|
|
605
|
+
body: formData
|
|
606
|
+
});
|
|
607
|
+
if (!response.ok) {
|
|
608
|
+
const error = await response.json().catch(() => ({ message: `HTTP error ${response.status}` }));
|
|
609
|
+
throw new Error(error.message, { cause: error });
|
|
610
|
+
}
|
|
611
|
+
return (await response.json()).data;
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
//#endregion
|
|
443
615
|
//#region src/cloud/websocket-client.ts
|
|
444
616
|
function resolveWebSocketConfig(serverConfig) {
|
|
445
617
|
return {
|
|
446
618
|
host: serverConfig.host,
|
|
447
619
|
port: serverConfig.port,
|
|
448
|
-
appKey: serverConfig.
|
|
620
|
+
appKey: serverConfig.appKey
|
|
449
621
|
};
|
|
450
622
|
}
|
|
451
623
|
var WebSocketClient = class {
|
|
@@ -487,9 +659,9 @@ var WebSocketClient = class {
|
|
|
487
659
|
Accept: "application/json"
|
|
488
660
|
},
|
|
489
661
|
params: {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
662
|
+
userId: this.authManager.userConfig?.id ?? "",
|
|
663
|
+
userName: this.authManager.userConfig?.name ?? "",
|
|
664
|
+
userSignature: this.authManager.userConfig?.signature ?? ""
|
|
493
665
|
}
|
|
494
666
|
}
|
|
495
667
|
});
|
|
@@ -525,25 +697,25 @@ var WebSocketClient = class {
|
|
|
525
697
|
function handleOperation(editor, payload) {
|
|
526
698
|
const { operation, data } = payload;
|
|
527
699
|
switch (operation) {
|
|
528
|
-
case "
|
|
529
|
-
editor.addBlock(data.block, data.
|
|
700
|
+
case "addBlock":
|
|
701
|
+
editor.addBlock(data.block, data.sectionId, data.columnIndex, data.index);
|
|
530
702
|
break;
|
|
531
|
-
case "
|
|
532
|
-
editor.updateBlock(data.
|
|
703
|
+
case "updateBlock":
|
|
704
|
+
editor.updateBlock(data.blockId, data.updates);
|
|
533
705
|
break;
|
|
534
|
-
case "
|
|
535
|
-
editor.removeBlock(data.
|
|
706
|
+
case "deleteBlock":
|
|
707
|
+
editor.removeBlock(data.blockId);
|
|
536
708
|
break;
|
|
537
|
-
case "
|
|
538
|
-
editor.moveBlock(data.
|
|
709
|
+
case "moveBlock":
|
|
710
|
+
editor.moveBlock(data.blockId, data.index, data.sectionId, data.columnIndex);
|
|
539
711
|
break;
|
|
540
|
-
case "
|
|
712
|
+
case "updateSettings":
|
|
541
713
|
editor.updateSettings(data.updates);
|
|
542
714
|
break;
|
|
543
|
-
case "
|
|
715
|
+
case "setContent":
|
|
544
716
|
editor.setContent(data.content);
|
|
545
717
|
break;
|
|
546
|
-
case "
|
|
718
|
+
case "updateBlockStyle": editor.updateBlock(data.blockId, { styles: data.styles });
|
|
547
719
|
}
|
|
548
720
|
}
|
|
549
721
|
//#endregion
|
|
@@ -595,12 +767,12 @@ function useAiChat(options) {
|
|
|
595
767
|
});
|
|
596
768
|
if (!response.ok) return;
|
|
597
769
|
const data = await response.json();
|
|
598
|
-
if (data.
|
|
770
|
+
if (data.conversationId) conversationId.value = data.conversationId;
|
|
599
771
|
if (Array.isArray(data.data) && data.data.length > 0) messages.value = data.data.map((msg) => ({
|
|
600
772
|
id: msg.id,
|
|
601
773
|
role: msg.role,
|
|
602
774
|
content: msg.content,
|
|
603
|
-
timestamp: new Date(msg.
|
|
775
|
+
timestamp: new Date(msg.createdAt).getTime()
|
|
604
776
|
}));
|
|
605
777
|
} catch {} finally {
|
|
606
778
|
isLoadingHistory.value = false;
|
|
@@ -623,8 +795,8 @@ function useAiChat(options) {
|
|
|
623
795
|
Accept: "text/event-stream"
|
|
624
796
|
},
|
|
625
797
|
body: JSON.stringify({
|
|
626
|
-
|
|
627
|
-
|
|
798
|
+
currentContent,
|
|
799
|
+
mergeTags: mergeTags.map((p) => ({
|
|
628
800
|
label: p.label,
|
|
629
801
|
value: p.value
|
|
630
802
|
}))
|
|
@@ -691,17 +863,17 @@ function useAiChat(options) {
|
|
|
691
863
|
},
|
|
692
864
|
body: JSON.stringify({
|
|
693
865
|
prompt,
|
|
694
|
-
|
|
695
|
-
|
|
866
|
+
currentContent,
|
|
867
|
+
mergeTags: mergeTags.map((p) => ({
|
|
696
868
|
label: p.label,
|
|
697
869
|
value: p.value
|
|
698
870
|
})),
|
|
699
|
-
|
|
871
|
+
conversationId: conversationId.value
|
|
700
872
|
})
|
|
701
873
|
});
|
|
702
874
|
if (!response.ok) {
|
|
703
875
|
const errorData = await response.json().catch(() => null);
|
|
704
|
-
if (response.status === 403) throw new Error("
|
|
876
|
+
if (response.status === 403) throw new Error("aiGenerationNotAvailable");
|
|
705
877
|
throw new Error(errorData?.message || "Failed to generate template");
|
|
706
878
|
}
|
|
707
879
|
const reader = response.body?.getReader();
|
|
@@ -728,7 +900,7 @@ function useAiChat(options) {
|
|
|
728
900
|
if (event.type === "text") updateMessage(assistantMsgId, { content: (messages.value.find((m) => m.id === assistantMsgId)?.content ?? "") + event.text });
|
|
729
901
|
else if (event.type === "error") throw new Error(event.message || "Failed to generate template");
|
|
730
902
|
else if (event.type === "done") {
|
|
731
|
-
if (event.
|
|
903
|
+
if (event.conversationId) conversationId.value = event.conversationId;
|
|
732
904
|
updateMessage(assistantMsgId, { content: event.text });
|
|
733
905
|
result = event.content ?? null;
|
|
734
906
|
if (result) {
|
|
@@ -822,14 +994,14 @@ function useAiRewrite(options) {
|
|
|
822
994
|
body: JSON.stringify({
|
|
823
995
|
content,
|
|
824
996
|
instruction,
|
|
825
|
-
|
|
997
|
+
mergeTags: mergeTags.map((p) => ({
|
|
826
998
|
label: p.label,
|
|
827
999
|
value: p.value
|
|
828
1000
|
}))
|
|
829
1001
|
})
|
|
830
1002
|
});
|
|
831
1003
|
if (!response.ok) {
|
|
832
|
-
if (response.status === 403) throw new Error("
|
|
1004
|
+
if (response.status === 403) throw new Error("aiGenerationNotAvailable");
|
|
833
1005
|
const errorData = await response.json().catch(() => null);
|
|
834
1006
|
throw new Error(errorData?.message || "Failed to rewrite text");
|
|
835
1007
|
}
|
|
@@ -944,15 +1116,15 @@ function useTemplateScoring(options) {
|
|
|
944
1116
|
Accept: "text/event-stream"
|
|
945
1117
|
},
|
|
946
1118
|
body: JSON.stringify({
|
|
947
|
-
|
|
948
|
-
|
|
1119
|
+
currentContent: content,
|
|
1120
|
+
mergeTags: mergeTags.map((p) => ({
|
|
949
1121
|
label: p.label,
|
|
950
1122
|
value: p.value
|
|
951
1123
|
}))
|
|
952
1124
|
})
|
|
953
1125
|
});
|
|
954
1126
|
if (!response.ok) {
|
|
955
|
-
if (response.status === 403) throw new Error("
|
|
1127
|
+
if (response.status === 403) throw new Error("aiGenerationNotAvailable");
|
|
956
1128
|
const errorData = await response.json().catch(() => null);
|
|
957
1129
|
throw new Error(errorData?.message || "Failed to score template");
|
|
958
1130
|
}
|
|
@@ -1015,14 +1187,14 @@ function useTemplateScoring(options) {
|
|
|
1015
1187
|
suggestion: finding.suggestion,
|
|
1016
1188
|
category: finding.category
|
|
1017
1189
|
},
|
|
1018
|
-
|
|
1190
|
+
mergeTags: mergeTags.map((p) => ({
|
|
1019
1191
|
label: p.label,
|
|
1020
1192
|
value: p.value
|
|
1021
1193
|
}))
|
|
1022
1194
|
})
|
|
1023
1195
|
});
|
|
1024
1196
|
if (!response.ok) {
|
|
1025
|
-
if (response.status === 403) throw new Error("
|
|
1197
|
+
if (response.status === 403) throw new Error("aiGenerationNotAvailable");
|
|
1026
1198
|
const errorData = await response.json().catch(() => null);
|
|
1027
1199
|
throw new Error(errorData?.message || "Failed to fix finding");
|
|
1028
1200
|
}
|
|
@@ -1113,7 +1285,7 @@ function useDesignReference(options) {
|
|
|
1113
1285
|
});
|
|
1114
1286
|
if (!response.ok) {
|
|
1115
1287
|
const errorData = await response.json().catch(() => null);
|
|
1116
|
-
if (response.status === 403) throw new Error("
|
|
1288
|
+
if (response.status === 403) throw new Error("aiGenerationNotAvailable");
|
|
1117
1289
|
throw new Error(errorData?.message || "Failed to generate template from design");
|
|
1118
1290
|
}
|
|
1119
1291
|
const reader = response.body?.getReader();
|
|
@@ -1169,28 +1341,23 @@ function useDesignReference(options) {
|
|
|
1169
1341
|
/** The Pusher event Cloud's backend fans comment writes out on. */
|
|
1170
1342
|
const BROADCAST_EVENT = "comment-broadcast";
|
|
1171
1343
|
/**
|
|
1172
|
-
* Cloud's
|
|
1173
|
-
*
|
|
1174
|
-
*
|
|
1344
|
+
* Cloud's HTTP row → {@link Comment}. Omits `updatedAt` when it equals
|
|
1345
|
+
* `createdAt` (Cloud stamps both on insert) so the panel does not mark
|
|
1346
|
+
* every comment "(edited)", and omits `resolvedBy` / empty `replies`
|
|
1347
|
+
* when they are absent.
|
|
1175
1348
|
*/
|
|
1176
1349
|
function toComment(record) {
|
|
1177
1350
|
const comment = {
|
|
1178
1351
|
id: record.id,
|
|
1179
1352
|
body: record.body,
|
|
1180
|
-
author:
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
blockId: record.block_id,
|
|
1186
|
-
parentId: record.parent_id,
|
|
1187
|
-
resolvedAt: record.resolved_at
|
|
1188
|
-
};
|
|
1189
|
-
if (record.updated_at !== record.created_at) comment.updatedAt = record.updated_at;
|
|
1190
|
-
if (record.resolved_by_identifier) comment.resolvedBy = {
|
|
1191
|
-
id: record.resolved_by_identifier,
|
|
1192
|
-
name: record.resolved_by_name ?? ""
|
|
1353
|
+
author: record.author,
|
|
1354
|
+
createdAt: record.createdAt,
|
|
1355
|
+
blockId: record.blockId,
|
|
1356
|
+
parentId: record.parentId,
|
|
1357
|
+
resolvedAt: record.resolvedAt
|
|
1193
1358
|
};
|
|
1359
|
+
if (record.updatedAt !== record.createdAt) comment.updatedAt = record.updatedAt;
|
|
1360
|
+
if (record.resolvedBy) comment.resolvedBy = record.resolvedBy;
|
|
1194
1361
|
if (record.replies?.length) comment.replies = record.replies.map(toComment);
|
|
1195
1362
|
return comment;
|
|
1196
1363
|
}
|
|
@@ -1206,7 +1373,7 @@ function toComment(record) {
|
|
|
1206
1373
|
* Two things about the write payloads are Cloud's alone and stay on this side of
|
|
1207
1374
|
* the seam:
|
|
1208
1375
|
*
|
|
1209
|
-
* - **The author is signed.** `
|
|
1376
|
+
* - **The author is signed.** `userId` / `userName` / `userSignature` come from
|
|
1210
1377
|
* the JWT, not from the editor's `user` config, so a browser cannot attribute a
|
|
1211
1378
|
* comment to someone else. The editor's `user` key still gates the feature and
|
|
1212
1379
|
* drives "you wrote this" in the UI; the two agree because `initCloud()` fills
|
|
@@ -1223,9 +1390,9 @@ function createCloudCommentsProvider(options) {
|
|
|
1223
1390
|
const user = authManager.userConfig;
|
|
1224
1391
|
if (!user) throw new Error("[Templatical] Cloud comments need a signed user in the auth token. The project's token endpoint must include a `user` claim.");
|
|
1225
1392
|
return {
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1393
|
+
userId: user.id,
|
|
1394
|
+
userName: user.name,
|
|
1395
|
+
userSignature: user.signature
|
|
1229
1396
|
};
|
|
1230
1397
|
}
|
|
1231
1398
|
function socketHeaders() {
|
|
@@ -1239,8 +1406,8 @@ function createCloudCommentsProvider(options) {
|
|
|
1239
1406
|
async create(templateId, input) {
|
|
1240
1407
|
return toComment(await api.createComment(templateId, {
|
|
1241
1408
|
body: input.body,
|
|
1242
|
-
|
|
1243
|
-
|
|
1409
|
+
blockId: input.blockId,
|
|
1410
|
+
parentId: input.parentId,
|
|
1244
1411
|
...userPayload()
|
|
1245
1412
|
}, socketHeaders()));
|
|
1246
1413
|
},
|
|
@@ -1261,22 +1428,22 @@ function createCloudCommentsProvider(options) {
|
|
|
1261
1428
|
function handle(payload) {
|
|
1262
1429
|
const comment = toComment(payload.comment);
|
|
1263
1430
|
switch (payload.action) {
|
|
1264
|
-
case "
|
|
1431
|
+
case "commentCreated":
|
|
1265
1432
|
onChange({
|
|
1266
1433
|
type: "created",
|
|
1267
1434
|
comment
|
|
1268
1435
|
});
|
|
1269
1436
|
break;
|
|
1270
|
-
case "
|
|
1437
|
+
case "commentDeleted":
|
|
1271
1438
|
onChange({
|
|
1272
1439
|
type: "deleted",
|
|
1273
1440
|
commentId: comment.id,
|
|
1274
1441
|
parentId: comment.parentId
|
|
1275
1442
|
});
|
|
1276
1443
|
break;
|
|
1277
|
-
case "
|
|
1278
|
-
case "
|
|
1279
|
-
case "
|
|
1444
|
+
case "commentUpdated":
|
|
1445
|
+
case "commentResolved":
|
|
1446
|
+
case "commentUnresolved": onChange({
|
|
1280
1447
|
type: "updated",
|
|
1281
1448
|
comment
|
|
1282
1449
|
});
|
|
@@ -1298,8 +1465,8 @@ function createCloudCommentsProvider(options) {
|
|
|
1298
1465
|
const COLLAB_EVENTS = [
|
|
1299
1466
|
"pusher:member_added",
|
|
1300
1467
|
"pusher:member_removed",
|
|
1301
|
-
"client-
|
|
1302
|
-
"client-
|
|
1468
|
+
"client-blockLocked",
|
|
1469
|
+
"client-blockUnlocked",
|
|
1303
1470
|
"client-operation",
|
|
1304
1471
|
"mcp-operation"
|
|
1305
1472
|
];
|
|
@@ -1388,14 +1555,14 @@ function useCollaboration(options) {
|
|
|
1388
1555
|
}
|
|
1389
1556
|
function broadcastBlockLocked(blockId) {
|
|
1390
1557
|
if (!channel.value) return;
|
|
1391
|
-
channel.value.trigger("client-
|
|
1558
|
+
channel.value.trigger("client-blockLocked", {
|
|
1392
1559
|
blockId,
|
|
1393
1560
|
userId: myUserId.value
|
|
1394
1561
|
});
|
|
1395
1562
|
}
|
|
1396
1563
|
function broadcastBlockUnlocked(blockId) {
|
|
1397
1564
|
if (!channel.value) return;
|
|
1398
|
-
channel.value.trigger("client-
|
|
1565
|
+
channel.value.trigger("client-blockUnlocked", { blockId });
|
|
1399
1566
|
}
|
|
1400
1567
|
watch(() => editor.state.selectedBlockId, (newBlockId, oldBlockId) => {
|
|
1401
1568
|
if (isProcessingRemoteOperation) return;
|
|
@@ -1423,7 +1590,7 @@ function useCollaboration(options) {
|
|
|
1423
1590
|
removeCollaborator(member.id);
|
|
1424
1591
|
if (collaborator) options.onCollaboratorLeft?.(collaborator);
|
|
1425
1592
|
});
|
|
1426
|
-
newChannel.bind("client-
|
|
1593
|
+
newChannel.bind("client-blockLocked", (data) => {
|
|
1427
1594
|
handleBlockLocked(data);
|
|
1428
1595
|
const collaborator = collaborators.value.find((c) => c.id === data.userId);
|
|
1429
1596
|
if (collaborator) options.onBlockLocked?.({
|
|
@@ -1431,7 +1598,7 @@ function useCollaboration(options) {
|
|
|
1431
1598
|
collaborator
|
|
1432
1599
|
});
|
|
1433
1600
|
});
|
|
1434
|
-
newChannel.bind("client-
|
|
1601
|
+
newChannel.bind("client-blockUnlocked", (data) => {
|
|
1435
1602
|
const holder = lockedBlocks.value.get(data.blockId);
|
|
1436
1603
|
handleBlockUnlocked(data);
|
|
1437
1604
|
if (holder) options.onBlockUnlocked?.({
|
|
@@ -1475,11 +1642,11 @@ function useCollaborationBroadcast(editor, collaboration) {
|
|
|
1475
1642
|
editor.addBlock = (block, targetSectionId, columnIndex, index) => {
|
|
1476
1643
|
originalAddBlock(block, targetSectionId, columnIndex, index);
|
|
1477
1644
|
collaboration._broadcastOperation({
|
|
1478
|
-
operation: "
|
|
1645
|
+
operation: "addBlock",
|
|
1479
1646
|
data: {
|
|
1480
1647
|
block,
|
|
1481
|
-
|
|
1482
|
-
|
|
1648
|
+
sectionId: targetSectionId,
|
|
1649
|
+
columnIndex,
|
|
1483
1650
|
index
|
|
1484
1651
|
},
|
|
1485
1652
|
timestamp: Date.now()
|
|
@@ -1488,9 +1655,9 @@ function useCollaborationBroadcast(editor, collaboration) {
|
|
|
1488
1655
|
editor.updateBlock = (blockId, updates) => {
|
|
1489
1656
|
originalUpdateBlock(blockId, updates);
|
|
1490
1657
|
collaboration._broadcastOperation({
|
|
1491
|
-
operation: "
|
|
1658
|
+
operation: "updateBlock",
|
|
1492
1659
|
data: {
|
|
1493
|
-
|
|
1660
|
+
blockId,
|
|
1494
1661
|
updates
|
|
1495
1662
|
},
|
|
1496
1663
|
timestamp: Date.now()
|
|
@@ -1499,20 +1666,20 @@ function useCollaborationBroadcast(editor, collaboration) {
|
|
|
1499
1666
|
editor.removeBlock = (blockId) => {
|
|
1500
1667
|
originalRemoveBlock(blockId);
|
|
1501
1668
|
collaboration._broadcastOperation({
|
|
1502
|
-
operation: "
|
|
1503
|
-
data: {
|
|
1669
|
+
operation: "deleteBlock",
|
|
1670
|
+
data: { blockId },
|
|
1504
1671
|
timestamp: Date.now()
|
|
1505
1672
|
});
|
|
1506
1673
|
};
|
|
1507
1674
|
editor.moveBlock = (blockId, newIndex, targetSectionId, columnIndex) => {
|
|
1508
1675
|
originalMoveBlock(blockId, newIndex, targetSectionId, columnIndex);
|
|
1509
1676
|
collaboration._broadcastOperation({
|
|
1510
|
-
operation: "
|
|
1677
|
+
operation: "moveBlock",
|
|
1511
1678
|
data: {
|
|
1512
|
-
|
|
1679
|
+
blockId,
|
|
1513
1680
|
index: newIndex,
|
|
1514
|
-
|
|
1515
|
-
|
|
1681
|
+
sectionId: targetSectionId,
|
|
1682
|
+
columnIndex
|
|
1516
1683
|
},
|
|
1517
1684
|
timestamp: Date.now()
|
|
1518
1685
|
});
|
|
@@ -1520,7 +1687,7 @@ function useCollaborationBroadcast(editor, collaboration) {
|
|
|
1520
1687
|
editor.updateSettings = (updates) => {
|
|
1521
1688
|
originalUpdateSettings(updates);
|
|
1522
1689
|
collaboration._broadcastOperation({
|
|
1523
|
-
operation: "
|
|
1690
|
+
operation: "updateSettings",
|
|
1524
1691
|
data: { updates },
|
|
1525
1692
|
timestamp: Date.now()
|
|
1526
1693
|
});
|
|
@@ -1528,7 +1695,7 @@ function useCollaborationBroadcast(editor, collaboration) {
|
|
|
1528
1695
|
editor.setContent = (content, markDirty) => {
|
|
1529
1696
|
originalSetContent(content, markDirty);
|
|
1530
1697
|
collaboration._broadcastOperation({
|
|
1531
|
-
operation: "
|
|
1698
|
+
operation: "setContent",
|
|
1532
1699
|
data: { content },
|
|
1533
1700
|
timestamp: Date.now()
|
|
1534
1701
|
});
|
|
@@ -1621,6 +1788,152 @@ function createCloudSavedBlocksProvider(authManager) {
|
|
|
1621
1788
|
};
|
|
1622
1789
|
}
|
|
1623
1790
|
//#endregion
|
|
1791
|
+
//#region src/cloud/media-provider.ts
|
|
1792
|
+
/**
|
|
1793
|
+
* Cloud's HTTP row → {@link MediaAsset}. Omits null optionals so
|
|
1794
|
+
* `thumbnailUrl` / `width` / `height` are absent rather than `null` —
|
|
1795
|
+
* the grid falls back to `url` when there is no thumbnail. Confirm
|
|
1796
|
+
* always inserts `url`.
|
|
1797
|
+
*/
|
|
1798
|
+
function toAsset(item) {
|
|
1799
|
+
const asset = {
|
|
1800
|
+
id: item.id,
|
|
1801
|
+
url: item.url,
|
|
1802
|
+
filename: item.filename,
|
|
1803
|
+
alt: item.alt,
|
|
1804
|
+
mimeType: item.mimeType,
|
|
1805
|
+
folderId: item.folderId,
|
|
1806
|
+
createdAt: item.createdAt,
|
|
1807
|
+
updatedAt: item.updatedAt,
|
|
1808
|
+
size: item.size
|
|
1809
|
+
};
|
|
1810
|
+
if (item.width != null) asset.width = item.width;
|
|
1811
|
+
if (item.height != null) asset.height = item.height;
|
|
1812
|
+
if (item.thumbnailUrl != null) asset.thumbnailUrl = item.thumbnailUrl;
|
|
1813
|
+
return asset;
|
|
1814
|
+
}
|
|
1815
|
+
function toFolder(folder, inheritedParentId = null) {
|
|
1816
|
+
return {
|
|
1817
|
+
id: folder.id,
|
|
1818
|
+
name: folder.name,
|
|
1819
|
+
parentId: folder.parentId !== void 0 ? folder.parentId : inheritedParentId
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Flatten a Cloud folder payload into the contract's flat list.
|
|
1824
|
+
*
|
|
1825
|
+
* The index endpoint may return a tree (`children`) or already-flat
|
|
1826
|
+
* `parentId` rows. Depth-first so a parent is always listed before its
|
|
1827
|
+
* descendants. Already-flat rows just copy `parentId`.
|
|
1828
|
+
*/
|
|
1829
|
+
function flattenFolders(nodes, inheritedParentId = null) {
|
|
1830
|
+
const out = [];
|
|
1831
|
+
for (const node of nodes) {
|
|
1832
|
+
out.push(toFolder(node, inheritedParentId));
|
|
1833
|
+
if (node.children?.length) out.push(...flattenFolders(node.children, node.id));
|
|
1834
|
+
}
|
|
1835
|
+
return out;
|
|
1836
|
+
}
|
|
1837
|
+
function mimeTypesFromPlan(plan) {
|
|
1838
|
+
const categories = plan?.media?.categories;
|
|
1839
|
+
if (!categories) return;
|
|
1840
|
+
const mapped = {};
|
|
1841
|
+
for (const [key, value] of Object.entries(categories)) mapped[key] = value.mimeTypes;
|
|
1842
|
+
return mapped;
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Cloud-backed {@link MediaProvider} — the Templatical Cloud adapter for the
|
|
1846
|
+
* same DAM contract consumers implement themselves.
|
|
1847
|
+
*
|
|
1848
|
+
* Every method is a function: Cloud's media storage is what the plan pays
|
|
1849
|
+
* for, so there is no Cloud tier that can list but not upload. A consumer
|
|
1850
|
+
* who wants a read-only gallery supplies their own provider with the
|
|
1851
|
+
* mutations set to `false`.
|
|
1852
|
+
*
|
|
1853
|
+
* **Ignores `templateId`.** Cloud's library is project-scoped; a CMS that
|
|
1854
|
+
* scopes a gallery per template reads that param in its own `list`.
|
|
1855
|
+
*
|
|
1856
|
+
* **`storage` / `maxFileSize` / `mimeTypes` are live.** They read
|
|
1857
|
+
* `getPlanConfig()` at call time, because plan config arrives after
|
|
1858
|
+
* construction. Snapshotting at setup would pin `storage()` to `null` and
|
|
1859
|
+
* hide the quota ring for the whole session. A spread of this object
|
|
1860
|
+
* freezes the getters — `initCloud()` must `Object.assign`, never spread.
|
|
1861
|
+
*/
|
|
1862
|
+
function createCloudMediaProvider(authManager, getPlanConfig) {
|
|
1863
|
+
const api = new MediaApiClient(authManager);
|
|
1864
|
+
return {
|
|
1865
|
+
async list(params) {
|
|
1866
|
+
const response = await api.browseMedia({
|
|
1867
|
+
search: params?.search,
|
|
1868
|
+
cursor: params?.cursor,
|
|
1869
|
+
folderId: params?.folderId,
|
|
1870
|
+
category: params?.category
|
|
1871
|
+
});
|
|
1872
|
+
const page = { items: response.data.map(toAsset) };
|
|
1873
|
+
if (response.meta.nextCursor) page.nextCursor = response.meta.nextCursor;
|
|
1874
|
+
return page;
|
|
1875
|
+
},
|
|
1876
|
+
async create(input) {
|
|
1877
|
+
return toAsset(await api.uploadMedia(input.file, input.folderId));
|
|
1878
|
+
},
|
|
1879
|
+
async update(id, patch) {
|
|
1880
|
+
return toAsset(await api.updateMedia(id, patch.filename, patch.alt));
|
|
1881
|
+
},
|
|
1882
|
+
delete(ids) {
|
|
1883
|
+
return api.deleteMedia(ids);
|
|
1884
|
+
},
|
|
1885
|
+
folders: {
|
|
1886
|
+
async list() {
|
|
1887
|
+
return flattenFolders(await api.getMediaFolders());
|
|
1888
|
+
},
|
|
1889
|
+
async create(input) {
|
|
1890
|
+
return toFolder(await api.createMediaFolder(input.name, input.parentId));
|
|
1891
|
+
},
|
|
1892
|
+
async update(id, patch) {
|
|
1893
|
+
return toFolder(await api.renameMediaFolder(id, patch.name));
|
|
1894
|
+
},
|
|
1895
|
+
delete(id) {
|
|
1896
|
+
return api.deleteMediaFolder(id);
|
|
1897
|
+
},
|
|
1898
|
+
async move(ids, folderId) {
|
|
1899
|
+
return (await api.moveMedia(ids, folderId)).map(toAsset);
|
|
1900
|
+
}
|
|
1901
|
+
},
|
|
1902
|
+
async replace(id, file) {
|
|
1903
|
+
return toAsset(await api.replaceMedia(id, file));
|
|
1904
|
+
},
|
|
1905
|
+
async importFromUrl(url, folderId) {
|
|
1906
|
+
return toAsset(await api.importFromUrl(url, folderId));
|
|
1907
|
+
},
|
|
1908
|
+
async checkUsage(ids) {
|
|
1909
|
+
const response = await api.checkMediaUsage(ids);
|
|
1910
|
+
const mapped = {};
|
|
1911
|
+
for (const [id, info] of Object.entries(response.data)) mapped[id] = {
|
|
1912
|
+
templateCount: info.templateCount,
|
|
1913
|
+
templateNames: info.templateNames
|
|
1914
|
+
};
|
|
1915
|
+
return mapped;
|
|
1916
|
+
},
|
|
1917
|
+
async frequentlyUsed() {
|
|
1918
|
+
return (await api.getFrequentlyUsed()).map(toAsset);
|
|
1919
|
+
},
|
|
1920
|
+
async storage() {
|
|
1921
|
+
return getPlanConfig?.()?.storage ?? null;
|
|
1922
|
+
},
|
|
1923
|
+
/**
|
|
1924
|
+
* A getter, not a snapshot: plan config arrives after construction.
|
|
1925
|
+
* The editor reads this inside a computed; capturing it at setup
|
|
1926
|
+
* would pin the client pre-check to `undefined`.
|
|
1927
|
+
*/
|
|
1928
|
+
get maxFileSize() {
|
|
1929
|
+
return getPlanConfig?.()?.media?.maxFileSize;
|
|
1930
|
+
},
|
|
1931
|
+
get mimeTypes() {
|
|
1932
|
+
return mimeTypesFromPlan(getPlanConfig?.() ?? null);
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
//#endregion
|
|
1624
1937
|
//#region src/cloud/templates-provider.ts
|
|
1625
1938
|
/**
|
|
1626
1939
|
* How often a save may also record an automatic version. Cloud's storage, Cloud's
|
|
@@ -1786,8 +2099,8 @@ function createCloudRenderProvider(options) {
|
|
|
1786
2099
|
//#endregion
|
|
1787
2100
|
//#region src/cloud/version-history-provider.ts
|
|
1788
2101
|
/**
|
|
1789
|
-
* Cloud's
|
|
1790
|
-
*
|
|
2102
|
+
* Cloud's HTTP row → {@link TemplateVersion}. Copies the contract fields
|
|
2103
|
+
* and drops `templateId`.
|
|
1791
2104
|
*
|
|
1792
2105
|
* `content` is carried through deliberately — Cloud's index endpoint already
|
|
1793
2106
|
* returns it, so every listed version arrives hydrated and the editor never
|
|
@@ -1799,8 +2112,8 @@ function createCloudRenderProvider(options) {
|
|
|
1799
2112
|
function toVersion(record) {
|
|
1800
2113
|
return {
|
|
1801
2114
|
id: record.id,
|
|
1802
|
-
createdAt: record.
|
|
1803
|
-
isAutomatic: record.
|
|
2115
|
+
createdAt: record.createdAt,
|
|
2116
|
+
isAutomatic: record.isAutomatic,
|
|
1804
2117
|
content: record.content
|
|
1805
2118
|
};
|
|
1806
2119
|
}
|
|
@@ -1894,7 +2207,7 @@ function createCloudTestEmailProvider(options) {
|
|
|
1894
2207
|
await api.sendTestEmail(templateId, {
|
|
1895
2208
|
recipient,
|
|
1896
2209
|
html,
|
|
1897
|
-
|
|
2210
|
+
allowedEmails: options.allowedEmails.value,
|
|
1898
2211
|
signature
|
|
1899
2212
|
});
|
|
1900
2213
|
}
|
|
@@ -1991,7 +2304,7 @@ async function checkApiAndAuth(url, authManager) {
|
|
|
1991
2304
|
}
|
|
1992
2305
|
}
|
|
1993
2306
|
async function checkWebSocket(wsConfig) {
|
|
1994
|
-
if (!wsConfig?.host || !wsConfig?.
|
|
2307
|
+
if (!wsConfig?.host || !wsConfig?.appKey) return {
|
|
1995
2308
|
ok: false,
|
|
1996
2309
|
error: "WebSocket configuration not available"
|
|
1997
2310
|
};
|
|
@@ -1999,7 +2312,7 @@ async function checkWebSocket(wsConfig) {
|
|
|
1999
2312
|
ok: false,
|
|
2000
2313
|
error: "WebSocket not supported in this environment"
|
|
2001
2314
|
};
|
|
2002
|
-
const url = `${wsConfig.port === 443 ? "wss" : "ws"}://${wsConfig.host}:${wsConfig.port}/app/${wsConfig.
|
|
2315
|
+
const url = `${wsConfig.port === 443 ? "wss" : "ws"}://${wsConfig.host}:${wsConfig.port}/app/${wsConfig.appKey}?protocol=7&client=js&version=8.4.0-rc2&flash=false`;
|
|
2003
2316
|
return new Promise((resolve) => {
|
|
2004
2317
|
let ws = null;
|
|
2005
2318
|
const timeout = setTimeout(() => {
|
|
@@ -2056,6 +2369,6 @@ function useMcpListener(options) {
|
|
|
2056
2369
|
});
|
|
2057
2370
|
}
|
|
2058
2371
|
//#endregion
|
|
2059
|
-
export { API_ROUTES, ApiClient, AuthManager, WebSocketClient, buildUrl, createCloudCommentsProvider, createCloudRenderProvider, createCloudSavedBlocksProvider, createCloudTemplatesProvider, createCloudTestEmailProvider, createCloudVersionHistoryProvider, createSdkAuthManager, handleOperation, isFatalAuthError, performHealthCheck, resolveExportFonts, resolveWebSocketConfig, useAiChat, useAiConfig, useAiRewrite, useCollaboration, useCollaborationBroadcast, useDesignReference, useExport, useMcpListener, usePlanConfig, useTemplateScoring, useTestEmail, useWebSocket };
|
|
2372
|
+
export { API_ROUTES, ApiClient, AuthManager, MediaApiClient, WebSocketClient, buildUrl, createCloudCommentsProvider, createCloudMediaProvider, createCloudRenderProvider, createCloudSavedBlocksProvider, createCloudTemplatesProvider, createCloudTestEmailProvider, createCloudVersionHistoryProvider, createSdkAuthManager, handleOperation, isFatalAuthError, performHealthCheck, resolveExportFonts, resolveWebSocketConfig, useAiChat, useAiConfig, useAiRewrite, useCollaboration, useCollaborationBroadcast, useDesignReference, useExport, useMcpListener, usePlanConfig, useTemplateScoring, useTestEmail, useWebSocket };
|
|
2060
2373
|
|
|
2061
2374
|
//# sourceMappingURL=index.js.map
|