@weavour/editor 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +113 -2
- package/dist/classic.d.ts +6 -0
- package/dist/core.d.ts +2 -0
- package/dist/editor-blocks.d.ts +2 -0
- package/dist/editor-theme.d.ts +30 -0
- package/dist/editor.js +653 -0
- package/dist/email-document.d.ts +11 -0
- package/dist/errors.d.ts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +653 -0
- package/dist/legacy.d.ts +1 -0
- package/dist/legacy.js +653 -0
- package/dist/merge-tags.d.ts +26 -0
- package/dist/protocol.d.ts +9 -0
- package/dist/protocol.js +234 -0
- package/dist/template-preview-limits.d.ts +2 -0
- package/dist/types.d.ts +94 -0
- package/package.json +35 -4
package/dist/legacy.js
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
/*! Weavour embedding SDK v0.1.0 | MIT | https://github.com/weavour/editor-sdk */
|
|
2
|
+
"use strict";
|
|
3
|
+
(() => {
|
|
4
|
+
// packages/editor/src/merge-tags.ts
|
|
5
|
+
var defaultMergeTags = [
|
|
6
|
+
{ id: "unsubscribe_url", label: "Unsubscribe link", token: "{{unsubscribe_url}}", kind: "url" },
|
|
7
|
+
{ id: "online_version_url", label: "Online version link", token: "{{online_version_url}}", kind: "url" }
|
|
8
|
+
];
|
|
9
|
+
var control = /[\u0000-\u001f\u007f]/;
|
|
10
|
+
function validMergeToken(value) {
|
|
11
|
+
return typeof value === "string" && !!value.trim() && value.length <= 200 && !control.test(value) && !/[<>"'`&]/.test(value) && !/^[a-z][a-z\d+.-]*:/i.test(value.trimStart());
|
|
12
|
+
}
|
|
13
|
+
function mergeTagReferences(value) {
|
|
14
|
+
if (!Array.isArray(value) || value.length > 1e3) throw new Error("invalid_merge_tags");
|
|
15
|
+
return value.map((tag) => {
|
|
16
|
+
if (!tag || typeof tag !== "object" || typeof tag.id !== "string" || !/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(tag.id) || !validMergeToken(tag.token) || !["text", "url"].includes(tag.kind) || defaultMergeTags.some((builtin) => builtin.id === tag.id && tag.kind !== "url")) throw new Error("invalid_merge_tags");
|
|
17
|
+
return { id: tag.id, token: tag.token, kind: tag.kind };
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function parseMergeTags(value) {
|
|
21
|
+
if (!Array.isArray(value) || value.length > 100) throw new Error("invalid_merge_tags");
|
|
22
|
+
const refs = mergeTagReferences(value);
|
|
23
|
+
const tags = value.map((tag, index) => {
|
|
24
|
+
if (Object.keys(tag).some((key) => !["id", "label", "token", "kind", "description", "sampleValue"].includes(key)) || typeof tag.label !== "string" || !tag.label.trim() || tag.label.length > 100 || control.test(tag.label) || tag.description !== void 0 && (typeof tag.description !== "string" || tag.description.length > 500 || control.test(tag.description)) || tag.sampleValue !== void 0 && (typeof tag.sampleValue !== "string" || tag.sampleValue.length > 2048 || control.test(tag.sampleValue)) || tag.kind === "url" && tag.sampleValue && !safeMergeLink(tag.sampleValue)) throw new Error("invalid_merge_tags");
|
|
25
|
+
return { ...refs[index], label: tag.label, ...tag.description === void 0 ? {} : { description: tag.description }, ...tag.sampleValue === void 0 ? {} : { sampleValue: tag.sampleValue } };
|
|
26
|
+
});
|
|
27
|
+
const ids = /* @__PURE__ */ new Set();
|
|
28
|
+
for (const [index, tag] of tags.entries()) {
|
|
29
|
+
if (ids.has(tag.id) || tags.some((other, at) => at !== index && (other.token.includes(tag.token) || tag.token.includes(other.token)))) throw new Error("invalid_merge_tags");
|
|
30
|
+
ids.add(tag.id);
|
|
31
|
+
}
|
|
32
|
+
return tags;
|
|
33
|
+
}
|
|
34
|
+
function resolveMergeTags(saved = [], overrides = []) {
|
|
35
|
+
const resolved = new Map(defaultMergeTags.map((tag) => [tag.id, tag]));
|
|
36
|
+
for (const layer of [saved, overrides]) for (const tag of parseMergeTags(layer)) resolved.set(tag.id, tag);
|
|
37
|
+
return parseMergeTags([...resolved.values()]);
|
|
38
|
+
}
|
|
39
|
+
function safeMergeLink(value, tags = []) {
|
|
40
|
+
if (!value || value.length > 2048 || control.test(value)) return void 0;
|
|
41
|
+
if (tags.some((tag) => tag.kind === "url" && tag.token === value)) return value;
|
|
42
|
+
if (/\s|[{}<>"`]/.test(value)) return void 0;
|
|
43
|
+
try {
|
|
44
|
+
const url = new URL(value);
|
|
45
|
+
return ["https:", "http:", "mailto:", "tel:"].includes(url.protocol) && !url.username && !url.password && (["https:", "http:"].includes(url.protocol) || url.pathname) ? value : void 0;
|
|
46
|
+
} catch {
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// packages/editor/src/template-preview-limits.ts
|
|
52
|
+
var TEMPLATE_PREVIEW_LIMIT = 131072;
|
|
53
|
+
|
|
54
|
+
// packages/editor/src/email-document.ts
|
|
55
|
+
function emailDocument(value) {
|
|
56
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid_document");
|
|
57
|
+
const doc = value;
|
|
58
|
+
if (doc.schemaVersion !== 1 || typeof doc.subject !== "string" || doc.subject.length > 200 || typeof doc.preheader !== "string" || doc.preheader.length > 500 || !doc.content || typeof doc.content !== "object" || Array.isArray(doc.content)) throw new Error("invalid_document");
|
|
59
|
+
let nodes = 0;
|
|
60
|
+
const visit = (value2, depth) => {
|
|
61
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2) || depth > 50 || ++nodes > 1e4) throw new Error("invalid_document");
|
|
62
|
+
const node = value2;
|
|
63
|
+
if (typeof node.type !== "string" || !node.type || node.type.length > 100 || node.text !== void 0 && typeof node.text !== "string") throw new Error("invalid_document");
|
|
64
|
+
if (node.type === "doc") {
|
|
65
|
+
const attrs = node.attrs;
|
|
66
|
+
if (attrs?.mergeTagReferences !== void 0) mergeTagReferences(attrs.mergeTagReferences);
|
|
67
|
+
}
|
|
68
|
+
if (node.type === "mergeTag" || node.type === "unsubscribe") {
|
|
69
|
+
const attrs = node.attrs;
|
|
70
|
+
if (!attrs) throw new Error("invalid_document");
|
|
71
|
+
mergeTagReferences([{ id: attrs.id, token: attrs.token, kind: node.type === "unsubscribe" ? "url" : attrs.kind }]);
|
|
72
|
+
if (node.type === "unsubscribe" && (attrs.id !== "unsubscribe_url" || typeof attrs.label !== "string" || !attrs.label.trim() || attrs.label.length > 500)) throw new Error("invalid_document");
|
|
73
|
+
}
|
|
74
|
+
if (node.content !== void 0) {
|
|
75
|
+
if (!Array.isArray(node.content)) throw new Error("invalid_document");
|
|
76
|
+
node.content.forEach((child) => visit(child, depth + 1));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
if (doc.content.type !== "doc") throw new Error("invalid_document");
|
|
80
|
+
visit(doc.content, 0);
|
|
81
|
+
if (doc.previewHtml !== void 0 && (typeof doc.previewHtml !== "string" || doc.previewHtml.length > TEMPLATE_PREVIEW_LIMIT)) throw new Error("invalid_document");
|
|
82
|
+
return { ...doc.previewHtml === void 0 ? {} : { previewHtml: doc.previewHtml }, schemaVersion: 1, content: doc.content, subject: doc.subject, preheader: doc.preheader };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// packages/editor/src/protocol.ts
|
|
86
|
+
var MAX_MESSAGE_BYTES = 2097152;
|
|
87
|
+
function validMessage(value) {
|
|
88
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
89
|
+
let count = 0;
|
|
90
|
+
const visit = (item, depth) => {
|
|
91
|
+
if (++count > 5e4 || depth > 60) return false;
|
|
92
|
+
if (item === null || item === void 0 || typeof item === "string" || typeof item === "boolean") return true;
|
|
93
|
+
if (typeof item === "number") return Number.isFinite(item);
|
|
94
|
+
if (typeof item !== "object") return false;
|
|
95
|
+
return Object.values(item).every((child) => visit(child, depth + 1));
|
|
96
|
+
};
|
|
97
|
+
try {
|
|
98
|
+
return visit(value, 0) && JSON.stringify(value).length <= MAX_MESSAGE_BYTES;
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// packages/editor/src/errors.ts
|
|
105
|
+
var EditorError = class extends Error {
|
|
106
|
+
constructor(code, message) {
|
|
107
|
+
super(message);
|
|
108
|
+
this.code = code;
|
|
109
|
+
this.name = "EditorError";
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// packages/editor/src/core.ts
|
|
114
|
+
var mounted = /* @__PURE__ */ new WeakSet();
|
|
115
|
+
function createSDK(settings = {}) {
|
|
116
|
+
const scriptUrl2 = settings.scriptUrl;
|
|
117
|
+
const fetch = settings.fetch ?? ((...args) => globalThis.fetch(...args));
|
|
118
|
+
const defaultEditorUrl = scriptUrl2 ? new URL("/editor", scriptUrl2).href : "https://weavour.com/editor";
|
|
119
|
+
function mount(target, options) {
|
|
120
|
+
const container = typeof target === "string" ? document.querySelector(target) : target;
|
|
121
|
+
if (!(container instanceof HTMLElement)) throw new TypeError("Editor container was not found.");
|
|
122
|
+
if (mounted.has(container)) throw new Error("This container already has a Weavour editor.");
|
|
123
|
+
if (!options || typeof options !== "object") throw new TypeError("Mount options are required.");
|
|
124
|
+
if (options.demo !== void 0 && typeof options.demo !== "boolean") throw new TypeError("demo must be a boolean.");
|
|
125
|
+
const demo = options.demo === true;
|
|
126
|
+
const demoBlocks = ["text", "heading", "button", "divider", "list", "space", "menu", "unsubscribe", "section", "columns"];
|
|
127
|
+
if (demo) options = {
|
|
128
|
+
...options,
|
|
129
|
+
editorKey: "ek_public_demo",
|
|
130
|
+
userId: "demo",
|
|
131
|
+
draftId: void 0,
|
|
132
|
+
enableAutosave: false,
|
|
133
|
+
enableAi: false,
|
|
134
|
+
enableTemplates: false,
|
|
135
|
+
allowedBlocks: demoBlocks.filter((block) => !options.allowedBlocks || options.allowedBlocks.includes(block))
|
|
136
|
+
};
|
|
137
|
+
for (const blocks of [options.allowedBlocks, options.editorConfig?.allowedBlocks]) {
|
|
138
|
+
if (blocks !== void 0 && (!Array.isArray(blocks) || blocks.some((block) => typeof block !== "string"))) throw new TypeError("allowedBlocks must be an array of block names.");
|
|
139
|
+
}
|
|
140
|
+
if (options.userId === void 0 && options.organizationId !== void 0) {
|
|
141
|
+
options = { ...options, userId: options.organizationId, organizationId: void 0 };
|
|
142
|
+
}
|
|
143
|
+
for (const name of ["editorKey", "userId"]) {
|
|
144
|
+
if (typeof options[name] !== "string" || !options[name].trim() || options[name].length > 200 || /[\u0000-\u001f\u007f]/.test(options[name])) {
|
|
145
|
+
throw new TypeError(name + " must be a non-empty string.");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (options.organizationId !== void 0 && (typeof options.organizationId !== "string" || !options.organizationId.trim() || options.organizationId.length > 200 || /[\u0000-\u001f\u007f]/.test(options.organizationId))) {
|
|
149
|
+
throw new TypeError("organizationId must be a non-empty string when supplied.");
|
|
150
|
+
}
|
|
151
|
+
if (options.enableAi !== void 0 && typeof options.enableAi !== "boolean") throw new TypeError("enableAi must be a boolean.");
|
|
152
|
+
if (options.aiPaidOrigin !== void 0 && (typeof options.aiPaidOrigin !== "string" || new URL(options.aiPaidOrigin).origin !== options.aiPaidOrigin)) throw new TypeError("aiPaidOrigin must be an origin.");
|
|
153
|
+
for (const value of [options.enableTemplates, options.editorConfig?.enableTemplates]) {
|
|
154
|
+
if (value !== void 0 && typeof value !== "boolean") throw new TypeError("enableTemplates must be a boolean.");
|
|
155
|
+
}
|
|
156
|
+
for (const [name, limit] of [["subject", 200], ["preheader", 500]]) {
|
|
157
|
+
if (options[name] !== void 0 && (typeof options[name] !== "string" || options[name].length > limit)) throw new TypeError(name + " must be a string within its length limit.");
|
|
158
|
+
}
|
|
159
|
+
for (const value of [options.enableAutosave, options.editorConfig?.enableAutosave]) {
|
|
160
|
+
if (value !== void 0 && typeof value !== "boolean") throw new TypeError("enableAutosave must be a boolean.");
|
|
161
|
+
}
|
|
162
|
+
if (options.draftId !== void 0 && (typeof options.draftId !== "string" || !/^[a-f0-9-]{36}$/.test(options.draftId))) throw new TypeError("Invalid draft ID.");
|
|
163
|
+
if (options.draftId !== void 0 && (options.enableAutosave ?? options.editorConfig?.enableAutosave) === false) throw new TypeError("draftId requires autosave.");
|
|
164
|
+
if (options.emailDocument !== void 0) {
|
|
165
|
+
const full = emailDocument(options.emailDocument);
|
|
166
|
+
if (options.document !== void 0 || options.subject !== void 0 || options.preheader !== void 0) throw new TypeError("Use emailDocument or raw document/metadata, not both.");
|
|
167
|
+
options = { ...options, document: full.content, subject: full.subject, preheader: full.preheader };
|
|
168
|
+
}
|
|
169
|
+
if (options.draftId !== void 0 && (options.document !== void 0 || options.subject !== void 0 || options.preheader !== void 0)) {
|
|
170
|
+
if (settings.readiness !== "configured") throw new TypeError("draftId cannot be combined with initial content.");
|
|
171
|
+
options = { ...options, document: void 0, subject: void 0, preheader: void 0 };
|
|
172
|
+
}
|
|
173
|
+
const mergeTags = options.mergeTags ?? options.editorConfig?.mergeTags;
|
|
174
|
+
if (mergeTags !== void 0) resolveMergeTags([], mergeTags);
|
|
175
|
+
const url = new URL(options.editorUrl || defaultEditorUrl);
|
|
176
|
+
if (!["https:", "http:"].includes(url.protocol)) throw new TypeError("Invalid editor URL.");
|
|
177
|
+
if (url.username || url.password || url.protocol !== "https:" && !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) throw new TypeError("Editor URL must use HTTPS.");
|
|
178
|
+
const origin = url.origin;
|
|
179
|
+
url.searchParams.set("parentOrigin", window.location.origin);
|
|
180
|
+
const config = structuredClone({
|
|
181
|
+
demo,
|
|
182
|
+
mergeTags: mergeTags === void 0 ? void 0 : parseMergeTags(mergeTags),
|
|
183
|
+
editorKey: options.editorKey,
|
|
184
|
+
draftId: options.draftId,
|
|
185
|
+
enableAutosave: options.enableAutosave ?? options.editorConfig?.enableAutosave,
|
|
186
|
+
userId: options.userId,
|
|
187
|
+
organizationId: options.organizationId,
|
|
188
|
+
locale: options.locale || "en",
|
|
189
|
+
theme: { ...options.editorConfig?.theme, ...options.theme },
|
|
190
|
+
capabilities: { testEmail: !demo && typeof options.onTestEmail === "function", changeCallback: !demo && (typeof options.onChange === "function" || typeof options.onDocumentChange === "function") },
|
|
191
|
+
allowedBlocks: options.allowedBlocks ?? options.editorConfig?.allowedBlocks,
|
|
192
|
+
enableTemplates: options.enableTemplates ?? options.editorConfig?.enableTemplates ?? true,
|
|
193
|
+
document: options.document,
|
|
194
|
+
subject: options.subject,
|
|
195
|
+
preheader: options.preheader,
|
|
196
|
+
enableAi: (options.enableAi ?? options.editorConfig?.enableAi) !== false,
|
|
197
|
+
aiPaidOrigin: options.aiPaidOrigin
|
|
198
|
+
});
|
|
199
|
+
if (!validMessage({ config })) throw new TypeError("Editor configuration exceeds wire limits.");
|
|
200
|
+
const iframe = document.createElement("iframe");
|
|
201
|
+
iframe.title = "Weavour email editor";
|
|
202
|
+
iframe.style.width = "100%";
|
|
203
|
+
iframe.style.height = "100%";
|
|
204
|
+
iframe.style.border = "0";
|
|
205
|
+
iframe.style.display = "block";
|
|
206
|
+
iframe.style.visibility = "hidden";
|
|
207
|
+
function post(message, targetOrigin = origin) {
|
|
208
|
+
iframe.contentWindow.postMessage({ ...message, sessionId: config.sdkSessionId }, targetOrigin);
|
|
209
|
+
}
|
|
210
|
+
let resolveReady;
|
|
211
|
+
let rejectReady;
|
|
212
|
+
let readyPromise = new Promise((resolve, reject) => {
|
|
213
|
+
resolveReady = resolve;
|
|
214
|
+
rejectReady = reject;
|
|
215
|
+
});
|
|
216
|
+
void readyPromise.catch(() => {
|
|
217
|
+
});
|
|
218
|
+
let generation = 0;
|
|
219
|
+
const instanceId = crypto.randomUUID();
|
|
220
|
+
let destroyed = false;
|
|
221
|
+
let ready = false;
|
|
222
|
+
let configured = false;
|
|
223
|
+
let autosaveReady = false;
|
|
224
|
+
let autosaveEnabled = config.enableAutosave ?? true;
|
|
225
|
+
let timer;
|
|
226
|
+
const testRequests = /* @__PURE__ */ new Set();
|
|
227
|
+
const timerRequests = /* @__PURE__ */ new Map();
|
|
228
|
+
const assetRequests = /* @__PURE__ */ new Map();
|
|
229
|
+
const exports = /* @__PURE__ */ new Map();
|
|
230
|
+
const templateRequests = /* @__PURE__ */ new Map();
|
|
231
|
+
const aiRequests = /* @__PURE__ */ new Map();
|
|
232
|
+
const draftRequests = /* @__PURE__ */ new Map();
|
|
233
|
+
const flushes = /* @__PURE__ */ new Map();
|
|
234
|
+
const localScript = scriptUrl2 && ["localhost", "127.0.0.1"].includes(new URL(scriptUrl2).hostname);
|
|
235
|
+
const apiOrigin = new URL(options.apiUrl || (localScript ? "http://localhost:8787" : "https://api.weavour.com")).origin;
|
|
236
|
+
config.apiOrigin = apiOrigin;
|
|
237
|
+
if (!apiOrigin.startsWith("https://") && !["localhost", "127.0.0.1"].includes(new URL(apiOrigin).hostname)) throw new TypeError("API URL must use HTTPS.");
|
|
238
|
+
function destroy() {
|
|
239
|
+
if (destroyed) return;
|
|
240
|
+
destroyed = true;
|
|
241
|
+
rejectReady(new EditorError("destroyed", "Editor destroyed."));
|
|
242
|
+
clearTimeout(timer);
|
|
243
|
+
for (const controller of timerRequests.values()) controller.abort();
|
|
244
|
+
timerRequests.clear();
|
|
245
|
+
for (const controller of assetRequests.values()) controller.abort();
|
|
246
|
+
assetRequests.clear();
|
|
247
|
+
for (const controller of templateRequests.values()) controller.abort();
|
|
248
|
+
templateRequests.clear();
|
|
249
|
+
for (const controller of aiRequests.values()) controller.abort();
|
|
250
|
+
aiRequests.clear();
|
|
251
|
+
for (const operation of exports.values()) {
|
|
252
|
+
clearTimeout(operation.timeout);
|
|
253
|
+
operation.reject(new EditorError("destroyed", "Editor destroyed."));
|
|
254
|
+
}
|
|
255
|
+
exports.clear();
|
|
256
|
+
for (const controller of draftRequests.values()) controller.abort();
|
|
257
|
+
draftRequests.clear();
|
|
258
|
+
for (const operation of flushes.values()) {
|
|
259
|
+
clearTimeout(operation.timeout);
|
|
260
|
+
operation.reject(new EditorError("destroyed", "Editor destroyed."));
|
|
261
|
+
}
|
|
262
|
+
flushes.clear();
|
|
263
|
+
window.removeEventListener("message", receive);
|
|
264
|
+
iframe.removeEventListener("load", initialize);
|
|
265
|
+
iframe.remove();
|
|
266
|
+
mounted.delete(container);
|
|
267
|
+
}
|
|
268
|
+
function initialize() {
|
|
269
|
+
if (destroyed) return;
|
|
270
|
+
ready = false;
|
|
271
|
+
configured = false;
|
|
272
|
+
autosaveReady = false;
|
|
273
|
+
if (generation > 0) {
|
|
274
|
+
rejectReady(new EditorError("reloaded", "Editor reloaded."));
|
|
275
|
+
readyPromise = new Promise((resolve, reject) => {
|
|
276
|
+
resolveReady = resolve;
|
|
277
|
+
rejectReady = reject;
|
|
278
|
+
});
|
|
279
|
+
void readyPromise.catch(() => {
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
generation++;
|
|
283
|
+
config.sdkSessionId = instanceId + ":" + generation;
|
|
284
|
+
for (const requests of [timerRequests, assetRequests, templateRequests, aiRequests, draftRequests]) {
|
|
285
|
+
for (const controller of requests.values()) controller.abort();
|
|
286
|
+
requests.clear();
|
|
287
|
+
}
|
|
288
|
+
for (const operation of exports.values()) {
|
|
289
|
+
clearTimeout(operation.timeout);
|
|
290
|
+
operation.reject(new EditorError("reloaded", "Editor reloaded."));
|
|
291
|
+
}
|
|
292
|
+
exports.clear();
|
|
293
|
+
testRequests.clear();
|
|
294
|
+
for (const operation of flushes.values()) {
|
|
295
|
+
clearTimeout(operation.timeout);
|
|
296
|
+
operation.reject(new EditorError("reloaded", "Editor reloaded."));
|
|
297
|
+
}
|
|
298
|
+
flushes.clear();
|
|
299
|
+
clearTimeout(timer);
|
|
300
|
+
timer = setTimeout(() => {
|
|
301
|
+
const error = new EditorError("timeout", "Editor readiness timed out.");
|
|
302
|
+
rejectReady(error);
|
|
303
|
+
destroy();
|
|
304
|
+
options.onError?.(error);
|
|
305
|
+
}, 15e3);
|
|
306
|
+
post({ type: "weavour:init", version: 1, config }, origin);
|
|
307
|
+
}
|
|
308
|
+
let lastDraftId = null;
|
|
309
|
+
function receive(event) {
|
|
310
|
+
if (destroyed || event.origin !== origin || event.source !== iframe.contentWindow) return;
|
|
311
|
+
const message = event.data;
|
|
312
|
+
const receivingGeneration = generation;
|
|
313
|
+
if (!validMessage(message) || message.version !== 1) return;
|
|
314
|
+
if (settings.readiness !== "configured" && message.type !== "weavour:ready" && message.sessionId !== config.sdkSessionId) return;
|
|
315
|
+
if (demo && !["weavour:ready", "weavour:application-ready", "weavour:focus-editor", "weavour:config-error", "weavour:config-update-error"].includes(message.type)) return;
|
|
316
|
+
if (message.type === "weavour:autosave-config" && configured && typeof message.enableAutosave === "boolean") {
|
|
317
|
+
autosaveEnabled = message.enableAutosave;
|
|
318
|
+
if (!autosaveEnabled) {
|
|
319
|
+
autosaveReady = false;
|
|
320
|
+
for (const [id, operation] of flushes) {
|
|
321
|
+
clearTimeout(operation.timeout);
|
|
322
|
+
operation.reject(new EditorError("autosave_disabled", "Autosave is disabled."));
|
|
323
|
+
flushes.delete(id);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (message.type === "weavour:autosave-ready" && configured && autosaveEnabled) {
|
|
329
|
+
autosaveReady = true;
|
|
330
|
+
for (const [id, operation] of flushes) if (!operation.sent) {
|
|
331
|
+
operation.sent = true;
|
|
332
|
+
post({ type: "weavour:flush", version: 1, id }, origin);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (message.type === "weavour:draft-status" && configured && ["idle", "unsaved", "saving", "saved", "failed", "conflict", "read-only"].includes(message.status) && (message.draftId === null || typeof message.draftId === "string" && /^[a-f0-9-]{36}$/.test(message.draftId))) {
|
|
336
|
+
if (typeof options.onSaveStatusChange === "function") options.onSaveStatusChange({ status: message.status, draftId: message.draftId, revision: message.revision ?? null });
|
|
337
|
+
if (message.draftId !== lastDraftId) {
|
|
338
|
+
lastDraftId = message.draftId;
|
|
339
|
+
if (typeof options.onDraftChange === "function") options.onDraftChange({ draftId: message.draftId, revision: message.revision ?? null });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (message.type === "weavour:flush-result" && typeof message.id === "string" && flushes.has(message.id)) {
|
|
343
|
+
const operation = flushes.get(message.id);
|
|
344
|
+
flushes.delete(message.id);
|
|
345
|
+
clearTimeout(operation.timeout);
|
|
346
|
+
message.error ? operation.reject(new Error(message.error)) : operation.resolve(message.result);
|
|
347
|
+
}
|
|
348
|
+
if (message.type === "weavour:draft-cancel" && typeof message.id === "string") draftRequests.get(message.id)?.abort();
|
|
349
|
+
if (message.type === "weavour:draft-request" && configured && typeof message.id === "string" && !draftRequests.has(message.id)) {
|
|
350
|
+
const path = message.path;
|
|
351
|
+
if (typeof path !== "string" || !/^drafts(?:\/[a-f0-9-]{36}(?:\/versions(?:\/[1-9]\d*)?|\/restore)?)?$/.test(path) || draftRequests.size >= 8) return;
|
|
352
|
+
const permitted = message.method === "GET" && !path.endsWith("/restore") || message.method === "POST" && (path === "drafts" || /\/(?:versions|restore)$/.test(path)) || message.method === "PATCH" && /^drafts\/[a-f0-9-]{36}$/.test(path);
|
|
353
|
+
if (!permitted) return;
|
|
354
|
+
if (!autosaveEnabled) {
|
|
355
|
+
post({ type: "weavour:draft-result", version: 1, id: message.id, error: "drafts_disabled", status: 403 }, origin);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const controller = new AbortController();
|
|
359
|
+
draftRequests.set(message.id, controller);
|
|
360
|
+
const timeout = setTimeout(() => controller.abort(), 2e4);
|
|
361
|
+
const endpoint = new URL(apiOrigin + "/editors/" + encodeURIComponent(config.editorKey) + "/" + path);
|
|
362
|
+
endpoint.searchParams.set("userId", config.userId);
|
|
363
|
+
if (config.organizationId !== void 0) endpoint.searchParams.set("organizationId", config.organizationId);
|
|
364
|
+
if (message.query && typeof message.query.cursor === "string") endpoint.searchParams.set("cursor", message.query.cursor);
|
|
365
|
+
fetch(endpoint, { method: message.method, credentials: "omit", signal: controller.signal, headers: message.body === void 0 ? {} : { "Content-Type": "application/json" }, ...message.body === void 0 ? {} : { body: JSON.stringify(message.body) } }).then(async (response) => {
|
|
366
|
+
const result = await response.json();
|
|
367
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:draft-result", version: 1, id: message.id, ...response.ok ? { result } : { error: result.error || "drafts_unavailable", status: response.status, retryAfter: Number(response.headers.get("Retry-After")) || 0 } }, origin);
|
|
368
|
+
}).catch(() => {
|
|
369
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:draft-result", version: 1, id: message.id, error: "drafts_unavailable", status: 0 }, origin);
|
|
370
|
+
}).finally(() => {
|
|
371
|
+
clearTimeout(timeout);
|
|
372
|
+
if (draftRequests.get(message.id) === controller) draftRequests.delete(message.id);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
if (message.type === "weavour:document-change" && configured && message.document && typeof message.document === "object" && typeof options.onChange === "function") {
|
|
376
|
+
options.onChange(message.document);
|
|
377
|
+
}
|
|
378
|
+
if (message.type === "weavour:document-change" && configured && message.emailDocument && typeof options.onDocumentChange === "function") {
|
|
379
|
+
try {
|
|
380
|
+
options.onDocumentChange(emailDocument(message.emailDocument));
|
|
381
|
+
} catch (error) {
|
|
382
|
+
options.onError?.(new EditorError("invalid_config", "Invalid document change payload."));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (message.type === "weavour:focus-editor" && ready) iframe.scrollIntoView({ block: "center", behavior: "auto" });
|
|
386
|
+
if (message.type === "weavour:config-update-error") {
|
|
387
|
+
if (typeof options.onError === "function") options.onError(new EditorError("invalid_config", "Invalid merge tags. The current editor configuration was preserved."));
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (message.type === "weavour:config-error") {
|
|
391
|
+
destroy();
|
|
392
|
+
if (typeof options.onError === "function") options.onError(new EditorError("config_failed", "Unable to load saved editor configuration."));
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (message.type === "weavour:ready") configured = true;
|
|
396
|
+
if (message.type === "weavour:ready" && settings.readiness !== "configured") {
|
|
397
|
+
if (message.capabilities?.applicationReady !== true) {
|
|
398
|
+
const error = new EditorError("incompatible_editor", "This iframe does not support application readiness. Upgrade the hosted editor.");
|
|
399
|
+
rejectReady(error);
|
|
400
|
+
destroy();
|
|
401
|
+
options.onError?.(error);
|
|
402
|
+
}
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if ((message.type === "weavour:application-ready" || message.type === "weavour:ready" && settings.readiness === "configured") && !ready) {
|
|
406
|
+
autosaveEnabled = !demo && (typeof message.enableAutosave === "boolean" ? message.enableAutosave : config.enableAutosave ?? true);
|
|
407
|
+
configured = true;
|
|
408
|
+
ready = true;
|
|
409
|
+
iframe.style.visibility = "visible";
|
|
410
|
+
clearTimeout(timer);
|
|
411
|
+
if (config.allowedBlocks !== void 0) post({ type: "weavour:update-config", version: 1, config: { allowedBlocks: config.allowedBlocks } }, origin);
|
|
412
|
+
resolveReady();
|
|
413
|
+
if (typeof options.onReady === "function") options.onReady();
|
|
414
|
+
}
|
|
415
|
+
if (message.type === "weavour:ai-request" && configured && typeof message.id === "string" && !aiRequests.has(message.id)) {
|
|
416
|
+
if (!/^ai\/(?:capabilities|quotes|operations(?:\/[a-f0-9-]{36}(?:\/(?:run|cancel|accept|image-preview))?)?)$/.test(message.path) || !["GET", "POST"].includes(message.method) || aiRequests.size >= 8) return;
|
|
417
|
+
if (!config.enableAi) {
|
|
418
|
+
post({ type: "weavour:ai-result", version: 1, id: message.id, error: "ai_disabled" }, origin);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const controller = new AbortController();
|
|
422
|
+
aiRequests.set(message.id, controller);
|
|
423
|
+
const timeout = setTimeout(() => controller.abort(), message.path.endsWith("/run") ? 21e4 : 42e3);
|
|
424
|
+
const endpoint = new URL(apiOrigin + "/editors/" + encodeURIComponent(config.editorKey) + "/" + message.path);
|
|
425
|
+
{
|
|
426
|
+
endpoint.searchParams.set("userId", config.userId);
|
|
427
|
+
if (config.organizationId !== void 0) endpoint.searchParams.set("organizationId", config.organizationId);
|
|
428
|
+
}
|
|
429
|
+
if (config.aiPaidOrigin) endpoint.searchParams.set("localPaidOrigin", config.aiPaidOrigin);
|
|
430
|
+
fetch(endpoint.href, {
|
|
431
|
+
method: message.method,
|
|
432
|
+
credentials: "omit",
|
|
433
|
+
signal: controller.signal,
|
|
434
|
+
headers: message.body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
435
|
+
...message.body === void 0 ? {} : { body: JSON.stringify(message.body) }
|
|
436
|
+
}).then(async (response) => {
|
|
437
|
+
const result = await response.json();
|
|
438
|
+
if (!response.ok) throw new Error(result.error || "ai_request_failed");
|
|
439
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:ai-result", version: 1, id: message.id, result }, origin);
|
|
440
|
+
}).catch((error) => {
|
|
441
|
+
const safe = ["ai_source_unavailable", "ai_image_unavailable", "ai_invalid_output", "invalid_ai_request", "ai_insufficient_credits", "ai_disabled", "ai_scope_disabled", "ai_paid_access_required", "ai_source_changed", "ai_quote_expired", "ai_customer_key_unavailable", "ai_provider_outcome_uncertain"].includes(error.message) ? error.message : "ai_request_failed";
|
|
442
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:ai-result", version: 1, id: message.id, error: safe }, origin);
|
|
443
|
+
}).finally(() => {
|
|
444
|
+
clearTimeout(timeout);
|
|
445
|
+
if (aiRequests.get(message.id) === controller) aiRequests.delete(message.id);
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
if (message.type === "weavour:template-cancel" && typeof message.id === "string") {
|
|
449
|
+
templateRequests.get(message.id)?.abort();
|
|
450
|
+
}
|
|
451
|
+
if (message.type === "weavour:template-request" && configured && typeof message.id === "string" && !templateRequests.has(message.id)) {
|
|
452
|
+
if (!/^templates(?:\/[a-zA-Z0-9-]+)?$/.test(message.path) || !["GET", "POST", "PATCH", "DELETE"].includes(message.method) || templateRequests.size >= 8) return;
|
|
453
|
+
if (config.enableTemplates === false) {
|
|
454
|
+
post({ type: "weavour:template-result", version: 1, id: message.id, error: "templates_disabled" }, origin);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const controller = new AbortController();
|
|
458
|
+
templateRequests.set(message.id, controller);
|
|
459
|
+
const timeout = setTimeout(() => controller.abort(), 2e4);
|
|
460
|
+
const endpoint = new URL(apiOrigin + "/editors/" + encodeURIComponent(config.editorKey) + "/" + message.path);
|
|
461
|
+
endpoint.searchParams.set("userId", config.userId);
|
|
462
|
+
if (config.organizationId !== void 0) endpoint.searchParams.set("organizationId", config.organizationId);
|
|
463
|
+
for (const key of ["search", "offset", "collection", "tag"]) if (message.query && message.query[key] !== void 0) endpoint.searchParams.set(key, String(message.query[key]));
|
|
464
|
+
fetch(endpoint.href, {
|
|
465
|
+
method: message.method,
|
|
466
|
+
credentials: "omit",
|
|
467
|
+
signal: controller.signal,
|
|
468
|
+
headers: message.body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
469
|
+
...message.body === void 0 ? {} : { body: JSON.stringify(message.body) }
|
|
470
|
+
}).then(async (response) => {
|
|
471
|
+
const result = await response.json();
|
|
472
|
+
if (!response.ok) throw new Error(result.error || "templates_unavailable");
|
|
473
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:template-result", version: 1, id: message.id, result }, origin);
|
|
474
|
+
}).catch((error) => {
|
|
475
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:template-result", version: 1, id: message.id, error: error.message || "templates_unavailable" }, origin);
|
|
476
|
+
}).finally(() => {
|
|
477
|
+
clearTimeout(timeout);
|
|
478
|
+
if (templateRequests.get(message.id) === controller) templateRequests.delete(message.id);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
if (message.type === "weavour:asset-request" && configured && typeof message.id === "string" && !assetRequests.has(message.id)) {
|
|
482
|
+
const routes = /^(assets|asset-folders|asset-imports)(?:\/[a-zA-Z0-9-]+(?:\/(?:complete|input))?)?$/;
|
|
483
|
+
if (!routes.test(message.path) || !["GET", "POST", "PATCH", "DELETE"].includes(message.method) || assetRequests.size >= 16) return;
|
|
484
|
+
const controller = new AbortController();
|
|
485
|
+
assetRequests.set(message.id, controller);
|
|
486
|
+
let timeout = setTimeout(() => controller.abort(), 45e3);
|
|
487
|
+
const call = async (path, method, body, query) => {
|
|
488
|
+
const endpoint = new URL(apiOrigin + "/editors/" + encodeURIComponent(config.editorKey) + "/" + path);
|
|
489
|
+
endpoint.searchParams.set("userId", config.userId);
|
|
490
|
+
if (config.organizationId !== void 0) endpoint.searchParams.set("organizationId", config.organizationId);
|
|
491
|
+
for (const key of ["search", "folderId", "archived", "offset", "page", "locale"]) if (query && query[key] !== void 0) endpoint.searchParams.set(key, String(query[key]));
|
|
492
|
+
const response = await fetch(endpoint.href, {
|
|
493
|
+
method,
|
|
494
|
+
credentials: "omit",
|
|
495
|
+
signal: controller.signal,
|
|
496
|
+
headers: body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
497
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
498
|
+
});
|
|
499
|
+
const result = await response.json();
|
|
500
|
+
if (!response.ok) {
|
|
501
|
+
throw new Error(result.error || "library_unavailable");
|
|
502
|
+
}
|
|
503
|
+
return result;
|
|
504
|
+
};
|
|
505
|
+
Promise.resolve().then(async () => {
|
|
506
|
+
if (message.file !== void 0) throw new Error("iframe_upgrade_required");
|
|
507
|
+
return call(message.path, message.method, message.body, message.query);
|
|
508
|
+
}).then((result) => {
|
|
509
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:asset-result", version: 1, id: message.id, result }, origin);
|
|
510
|
+
}).catch((failure) => {
|
|
511
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:asset-result", version: 1, id: message.id, error: failure.message || "library_unavailable" }, origin);
|
|
512
|
+
}).finally(() => {
|
|
513
|
+
clearTimeout(timeout);
|
|
514
|
+
if (assetRequests.get(message.id) === controller) assetRequests.delete(message.id);
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
if (message.type === "weavour:timer-publish" && configured && typeof message.id === "string" && message.settings && typeof message.settings === "object" && !timerRequests.has(message.id)) {
|
|
518
|
+
if (timerRequests.size >= 10) return;
|
|
519
|
+
const controller = new AbortController();
|
|
520
|
+
timerRequests.set(message.id, controller);
|
|
521
|
+
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
522
|
+
fetch(apiOrigin + "/editors/" + encodeURIComponent(config.editorKey) + "/timers/versions", {
|
|
523
|
+
method: "POST",
|
|
524
|
+
credentials: "omit",
|
|
525
|
+
signal: controller.signal,
|
|
526
|
+
headers: { "Content-Type": "application/json" },
|
|
527
|
+
body: JSON.stringify({ settings: message.settings, userId: config.userId, organizationId: config.organizationId })
|
|
528
|
+
}).then(async (response) => {
|
|
529
|
+
const result = await response.json();
|
|
530
|
+
if (!response.ok) throw new Error(result.error || "Timer publishing failed.");
|
|
531
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:timer-result", version: 1, id: message.id, result }, origin);
|
|
532
|
+
}).catch((error) => {
|
|
533
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:timer-result", version: 1, id: message.id, error: error.message || "Retry timer publishing." }, origin);
|
|
534
|
+
}).finally(() => {
|
|
535
|
+
clearTimeout(timeout);
|
|
536
|
+
if (timerRequests.get(message.id) === controller) timerRequests.delete(message.id);
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
if (message.type === "weavour:export-result" && typeof message.id === "string" && exports.has(message.id)) {
|
|
540
|
+
const operation = exports.get(message.id);
|
|
541
|
+
exports.delete(message.id);
|
|
542
|
+
clearTimeout(operation.timeout);
|
|
543
|
+
message.error ? operation.reject(new Error(message.error)) : operation.resolve(message.result);
|
|
544
|
+
}
|
|
545
|
+
if (message.type === "weavour:test-email" && configured && typeof message.id === "string" && message.payload && typeof message.payload === "object" && ["to", "subject", "html", "text"].every((key) => typeof message.payload[key] === "string") && !testRequests.has(message.id)) {
|
|
546
|
+
testRequests.add(message.id);
|
|
547
|
+
Promise.resolve().then(() => {
|
|
548
|
+
if (typeof options.onTestEmail !== "function") throw new Error("Test email delivery is unavailable.");
|
|
549
|
+
return options.onTestEmail(message.payload);
|
|
550
|
+
}).then(() => {
|
|
551
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:test-result", version: 1, id: message.id, ok: true }, origin);
|
|
552
|
+
}).catch(() => {
|
|
553
|
+
if (!destroyed && receivingGeneration === generation) post({ type: "weavour:test-result", version: 1, id: message.id, ok: false }, origin);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
window.addEventListener("message", receive);
|
|
558
|
+
iframe.addEventListener("load", initialize);
|
|
559
|
+
iframe.src = url.href;
|
|
560
|
+
timer = setTimeout(() => {
|
|
561
|
+
const error = new EditorError("timeout", "Editor loading timed out.");
|
|
562
|
+
rejectReady(error);
|
|
563
|
+
destroy();
|
|
564
|
+
options.onError?.(error);
|
|
565
|
+
}, 15e3);
|
|
566
|
+
container.appendChild(iframe);
|
|
567
|
+
mounted.add(container);
|
|
568
|
+
function updateConfig(next) {
|
|
569
|
+
if (destroyed) throw new EditorError("destroyed", "This editor has been destroyed.");
|
|
570
|
+
for (const key of settings.readiness === "configured" ? [] : ["editorKey", "userId", "organizationId", "document", "emailDocument", "draftId"]) if (next && Object.hasOwn(next, key)) throw new TypeError(key + " is mount-only.");
|
|
571
|
+
if (!next || typeof next !== "object") throw new TypeError("Configuration is required.");
|
|
572
|
+
if (demo) next = {
|
|
573
|
+
...next,
|
|
574
|
+
enableAutosave: false,
|
|
575
|
+
enableAi: false,
|
|
576
|
+
enableTemplates: false,
|
|
577
|
+
allowedBlocks: demoBlocks.filter((block) => !(next.allowedBlocks ?? config.allowedBlocks) || (next.allowedBlocks ?? config.allowedBlocks).includes(block))
|
|
578
|
+
};
|
|
579
|
+
if (Object.hasOwn(next, "mergeTags")) {
|
|
580
|
+
if (next.mergeTags !== void 0) resolveMergeTags([], next.mergeTags);
|
|
581
|
+
config.mergeTags = next.mergeTags === void 0 ? void 0 : parseMergeTags(next.mergeTags);
|
|
582
|
+
}
|
|
583
|
+
if (next.enableAutosave !== void 0 && typeof next.enableAutosave !== "boolean") throw new TypeError("enableAutosave must be a boolean.");
|
|
584
|
+
if (next.enableAutosave !== void 0) {
|
|
585
|
+
config.enableAutosave = next.enableAutosave;
|
|
586
|
+
if (!config.enableAutosave) {
|
|
587
|
+
autosaveEnabled = false;
|
|
588
|
+
autosaveReady = false;
|
|
589
|
+
for (const [id, operation] of flushes) {
|
|
590
|
+
clearTimeout(operation.timeout);
|
|
591
|
+
operation.reject(new EditorError("autosave_disabled", "Autosave is disabled."));
|
|
592
|
+
flushes.delete(id);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (next.enableAi !== void 0 && typeof next.enableAi !== "boolean") throw new TypeError("enableAi must be a boolean.");
|
|
597
|
+
if (next.enableAi !== void 0) config.enableAi = next.enableAi;
|
|
598
|
+
if (Object.hasOwn(next, "aiPaidOrigin")) {
|
|
599
|
+
if (next.aiPaidOrigin !== void 0 && (typeof next.aiPaidOrigin !== "string" || new URL(next.aiPaidOrigin).origin !== next.aiPaidOrigin)) throw new TypeError("aiPaidOrigin must be an origin.");
|
|
600
|
+
config.aiPaidOrigin = next.aiPaidOrigin;
|
|
601
|
+
}
|
|
602
|
+
if (next.enableTemplates !== void 0 && typeof next.enableTemplates !== "boolean") throw new TypeError("enableTemplates must be a boolean.");
|
|
603
|
+
if (next.allowedBlocks !== void 0 && (!Array.isArray(next.allowedBlocks) || next.allowedBlocks.some((block) => typeof block !== "string"))) throw new TypeError("allowedBlocks must be an array of block names.");
|
|
604
|
+
if (next.enableTemplates !== void 0) config.enableTemplates = next.enableTemplates;
|
|
605
|
+
if (next.allowedBlocks !== void 0) config.allowedBlocks = [...next.allowedBlocks];
|
|
606
|
+
if (next.theme) {
|
|
607
|
+
const switchingMode = (next.theme.mode === "light" || next.theme.mode === "dark") && next.theme.mode !== (config.theme.mode || "light");
|
|
608
|
+
if (switchingMode) {
|
|
609
|
+
const { cornerRadius, density, fontFamily } = config.theme;
|
|
610
|
+
config.theme = { cornerRadius, density, fontFamily };
|
|
611
|
+
}
|
|
612
|
+
config.theme = { ...config.theme, ...structuredClone(next.theme) };
|
|
613
|
+
}
|
|
614
|
+
if (next.locale) config.locale = next.locale;
|
|
615
|
+
if (ready) post({ type: "weavour:update-config", version: 1, config: { mergeTags: config.mergeTags, theme: config.theme, locale: config.locale, enableAi: config.enableAi, enableTemplates: config.enableTemplates, enableAutosave: config.enableAutosave, allowedBlocks: config.allowedBlocks } }, origin);
|
|
616
|
+
}
|
|
617
|
+
function exportHTML(previewOnly = false) {
|
|
618
|
+
if (demo) return Promise.reject(new EditorError("demo_restricted", "Export is disabled in demo mode."));
|
|
619
|
+
if (destroyed || !ready) return Promise.reject(new EditorError("not_ready", "Editor is not ready."));
|
|
620
|
+
return new Promise((resolve, reject) => {
|
|
621
|
+
const id = crypto.randomUUID();
|
|
622
|
+
const timeout = setTimeout(() => {
|
|
623
|
+
exports.delete(id);
|
|
624
|
+
reject(new EditorError("timeout", "Export timed out."));
|
|
625
|
+
}, 2e4);
|
|
626
|
+
exports.set(id, { resolve, reject, timeout });
|
|
627
|
+
post({ type: "weavour:export", version: 1, id, ...previewOnly ? { previewOnly: true } : {} }, origin);
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
function flushAutosave() {
|
|
631
|
+
if (destroyed || !ready) return Promise.reject(new EditorError("not_ready", "Editor is not ready."));
|
|
632
|
+
if (!autosaveEnabled) return Promise.reject(new EditorError("autosave_disabled", "Autosave is disabled."));
|
|
633
|
+
return new Promise((resolve, reject) => {
|
|
634
|
+
const id = crypto.randomUUID();
|
|
635
|
+
const timeout = setTimeout(() => {
|
|
636
|
+
flushes.delete(id);
|
|
637
|
+
reject(new EditorError("timeout", "Autosave flush timed out."));
|
|
638
|
+
}, 45e3);
|
|
639
|
+
flushes.set(id, { resolve, reject, timeout, sent: autosaveReady });
|
|
640
|
+
if (autosaveReady) post({ type: "weavour:flush", version: 1, id }, origin);
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
return Object.freeze({ get ready() {
|
|
644
|
+
return readyPromise;
|
|
645
|
+
}, destroy, updateConfig, flushAutosave, exportHTML: () => exportHTML(), previewTemplate: () => exportHTML(true) });
|
|
646
|
+
}
|
|
647
|
+
return Object.freeze({ mount });
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// packages/editor/src/legacy.ts
|
|
651
|
+
var scriptUrl = document.currentScript?.src;
|
|
652
|
+
window.Weavour = createSDK({ scriptUrl, readiness: "configured" });
|
|
653
|
+
})();
|