@wikex/admin-kit 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/admin/before-login.d.ts +5 -0
  3. package/dist/admin/before-login.js +26 -0
  4. package/dist/admin/brand.d.ts +19 -0
  5. package/dist/admin/brand.js +15 -0
  6. package/dist/admin/header.d.ts +5 -0
  7. package/dist/admin/header.js +87 -0
  8. package/dist/admin/logout-button.d.ts +8 -0
  9. package/dist/admin/logout-button.js +7 -0
  10. package/dist/admin/nav.d.ts +19 -0
  11. package/dist/admin/nav.js +285 -0
  12. package/dist/admin/theme-toggle.d.ts +5 -0
  13. package/dist/admin/theme-toggle.js +7 -0
  14. package/dist/admin/view-site.d.ts +5 -0
  15. package/dist/admin/view-site.js +6 -0
  16. package/dist/chunk-7FEACJUN.js +151 -0
  17. package/dist/chunk-BUD6BMFV.js +117 -0
  18. package/dist/chunk-GGSOO4FQ.js +27 -0
  19. package/dist/chunk-IL2CUCDG.js +2062 -0
  20. package/dist/chunk-JJLHSZ3T.js +40 -0
  21. package/dist/chunk-LC2CP5SR.js +13 -0
  22. package/dist/chunk-LP22Y3NO.js +61 -0
  23. package/dist/chunk-MDMKKWPU.js +12 -0
  24. package/dist/chunk-W4ZKDOWJ.js +37 -0
  25. package/dist/chunk-XGKGKCQ6.js +50 -0
  26. package/dist/chunk-YMBJCWN3.js +73 -0
  27. package/dist/index.d.ts +26 -0
  28. package/dist/index.js +44 -0
  29. package/dist/live-preview.d.ts +50 -0
  30. package/dist/live-preview.js +19 -0
  31. package/dist/project.d.ts +34 -0
  32. package/dist/project.js +8 -0
  33. package/dist/rich-text-client.d.ts +5 -0
  34. package/dist/rich-text-client.js +165 -0
  35. package/dist/rich-text.d.ts +5 -0
  36. package/dist/rich-text.js +6 -0
  37. package/dist/runtimeConfig-BwjQepcW.d.ts +30 -0
  38. package/dist/starter-client.d.ts +15 -0
  39. package/dist/starter-client.js +25 -0
  40. package/dist/starter.d.ts +40 -0
  41. package/dist/starter.js +62 -0
  42. package/dist/text-style-runtime.d.ts +5 -0
  43. package/dist/text-style-runtime.js +8 -0
  44. package/dist/text-styles.d.ts +86 -0
  45. package/dist/text-styles.js +22 -0
  46. package/package.json +23 -19
  47. package/src/TextStyleRuntime.tsx +0 -90
  48. package/src/admin/BeforeLogin.tsx +0 -19
  49. package/src/admin/Brand.tsx +0 -74
  50. package/src/admin/Header.tsx +0 -87
  51. package/src/admin/LogoutButton.tsx +0 -41
  52. package/src/admin/Nav.tsx +0 -35
  53. package/src/admin/NavClient.tsx +0 -288
  54. package/src/admin/ThemeToggle.tsx +0 -31
  55. package/src/admin/ViewSite.tsx +0 -11
  56. package/src/admin/useAdminBrand.ts +0 -81
  57. package/src/adminExperience.ts +0 -117
  58. package/src/index.ts +0 -11
  59. package/src/live-preview/GlobalInlineInspector.tsx +0 -334
  60. package/src/live-preview/LivePreviewEditor.tsx +0 -2088
  61. package/src/live-preview/TextStyleInspector.tsx +0 -289
  62. package/src/live-preview/index.ts +0 -23
  63. package/src/live-preview/runtimeConfig.ts +0 -52
  64. package/src/plugin.ts +0 -70
  65. package/src/project.ts +0 -82
  66. package/src/rich-text/client.tsx +0 -202
  67. package/src/rich-text/index.ts +0 -10
  68. package/src/starter/client.tsx +0 -22
  69. package/src/starter.ts +0 -95
  70. package/src/textStyles.ts +0 -161
@@ -0,0 +1,2062 @@
1
+ import {
2
+ EDITABLE_INTERACTIVE_SELECTOR,
3
+ EDITABLE_PREVIEW_SELECTOR,
4
+ TEXT_FONT_OPTIONS,
5
+ TEXT_LINE_HEIGHT_OPTIONS,
6
+ getEditableElementStyleKey,
7
+ getTextStyleCSS,
8
+ isSyntheticElementStyleKey,
9
+ normalizeTextStyleMap,
10
+ normalizeTextStyleValue
11
+ } from "./chunk-BUD6BMFV.js";
12
+
13
+ // src/live-preview/LivePreviewEditor.tsx
14
+ import { createPortal } from "react-dom";
15
+ import {
16
+ useCallback,
17
+ useEffect as useEffect3,
18
+ useRef as useRef2,
19
+ useState as useState3
20
+ } from "react";
21
+
22
+ // src/live-preview/GlobalInlineInspector.tsx
23
+ import { useEffect, useMemo, useState } from "react";
24
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
25
+ var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
26
+ var readValueAtPath = (source, fieldPath) => {
27
+ return fieldPath.split(".").reduce((current, segment) => {
28
+ if (Array.isArray(current)) return current[Number(segment)];
29
+ if (isRecord(current)) return current[segment];
30
+ return void 0;
31
+ }, source);
32
+ };
33
+ var setValueAtPath = (source, segments, value) => {
34
+ if (!segments.length) return value;
35
+ const [segment, ...remainingSegments] = segments;
36
+ const arrayIndex = Number(segment);
37
+ if (Number.isInteger(arrayIndex) && String(arrayIndex) === segment) {
38
+ const nextArray = Array.isArray(source) ? [...source] : [];
39
+ nextArray[arrayIndex] = setValueAtPath(nextArray[arrayIndex], remainingSegments, value);
40
+ return nextArray;
41
+ }
42
+ const nextRecord = isRecord(source) ? { ...source } : {};
43
+ nextRecord[segment] = setValueAtPath(nextRecord[segment], remainingSegments, value);
44
+ return nextRecord;
45
+ };
46
+ var getErrorMessage = async (response) => {
47
+ try {
48
+ const body = await response.json();
49
+ return body.errors?.[0]?.message || body.message;
50
+ } catch {
51
+ return void 0;
52
+ }
53
+ };
54
+ var readGlobal = async (scope, signal) => {
55
+ const response = await fetch(`/api/globals/${scope}?depth=0&draft=true`, {
56
+ credentials: "include",
57
+ signal
58
+ });
59
+ if (!response.ok) {
60
+ throw new Error(await getErrorMessage(response) || `Kh\xF4ng th\u1EC3 \u0111\u1ECDc ${scope}.`);
61
+ }
62
+ return await response.json();
63
+ };
64
+ var updateGlobalFields = async (scope, updates) => {
65
+ const currentGlobal = await readGlobal(scope);
66
+ const patch = { _status: "draft" };
67
+ for (const { path, value } of updates) {
68
+ const [rootField, ...remainingSegments] = path.split(".");
69
+ const currentRoot = Object.hasOwn(patch, rootField) ? patch[rootField] : currentGlobal[rootField];
70
+ patch[rootField] = setValueAtPath(currentRoot, remainingSegments, value);
71
+ }
72
+ const response = await fetch(`/api/globals/${scope}?depth=0&draft=true&autosave=true`, {
73
+ body: JSON.stringify(patch),
74
+ credentials: "include",
75
+ headers: { "Content-Type": "application/json" },
76
+ method: "POST"
77
+ });
78
+ if (!response.ok) {
79
+ throw new Error(await getErrorMessage(response) || `Kh\xF4ng th\u1EC3 c\u1EADp nh\u1EADt ${scope}.`);
80
+ }
81
+ };
82
+ var getLinkBasePath = (fieldPath) => {
83
+ if (fieldPath.endsWith(".label")) return fieldPath.slice(0, -".label".length);
84
+ if (fieldPath === "announcement.text") return "announcement";
85
+ return void 0;
86
+ };
87
+ var normalizeInspectorColor = (value) => typeof value === "string" && /^#[0-9A-F]{6}$/i.test(value) ? value.toUpperCase() : "";
88
+ var GlobalInlineInspector = ({
89
+ onApply,
90
+ selection
91
+ }) => {
92
+ const linkBasePath = useMemo(() => getLinkBasePath(selection.fieldPath), [selection.fieldPath]);
93
+ const [values, setValues] = useState({
94
+ backgroundColor: "",
95
+ text: selection.text,
96
+ textColor: "",
97
+ url: selection.href || ""
98
+ });
99
+ const [status, setStatus] = useState(
100
+ "loading"
101
+ );
102
+ const [linkContext, setLinkContext] = useState({ initialURL: selection.href || "", type: "" });
103
+ const [message, setMessage] = useState("\u0110ang t\u1EA3i thu\u1ED9c t\xEDnh\u2026");
104
+ useEffect(() => {
105
+ const controller = new AbortController();
106
+ setStatus("loading");
107
+ setMessage("\u0110ang t\u1EA3i thu\u1ED9c t\xEDnh\u2026");
108
+ void readGlobal(selection.scope, controller.signal).then((global) => {
109
+ const storedURL = linkBasePath ? String(readValueAtPath(global, `${linkBasePath}.url`) ?? selection.href ?? "") : "";
110
+ setValues({
111
+ backgroundColor: linkBasePath ? normalizeInspectorColor(readValueAtPath(global, `${linkBasePath}.backgroundColor`)) : "",
112
+ text: String(readValueAtPath(global, selection.fieldPath) ?? selection.text) || selection.text,
113
+ textColor: linkBasePath ? normalizeInspectorColor(readValueAtPath(global, `${linkBasePath}.textColor`)) : "",
114
+ url: storedURL
115
+ });
116
+ setLinkContext({
117
+ initialURL: storedURL,
118
+ type: linkBasePath ? String(readValueAtPath(global, `${linkBasePath}.type`) ?? "") : ""
119
+ });
120
+ setStatus("idle");
121
+ setMessage("");
122
+ }).catch((error) => {
123
+ if (controller.signal.aborted) return;
124
+ setStatus("error");
125
+ setMessage(error instanceof Error ? error.message : "Kh\xF4ng th\u1EC3 t\u1EA3i thu\u1ED9c t\xEDnh.");
126
+ });
127
+ return () => controller.abort();
128
+ }, [linkBasePath, selection]);
129
+ const save = async () => {
130
+ const textColor = normalizeInspectorColor(values.textColor);
131
+ const backgroundColor = normalizeInspectorColor(values.backgroundColor);
132
+ if (values.textColor && !textColor || values.backgroundColor && !backgroundColor) {
133
+ setStatus("error");
134
+ setMessage("M\xE0u t\xF9y ch\u1EC9nh c\u1EA7n d\xF9ng m\xE3 HEX \u0111\u1EA7y \u0111\u1EE7, v\xED d\u1EE5 #172019.");
135
+ return;
136
+ }
137
+ setStatus("saving");
138
+ setMessage("\u0110ang l\u01B0u\u2026");
139
+ const savedValues = { ...values, backgroundColor, textColor };
140
+ const updates = [{ path: selection.fieldPath, value: values.text }];
141
+ if (selection.supportsLink && linkBasePath) {
142
+ updates.push(
143
+ { path: `${linkBasePath}.textColor`, value: textColor || null },
144
+ { path: `${linkBasePath}.backgroundColor`, value: backgroundColor || null }
145
+ );
146
+ if (!linkBasePath.includes(".link") || linkContext.type === "custom") {
147
+ updates.push({ path: `${linkBasePath}.url`, value: values.url });
148
+ } else if (values.url !== linkContext.initialURL) {
149
+ updates.push(
150
+ { path: `${linkBasePath}.type`, value: "custom" },
151
+ { path: `${linkBasePath}.url`, value: values.url }
152
+ );
153
+ }
154
+ }
155
+ try {
156
+ await updateGlobalFields(selection.scope, updates);
157
+ setValues(savedValues);
158
+ onApply(savedValues);
159
+ setStatus("success");
160
+ setMessage("\u0110\xE3 l\u01B0u v\xE0o n\u1ED9i dung d\xF9ng chung.");
161
+ } catch (error) {
162
+ setStatus("error");
163
+ setMessage(error instanceof Error ? error.message : "Kh\xF4ng th\u1EC3 l\u01B0u thay \u0111\u1ED5i.");
164
+ }
165
+ };
166
+ const disabled = status === "loading" || status === "saving";
167
+ const scopeLabel = selection.scope === "header" ? "\u0110\u1EA7u trang" : selection.scope === "footer" ? "Ch\xE2n trang" : "Th\xF4ng tin website";
168
+ return /* @__PURE__ */ jsxs("section", { "aria-label": `Thu\u1ED9c t\xEDnh ${scopeLabel}`, className: "wikex-global-inspector", children: [
169
+ /* @__PURE__ */ jsx("div", { className: "wikex-global-inspector__eyebrow", children: "N\u1ED9i dung d\xF9ng chung" }),
170
+ /* @__PURE__ */ jsx("h2", { children: scopeLabel }),
171
+ /* @__PURE__ */ jsx("p", { className: "wikex-global-inspector__path", children: selection.fieldPath }),
172
+ /* @__PURE__ */ jsxs("label", { className: "wikex-global-inspector__field", children: [
173
+ /* @__PURE__ */ jsx("span", { children: "N\u1ED9i dung" }),
174
+ /* @__PURE__ */ jsx(
175
+ "textarea",
176
+ {
177
+ disabled,
178
+ onChange: (event) => setValues((current) => ({ ...current, text: event.target.value })),
179
+ rows: 3,
180
+ value: values.text
181
+ }
182
+ )
183
+ ] }),
184
+ selection.supportsLink && linkBasePath && /* @__PURE__ */ jsxs(Fragment, { children: [
185
+ /* @__PURE__ */ jsxs("label", { className: "wikex-global-inspector__field", children: [
186
+ /* @__PURE__ */ jsx("span", { children: "\u0110\u01B0\u1EDDng d\u1EABn" }),
187
+ /* @__PURE__ */ jsx(
188
+ "input",
189
+ {
190
+ disabled,
191
+ onChange: (event) => setValues((current) => ({ ...current, url: event.target.value })),
192
+ placeholder: "/contact ho\u1EB7c https://\u2026",
193
+ type: "text",
194
+ value: values.url
195
+ }
196
+ )
197
+ ] }),
198
+ /* @__PURE__ */ jsxs("div", { className: "wikex-global-inspector__colors", children: [
199
+ /* @__PURE__ */ jsxs("label", { className: "wikex-global-inspector__field", children: [
200
+ /* @__PURE__ */ jsx("span", { children: "M\xE0u ch\u1EEF" }),
201
+ /* @__PURE__ */ jsxs("div", { className: "wikex-global-inspector__color-control", children: [
202
+ /* @__PURE__ */ jsx(
203
+ "input",
204
+ {
205
+ "aria-label": "Ch\u1ECDn m\xE0u ch\u1EEF",
206
+ disabled,
207
+ onChange: (event) => setValues((current) => ({ ...current, textColor: event.target.value })),
208
+ type: "color",
209
+ value: normalizeInspectorColor(values.textColor) || "#FFFFFF"
210
+ }
211
+ ),
212
+ /* @__PURE__ */ jsx(
213
+ "input",
214
+ {
215
+ disabled,
216
+ maxLength: 7,
217
+ onChange: (event) => setValues((current) => ({ ...current, textColor: event.target.value })),
218
+ placeholder: "M\u1EB7c \u0111\u1ECBnh",
219
+ type: "text",
220
+ value: values.textColor
221
+ }
222
+ )
223
+ ] })
224
+ ] }),
225
+ /* @__PURE__ */ jsxs("label", { className: "wikex-global-inspector__field", children: [
226
+ /* @__PURE__ */ jsx("span", { children: "M\xE0u n\u1EC1n" }),
227
+ /* @__PURE__ */ jsxs("div", { className: "wikex-global-inspector__color-control", children: [
228
+ /* @__PURE__ */ jsx(
229
+ "input",
230
+ {
231
+ "aria-label": "Ch\u1ECDn m\xE0u n\u1EC1n",
232
+ disabled,
233
+ onChange: (event) => setValues((current) => ({ ...current, backgroundColor: event.target.value })),
234
+ type: "color",
235
+ value: normalizeInspectorColor(values.backgroundColor) || "#172019"
236
+ }
237
+ ),
238
+ /* @__PURE__ */ jsx(
239
+ "input",
240
+ {
241
+ disabled,
242
+ maxLength: 7,
243
+ onChange: (event) => setValues((current) => ({ ...current, backgroundColor: event.target.value })),
244
+ placeholder: "M\u1EB7c \u0111\u1ECBnh",
245
+ type: "text",
246
+ value: values.backgroundColor
247
+ }
248
+ )
249
+ ] })
250
+ ] })
251
+ ] })
252
+ ] }),
253
+ /* @__PURE__ */ jsxs("div", { className: "wikex-global-inspector__actions", children: [
254
+ /* @__PURE__ */ jsx(
255
+ "button",
256
+ {
257
+ disabled: disabled || !values.text.trim(),
258
+ onClick: () => void save(),
259
+ type: "button",
260
+ children: status === "saving" ? "\u0110ang l\u01B0u\u2026" : "L\u01B0u thay \u0111\u1ED5i"
261
+ }
262
+ ),
263
+ selection.supportsLink && linkBasePath && /* @__PURE__ */ jsx(
264
+ "button",
265
+ {
266
+ className: "wikex-global-inspector__reset",
267
+ disabled,
268
+ onClick: () => setValues((current) => ({ ...current, backgroundColor: "", textColor: "" })),
269
+ type: "button",
270
+ children: "D\xF9ng m\xE0u m\u1EB7c \u0111\u1ECBnh"
271
+ }
272
+ )
273
+ ] }),
274
+ message && /* @__PURE__ */ jsx("p", { className: `wikex-global-inspector__status is-${status}`, children: message })
275
+ ] });
276
+ };
277
+
278
+ // src/live-preview/TextStyleInspector.tsx
279
+ import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
280
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
281
+ var getErrorMessage2 = async (response) => {
282
+ try {
283
+ const body = await response.json();
284
+ return body.errors?.[0]?.message || body.message;
285
+ } catch {
286
+ return void 0;
287
+ }
288
+ };
289
+ var getDocumentURL = (selection) => selection.kind === "collection" ? `/api/${selection.collection}/${encodeURIComponent(selection.documentID)}?depth=0&draft=true` : `/api/globals/${selection.global}?depth=0&draft=true`;
290
+ var saveTextStylePatch = async (selection, patch, signal) => {
291
+ const currentResponse = await fetch(getDocumentURL(selection), {
292
+ credentials: "include",
293
+ signal
294
+ });
295
+ if (!currentResponse.ok) {
296
+ throw new Error(await getErrorMessage2(currentResponse) || "Kh\xF4ng th\u1EC3 \u0111\u1ECDc d\u1EEF li\u1EC7u hi\u1EC7n t\u1EA1i.");
297
+ }
298
+ const currentDocument = await currentResponse.json();
299
+ const textStyles = normalizeTextStyleMap(currentDocument.textStyles);
300
+ const nextStyle = normalizeTextStyleValue({
301
+ ...textStyles[selection.fieldPath],
302
+ ...patch
303
+ });
304
+ const persistedStyle = {};
305
+ if (nextStyle.fontFamily && nextStyle.fontFamily !== "default") {
306
+ persistedStyle.fontFamily = nextStyle.fontFamily;
307
+ }
308
+ if (nextStyle.lineHeight && nextStyle.lineHeight !== "default") {
309
+ persistedStyle.lineHeight = nextStyle.lineHeight;
310
+ }
311
+ if (nextStyle.width) {
312
+ persistedStyle.width = nextStyle.width;
313
+ persistedStyle.widthOffset = nextStyle.widthOffset;
314
+ }
315
+ if (Object.keys(persistedStyle).length) {
316
+ textStyles[selection.fieldPath] = persistedStyle;
317
+ } else {
318
+ delete textStyles[selection.fieldPath];
319
+ }
320
+ const response = await fetch(`${getDocumentURL(selection)}&autosave=true`, {
321
+ body: JSON.stringify({ _status: "draft", textStyles }),
322
+ credentials: "include",
323
+ headers: { "Content-Type": "application/json" },
324
+ method: selection.kind === "collection" ? "PATCH" : "POST",
325
+ signal
326
+ });
327
+ if (!response.ok) {
328
+ throw new Error(await getErrorMessage2(response) || "Kh\xF4ng th\u1EC3 l\u01B0u thu\u1ED9c t\xEDnh text.");
329
+ }
330
+ return persistedStyle;
331
+ };
332
+ var TextStyleInspector = ({
333
+ onApply,
334
+ onTextApply,
335
+ selection
336
+ }) => {
337
+ const initialValues = {
338
+ fontFamily: "default",
339
+ lineHeight: "default"
340
+ };
341
+ const [values, setValues] = useState2(initialValues);
342
+ const [status, setStatus] = useState2(
343
+ "loading"
344
+ );
345
+ const [message, setMessage] = useState2("\u0110ang t\u1EA3i thu\u1ED9c t\xEDnh\u2026");
346
+ const [text, setText] = useState2(selection.text);
347
+ const saveController = useRef(null);
348
+ const saveSequence = useRef(0);
349
+ const valuesRef = useRef(initialValues);
350
+ useEffect2(() => {
351
+ const controller = new AbortController();
352
+ saveController.current?.abort();
353
+ saveSequence.current += 1;
354
+ setText(selection.text);
355
+ setStatus("loading");
356
+ setMessage("\u0110ang t\u1EA3i thu\u1ED9c t\xEDnh\u2026");
357
+ void fetch(getDocumentURL(selection), {
358
+ credentials: "include",
359
+ signal: controller.signal
360
+ }).then(async (response) => {
361
+ if (!response.ok) {
362
+ throw new Error(await getErrorMessage2(response) || "Kh\xF4ng th\u1EC3 t\u1EA3i thu\u1ED9c t\xEDnh text.");
363
+ }
364
+ const document2 = await response.json();
365
+ const savedStyle = normalizeTextStyleValue(
366
+ normalizeTextStyleMap(document2.textStyles)[selection.fieldPath]
367
+ );
368
+ const nextValues = {
369
+ fontFamily: savedStyle.fontFamily || "default",
370
+ lineHeight: savedStyle.lineHeight || "default"
371
+ };
372
+ valuesRef.current = nextValues;
373
+ setValues(nextValues);
374
+ setStatus("idle");
375
+ setMessage("");
376
+ }).catch((error) => {
377
+ if (controller.signal.aborted) return;
378
+ setStatus("error");
379
+ setMessage(error instanceof Error ? error.message : "Kh\xF4ng th\u1EC3 t\u1EA3i thu\u1ED9c t\xEDnh text.");
380
+ });
381
+ return () => {
382
+ controller.abort();
383
+ saveController.current?.abort();
384
+ };
385
+ }, [selection]);
386
+ const save = async (nextValues) => {
387
+ saveController.current?.abort();
388
+ const controller = new AbortController();
389
+ const sequence = ++saveSequence.current;
390
+ saveController.current = controller;
391
+ setStatus("saving");
392
+ setMessage("\u0110ang t\u1EF1 \u0111\u1ED9ng l\u01B0u\u2026");
393
+ try {
394
+ await saveTextStylePatch(
395
+ selection,
396
+ {
397
+ fontFamily: nextValues.fontFamily,
398
+ lineHeight: nextValues.lineHeight
399
+ },
400
+ controller.signal
401
+ );
402
+ if (sequence !== saveSequence.current) return;
403
+ setStatus("success");
404
+ setMessage("\u0110\xE3 t\u1EF1 \u0111\u1ED9ng l\u01B0u v\xE0o b\u1EA3n nh\xE1p. B\u1EA5m \u201CXu\u1EA5t b\u1EA3n thay \u0111\u1ED5i\u201D \u0111\u1EC3 xu\u1EA5t b\u1EA3n.");
405
+ } catch (error) {
406
+ if (controller.signal.aborted || sequence !== saveSequence.current) return;
407
+ setStatus("error");
408
+ setMessage(error instanceof Error ? error.message : "Kh\xF4ng th\u1EC3 t\u1EF1 \u0111\u1ED9ng l\u01B0u thu\u1ED9c t\xEDnh text.");
409
+ }
410
+ };
411
+ const applyAndSave = (nextValues) => {
412
+ valuesRef.current = nextValues;
413
+ setValues(nextValues);
414
+ onApply(nextValues);
415
+ void save(nextValues);
416
+ };
417
+ const disabled = status === "loading";
418
+ return /* @__PURE__ */ jsxs2("section", { "aria-label": "Thu\u1ED9c t\xEDnh text", className: "wikex-global-inspector wikex-text-inspector", children: [
419
+ /* @__PURE__ */ jsx2("div", { className: "wikex-global-inspector__eyebrow", children: "Thu\u1ED9c t\xEDnh v\u0103n b\u1EA3n" }),
420
+ /* @__PURE__ */ jsx2("h2", { children: selection.tagName.toUpperCase() }),
421
+ /* @__PURE__ */ jsx2("p", { className: "wikex-global-inspector__path", children: selection.fieldPath }),
422
+ /* @__PURE__ */ jsxs2("label", { className: "wikex-global-inspector__field", children: [
423
+ /* @__PURE__ */ jsx2("span", { children: "N\u1ED9i dung" }),
424
+ selection.multiline ? /* @__PURE__ */ jsx2(
425
+ "textarea",
426
+ {
427
+ disabled: !selection.canEditText,
428
+ onChange: (event) => {
429
+ setText(event.target.value);
430
+ onTextApply(event.target.value);
431
+ },
432
+ rows: 3,
433
+ value: text
434
+ }
435
+ ) : /* @__PURE__ */ jsx2(
436
+ "input",
437
+ {
438
+ disabled: !selection.canEditText,
439
+ onChange: (event) => {
440
+ setText(event.target.value);
441
+ onTextApply(event.target.value);
442
+ },
443
+ type: "text",
444
+ value: text
445
+ }
446
+ ),
447
+ !selection.canEditText && /* @__PURE__ */ jsx2("small", { className: "wikex-text-inspector__hint", children: "N\u1ED9i dung n\xE0y n\u1EB1m trong component t\u0129nh; c\xF3 th\u1EC3 ch\u1EC9nh ki\u1EC3u ch\u1EEF nh\u01B0ng kh\xF4ng th\u1EC3 s\u1EEDa v\u0103n b\u1EA3n t\u1EEB CMS." })
448
+ ] }),
449
+ /* @__PURE__ */ jsxs2("label", { className: "wikex-global-inspector__field", children: [
450
+ /* @__PURE__ */ jsx2("span", { children: "Ph\xF4ng ch\u1EEF" }),
451
+ /* @__PURE__ */ jsx2(
452
+ "select",
453
+ {
454
+ disabled,
455
+ onChange: (event) => applyAndSave({
456
+ ...valuesRef.current,
457
+ fontFamily: event.target.value
458
+ }),
459
+ value: values.fontFamily,
460
+ children: TEXT_FONT_OPTIONS.map((option) => /* @__PURE__ */ jsx2("option", { value: option.value, children: option.label }, option.value))
461
+ }
462
+ )
463
+ ] }),
464
+ /* @__PURE__ */ jsxs2("label", { className: "wikex-global-inspector__field", children: [
465
+ /* @__PURE__ */ jsx2("span", { children: "Chi\u1EC1u cao d\xF2ng" }),
466
+ /* @__PURE__ */ jsx2(
467
+ "select",
468
+ {
469
+ disabled,
470
+ onChange: (event) => applyAndSave({
471
+ ...valuesRef.current,
472
+ lineHeight: event.target.value
473
+ }),
474
+ value: values.lineHeight,
475
+ children: TEXT_LINE_HEIGHT_OPTIONS.map((option) => /* @__PURE__ */ jsx2("option", { value: option.value, children: option.label }, option.value))
476
+ }
477
+ )
478
+ ] }),
479
+ /* @__PURE__ */ jsx2("div", { className: "wikex-global-inspector__actions", children: /* @__PURE__ */ jsx2(
480
+ "button",
481
+ {
482
+ className: "wikex-global-inspector__reset",
483
+ disabled,
484
+ onClick: () => applyAndSave({ fontFamily: "default", lineHeight: "default" }),
485
+ type: "button",
486
+ children: "\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh"
487
+ }
488
+ ) }),
489
+ message && /* @__PURE__ */ jsx2("p", { className: `wikex-global-inspector__status is-${status}`, children: message })
490
+ ] });
491
+ };
492
+
493
+ // src/live-preview/runtimeConfig.ts
494
+ var runtimeConfig = {
495
+ getVisualEditorRoute: () => null,
496
+ pageBlocks: [],
497
+ postBlocks: []
498
+ };
499
+ var configureLivePreview = (config) => {
500
+ runtimeConfig = config;
501
+ };
502
+ var getVisualEditorRoute = (pathname) => runtimeConfig.getVisualEditorRoute(pathname);
503
+ var isVisualEditorRoute = (pathname) => Boolean(getVisualEditorRoute(pathname));
504
+ var getBlockPickerOptions = (scope) => scope === "post" ? runtimeConfig.postBlocks : runtimeConfig.pageBlocks;
505
+
506
+ // src/live-preview/LivePreviewEditor.tsx
507
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
508
+ var DEFAULT_EDIT_RATIO = 0.4;
509
+ var HANDLE_WIDTH = 12;
510
+ var LIVE_PREVIEW_TRANSITION_MS = 160;
511
+ var MIN_EDIT_WIDTH = 320;
512
+ var MIN_PREVIEW_WIDTH = 320;
513
+ var STORAGE_KEY = "wikex-live-preview-edit-ratio";
514
+ var LEGACY_STORAGE_KEY = "aurea-live-preview-edit-ratio";
515
+ var MIN_TEXT_WIDTH_PX = 72;
516
+ var RESIZABLE_TEXT_SELECTOR = "h1,h2,h3,h4,h5,h6,p,li";
517
+ var isEditableGlobalScope = (value) => value === "header" || value === "footer" || value === "settings";
518
+ var normalizeText = (value) => value.replace(/\s+/g, " ").trim();
519
+ var readInlineText = (element) => element.innerText.replace(/\u00a0/g, " ").replace(/\r\n/g, "\n").trim();
520
+ var getMatchingPreviewFields = (element, fieldPath) => isSyntheticElementStyleKey(fieldPath) ? Array.from(
521
+ element.ownerDocument.querySelectorAll(EDITABLE_PREVIEW_SELECTOR)
522
+ ).filter((candidate) => getEditableElementStyleKey(candidate) === fieldPath) : Array.from(element.ownerDocument.querySelectorAll("[data-wikex-field]")).filter(
523
+ (candidate) => candidate.dataset.wikexField === fieldPath
524
+ );
525
+ var EditorIcon = ({ name }) => {
526
+ if (name === "elements") {
527
+ return /* @__PURE__ */ jsx3("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { d: "M4 4h6v6H4zM14 4h6v6h-6zM4 14h6v6H4zM14 14h6v6h-6z" }) });
528
+ }
529
+ if (name === "sections") {
530
+ return /* @__PURE__ */ jsx3("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { d: "M4 5h16v5H4zM4 14h16v5H4z" }) });
531
+ }
532
+ return /* @__PURE__ */ jsx3("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { d: "M6 3h10l3 3v15H6zM16 3v4h4M9 11h7M9 15h7" }) });
533
+ };
534
+ var getVisibleBlocksField = () => {
535
+ const fields = Array.from(
536
+ document.querySelectorAll("#field-customLayout, #field-layout, .blocks-field")
537
+ );
538
+ return fields[0] ?? null;
539
+ };
540
+ var getVisibleSectionsTarget = () => getVisibleBlocksField() ?? document.querySelector("#field-hero, #field-customHero, .collapsible-field");
541
+ var getVisibleRichTextTarget = () => document.querySelector(".collection-edit__main .rich-text-lexical");
542
+ var scrollEditorTo = (target) => {
543
+ if (!target) return;
544
+ target.scrollIntoView({ behavior: "smooth", block: "center" });
545
+ target.classList.remove("wikex-editor-target--active");
546
+ window.requestAnimationFrame(() => target.classList.add("wikex-editor-target--active"));
547
+ window.setTimeout(() => target.classList.remove("wikex-editor-target--active"), 1200);
548
+ };
549
+ var activateEditorTab = (label) => {
550
+ const tabLabels = label === "content" ? /* @__PURE__ */ new Set(["content", "n\u1ED9i dung"]) : /* @__PURE__ */ new Set(["hero", "m\u1EDF \u0111\u1EA7u"]);
551
+ const tab = Array.from(
552
+ document.querySelectorAll(".tabs-field__tab-button")
553
+ ).find((button) => tabLabels.has(normalizeText(button.textContent || "").toLowerCase()));
554
+ if (tab && !tab.classList.contains("tabs-field__tab-button--active")) tab.click();
555
+ };
556
+ var getPageBlockTriggerWhenReady = (onReady, attempt = 0) => {
557
+ const blocksField = getVisibleBlocksField();
558
+ if (!blocksField) {
559
+ activateEditorTab("content");
560
+ if (attempt < 12) {
561
+ window.setTimeout(() => getPageBlockTriggerWhenReady(onReady, attempt + 1), 100);
562
+ }
563
+ return;
564
+ }
565
+ scrollEditorTo(blocksField);
566
+ const trigger = blocksField.querySelector(
567
+ ".blocks-field__drawer-toggler button, .blocks-field__drawer-toggler"
568
+ );
569
+ if (trigger) window.setTimeout(() => onReady(trigger), 180);
570
+ };
571
+ var getPageBlockTrigger = (onReady) => {
572
+ const customLayoutRadio = document.querySelector(
573
+ '#field-layoutMode-custom, input[type="radio"][value="custom"]'
574
+ );
575
+ if (customLayoutRadio && !customLayoutRadio.checked) customLayoutRadio.click();
576
+ getPageBlockTriggerWhenReady(onReady);
577
+ };
578
+ var getPostBlockTriggerWhenReady = (onReady, attempt = 0) => {
579
+ activateEditorTab("content");
580
+ const richTextTarget = getVisibleRichTextTarget();
581
+ const blockTrigger = richTextTarget?.querySelector(
582
+ 'button[aria-label="blocks dropdown"], button.add-block-menu'
583
+ );
584
+ if (!richTextTarget || !blockTrigger) {
585
+ if (attempt < 16) {
586
+ window.setTimeout(() => getPostBlockTriggerWhenReady(onReady, attempt + 1), 100);
587
+ }
588
+ return;
589
+ }
590
+ scrollEditorTo(richTextTarget);
591
+ window.setTimeout(() => onReady(blockTrigger), 180);
592
+ };
593
+ var selectPostBlockWhenReady = (blockTrigger, itemKey, attempt = 0) => {
594
+ if (!blockTrigger.classList.contains("active")) blockTrigger.click();
595
+ const option = document.querySelector(`[data-item-key="${itemKey}"]`);
596
+ if (option) {
597
+ option.click();
598
+ return;
599
+ }
600
+ if (attempt < 12) {
601
+ window.setTimeout(() => selectPostBlockWhenReady(blockTrigger, itemKey, attempt + 1), 50);
602
+ }
603
+ };
604
+ var selectPageBlockWhenReady = (payloadLabel, attempt = 0) => {
605
+ const openDrawer = Array.from(document.querySelectorAll(".drawer--is-open")).find(
606
+ (drawer) => drawer.querySelector(".blocks-drawer__blocks")
607
+ );
608
+ const option = Array.from(
609
+ openDrawer?.querySelectorAll(".thumbnail-card") ?? []
610
+ ).find((button) => button.title === payloadLabel);
611
+ if (option) {
612
+ option.click();
613
+ document.documentElement.removeAttribute("data-wikex-forwarding-block");
614
+ return;
615
+ }
616
+ if (attempt < 12) {
617
+ window.setTimeout(() => selectPageBlockWhenReady(payloadLabel, attempt + 1), 50);
618
+ return;
619
+ }
620
+ document.documentElement.removeAttribute("data-wikex-forwarding-block");
621
+ };
622
+ var BlockPickerModal = ({
623
+ onClose,
624
+ onSelect,
625
+ options,
626
+ title
627
+ }) => {
628
+ useEffect3(() => {
629
+ const handleKeyDown = (event) => {
630
+ if (event.key === "Escape") onClose();
631
+ };
632
+ document.addEventListener("keydown", handleKeyDown);
633
+ return () => document.removeEventListener("keydown", handleKeyDown);
634
+ }, [onClose]);
635
+ return /* @__PURE__ */ jsx3("div", { className: "wikex-block-picker", onMouseDown: onClose, role: "presentation", children: /* @__PURE__ */ jsxs3(
636
+ "section",
637
+ {
638
+ "aria-label": title,
639
+ "aria-modal": "true",
640
+ className: "wikex-block-picker__dialog",
641
+ onMouseDown: (event) => event.stopPropagation(),
642
+ role: "dialog",
643
+ children: [
644
+ /* @__PURE__ */ jsxs3("header", { className: "wikex-block-picker__header", children: [
645
+ /* @__PURE__ */ jsxs3("div", { children: [
646
+ /* @__PURE__ */ jsx3("span", { children: "Th\xEAm kh\u1ED1i" }),
647
+ /* @__PURE__ */ jsx3("h2", { children: title })
648
+ ] }),
649
+ /* @__PURE__ */ jsx3("button", { "aria-label": "\u0110\xF3ng b\u1ED9 ch\u1ECDn component", onClick: onClose, type: "button", children: /* @__PURE__ */ jsx3("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { d: "M6 6l12 12M18 6 6 18" }) }) })
650
+ ] }),
651
+ /* @__PURE__ */ jsx3("div", { className: "wikex-block-picker__grid", children: options.map((option) => /* @__PURE__ */ jsxs3(
652
+ "button",
653
+ {
654
+ className: "wikex-block-picker__card",
655
+ onClick: () => onSelect(option),
656
+ type: "button",
657
+ children: [
658
+ /* @__PURE__ */ jsx3(
659
+ "span",
660
+ {
661
+ "aria-hidden": "true",
662
+ className: `wikex-block-picker__thumbnail${option.image ? "" : " wikex-block-picker__thumbnail--empty"}`,
663
+ style: option.image ? { backgroundImage: `url(${option.image})` } : void 0
664
+ }
665
+ ),
666
+ /* @__PURE__ */ jsxs3("span", { className: "wikex-block-picker__copy", children: [
667
+ /* @__PURE__ */ jsx3("strong", { children: option.label }),
668
+ /* @__PURE__ */ jsx3("span", { children: option.description })
669
+ ] })
670
+ ]
671
+ },
672
+ option.slug
673
+ )) })
674
+ ]
675
+ }
676
+ ) });
677
+ };
678
+ var getAdminBasePath = () => {
679
+ const match = window.location.pathname.match(/^(.*\/admin)(?:\/|$)/);
680
+ return match?.[1] ?? "/admin";
681
+ };
682
+ var getEditorDocumentTarget = () => {
683
+ return getVisualEditorRoute(window.location.pathname)?.documentTarget ?? null;
684
+ };
685
+ var getEditorScope = (pathname = window.location.pathname) => getVisualEditorRoute(pathname)?.scope ?? "content";
686
+ var getFieldControl = (fieldPath, text) => {
687
+ if (fieldPath) {
688
+ const namedControl = Array.from(document.getElementsByName(fieldPath)).find(
689
+ (control) => control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement
690
+ );
691
+ if (namedControl) return namedControl;
692
+ }
693
+ if (!text) return null;
694
+ const controls = Array.from(
695
+ document.querySelectorAll(
696
+ '.collection-edit__main input:not([type="hidden"]):not([type="radio"]):not([type="checkbox"]), .collection-edit__main textarea'
697
+ )
698
+ );
699
+ return controls.find((control) => normalizeText(control.value) === text) ?? null;
700
+ };
701
+ var getFieldContainer = (control, fieldPath) => {
702
+ if (control) return control.closest(".field-type");
703
+ if (!fieldPath) return null;
704
+ const blockMatch = fieldPath.match(/^(customLayout|layout)\.(\d+)(?:\.|$)/);
705
+ if (blockMatch) {
706
+ return document.getElementById(`${blockMatch[1]}-row-${Number(blockMatch[2])}`);
707
+ }
708
+ const rowMatch = fieldPath.match(/^([^.]+)\.(\d+)(?:\.|$)/);
709
+ if (rowMatch) {
710
+ const row = document.getElementById(`${rowMatch[1]}-row-${Number(rowMatch[2])}`);
711
+ if (row) return row;
712
+ }
713
+ return document.getElementById(`field-${fieldPath.replaceAll(".", "__")}`) ?? Array.from(document.querySelectorAll("[data-field-path]")).find(
714
+ (field) => field.dataset.fieldPath === fieldPath
715
+ ) ?? null;
716
+ };
717
+ var getRichTextControl = (fieldPath, text) => {
718
+ if (!text) return null;
719
+ const scope = getFieldContainer(null, fieldPath) ?? document.querySelector(".collection-edit__main");
720
+ if (!scope) return null;
721
+ const textNode = Array.from(
722
+ scope.querySelectorAll('.rich-text-lexical [data-lexical-text="true"]')
723
+ ).find((node) => normalizeText(node.textContent || "") === normalizeText(text));
724
+ const editor = textNode?.closest('[contenteditable="true"]');
725
+ return textNode && editor ? { editor, textNode } : null;
726
+ };
727
+ var expandEditorTarget = (target) => {
728
+ const row = target?.matches('[id*="-row-"]') ? target : target?.closest('[id*="-row-"]');
729
+ const collapsible = row?.querySelector(".blocks-field__row, .array-field__row");
730
+ if (collapsible?.classList.contains("collapsible--collapsed")) {
731
+ collapsible.querySelector(".collapsible__toggle")?.click();
732
+ }
733
+ };
734
+ var focusRichTextValue = (scope, text) => {
735
+ if (!scope || !text) return false;
736
+ const textNode = Array.from(
737
+ scope.querySelectorAll('.rich-text-lexical [data-lexical-text="true"]')
738
+ ).find((node) => normalizeText(node.textContent || "") === text);
739
+ const editor = textNode?.closest('[contenteditable="true"]');
740
+ if (!textNode || !editor) return false;
741
+ editor.focus({ preventScroll: true });
742
+ const selection = window.getSelection();
743
+ const range = document.createRange();
744
+ range.selectNodeContents(textNode);
745
+ selection?.removeAllRanges();
746
+ selection?.addRange(range);
747
+ return true;
748
+ };
749
+ var inferEditableFieldPath = (element, fieldPath) => {
750
+ if (!fieldPath) return void 0;
751
+ if (fieldPath === "hero" || fieldPath === "customHero") {
752
+ return `${fieldPath}.richText`;
753
+ }
754
+ const blockType = element.closest("[data-block-type]")?.dataset.blockType;
755
+ if (/^(customLayout|layout)\.\d+$/.test(fieldPath)) {
756
+ if (blockType === "cta") return `${fieldPath}.richText`;
757
+ if (blockType === "archive") return `${fieldPath}.introContent`;
758
+ }
759
+ return fieldPath;
760
+ };
761
+ var isAggregateInspectorSelection = (documentTarget, fieldPath) => {
762
+ if (!fieldPath) return false;
763
+ if (/^(customLayout|layout)\.\d+$/.test(fieldPath)) return true;
764
+ return documentTarget?.kind === "collection" && documentTarget.collection === "posts" && fieldPath === "content";
765
+ };
766
+ var updatePayloadControl = (control, value) => {
767
+ const prototype = control instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
768
+ const setter = Object.getOwnPropertyDescriptor(prototype, "value")?.set;
769
+ setter?.call(control, value);
770
+ control.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertText" }));
771
+ control.dispatchEvent(new Event("change", { bubbles: true }));
772
+ };
773
+ var updateRichTextControl = ({ editor, textNode }, value) => {
774
+ editor.focus({ preventScroll: true });
775
+ const selection = window.getSelection();
776
+ const range = document.createRange();
777
+ range.selectNodeContents(textNode);
778
+ selection?.removeAllRanges();
779
+ selection?.addRange(range);
780
+ const updated = document.execCommand("insertText", false, value);
781
+ if (!updated) {
782
+ textNode.textContent = value;
783
+ editor.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertText" }));
784
+ }
785
+ editor.dispatchEvent(new Event("change", { bubbles: true }));
786
+ };
787
+ var WixEditorChrome = ({ container, editor, previewWrapper, route, scope }) => {
788
+ const pagesHref = `${getAdminBasePath()}/collections/pages`;
789
+ const editorScopeRef = useRef2(scope);
790
+ editorScopeRef.current = scope;
791
+ const isGlobalEditor = scope === "header" || scope === "footer";
792
+ const selectedControl = useRef2(null);
793
+ const selectedRichTextControl = useRef2(null);
794
+ const selectedFieldPath = useRef2(void 0);
795
+ const selectedGlobal = useRef2(null);
796
+ const selectedPreviewElement = useRef2(null);
797
+ const selectedTextInspector = useRef2(null);
798
+ const selectedTextResize = useRef2(null);
799
+ const textWidthSaveController = useRef2(null);
800
+ const blockPickerTrigger = useRef2(null);
801
+ const selectedText = useRef2("");
802
+ const revealPreviewField = useRef2(() => void 0);
803
+ const [globalInspectorSelection, setGlobalInspectorSelection] = useState3(null);
804
+ const [textInspectorSelection, setTextInspectorSelection] = useState3(null);
805
+ const [inspectorOpen, setInspectorOpen] = useState3(false);
806
+ const [blockPickerScope, setBlockPickerScope] = useState3(null);
807
+ const [selectionLabel, setSelectionLabel] = useState3("Ch\u1ECDn m\u1ED9t ph\u1EA7n t\u1EED tr\xEAn trang");
808
+ useEffect3(() => {
809
+ selectedControl.current = null;
810
+ selectedRichTextControl.current = null;
811
+ selectedFieldPath.current = void 0;
812
+ selectedGlobal.current = null;
813
+ selectedPreviewElement.current = null;
814
+ selectedTextInspector.current = null;
815
+ selectedTextResize.current = null;
816
+ textWidthSaveController.current?.abort();
817
+ blockPickerTrigger.current = null;
818
+ selectedText.current = "";
819
+ setGlobalInspectorSelection(null);
820
+ setTextInspectorSelection(null);
821
+ setInspectorOpen(false);
822
+ setBlockPickerScope(null);
823
+ setSelectionLabel("Ch\u1ECDn m\u1ED9t ph\u1EA7n t\u1EED tr\xEAn trang");
824
+ }, [previewWrapper, route]);
825
+ const openInspector = useCallback(() => {
826
+ setInspectorOpen(true);
827
+ if (selectedGlobal.current) {
828
+ setGlobalInspectorSelection({ ...selectedGlobal.current });
829
+ setTextInspectorSelection(null);
830
+ return;
831
+ }
832
+ if (selectedTextInspector.current) {
833
+ setGlobalInspectorSelection(null);
834
+ setTextInspectorSelection({ ...selectedTextInspector.current });
835
+ return;
836
+ }
837
+ setGlobalInspectorSelection(null);
838
+ setTextInspectorSelection(null);
839
+ const revealSelectedField = (attempt = 0) => {
840
+ const fieldPath = selectedFieldPath.current;
841
+ const target = getFieldContainer(selectedControl.current, fieldPath);
842
+ if (!target && attempt < 12) {
843
+ if (fieldPath?.startsWith("layout.")) activateEditorTab("content");
844
+ if (fieldPath === "hero" || fieldPath?.startsWith("hero.")) activateEditorTab("hero");
845
+ window.setTimeout(() => revealSelectedField(attempt + 1), 100);
846
+ return;
847
+ }
848
+ scrollEditorTo(target);
849
+ expandEditorTarget(target);
850
+ window.setTimeout(() => {
851
+ const control = getFieldControl(fieldPath, selectedText.current);
852
+ if (control) {
853
+ selectedControl.current = control;
854
+ control.focus({ preventScroll: true });
855
+ control.select();
856
+ return;
857
+ }
858
+ const refreshedTarget = getFieldContainer(null, fieldPath) ?? target;
859
+ focusRichTextValue(refreshedTarget, selectedText.current);
860
+ }, 180);
861
+ };
862
+ window.setTimeout(revealSelectedField, 80);
863
+ }, []);
864
+ const applyGlobalInspectorValues = useCallback((values) => {
865
+ const element = selectedPreviewElement.current;
866
+ if (!element) return;
867
+ const globalSelection = selectedGlobal.current;
868
+ const matchingElements = globalSelection ? getMatchingPreviewFields(element, globalSelection.fieldPath) : [element];
869
+ for (const matchingElement of matchingElements) {
870
+ matchingElement.innerText = values.text;
871
+ const interactiveElement = matchingElement.closest("a, button");
872
+ if (interactiveElement) {
873
+ interactiveElement.style.backgroundColor = values.backgroundColor;
874
+ interactiveElement.style.color = values.textColor;
875
+ const resolvedURL = globalSelection?.scope === "settings" && globalSelection.fieldPath === "email" ? `mailto:${values.text}` : values.url;
876
+ if (interactiveElement instanceof HTMLAnchorElement && resolvedURL) {
877
+ interactiveElement.setAttribute("href", resolvedURL);
878
+ }
879
+ }
880
+ }
881
+ selectedText.current = values.text;
882
+ if (globalSelection) {
883
+ selectedGlobal.current = {
884
+ ...globalSelection,
885
+ href: values.url || globalSelection.href,
886
+ text: values.text
887
+ };
888
+ }
889
+ setSelectionLabel(`\u0110\xE3 c\u1EADp nh\u1EADt \xB7 ${selectedGlobal.current?.scope || "n\u1ED9i dung d\xF9ng chung"}`);
890
+ }, []);
891
+ const applyTextInspectorValues = useCallback((values) => {
892
+ const element = selectedPreviewElement.current;
893
+ const selection = selectedTextInspector.current;
894
+ if (!element || !selection) return;
895
+ const style = getTextStyleCSS(values);
896
+ const matchingElements = getMatchingPreviewFields(element, selection.fieldPath);
897
+ for (const matchingElement of matchingElements.length ? matchingElements : [element]) {
898
+ if ("fontFamily" in values) matchingElement.style.fontFamily = style.fontFamily || "";
899
+ if ("lineHeight" in values) matchingElement.style.lineHeight = style.lineHeight || "";
900
+ }
901
+ setSelectionLabel(`\u0110\xE3 c\u1EADp nh\u1EADt ki\u1EC3u ch\u1EEF \xB7 ${selection.fieldPath}`);
902
+ }, []);
903
+ const applyTextInspectorText = useCallback((value) => {
904
+ const element = selectedPreviewElement.current;
905
+ const control = selectedControl.current;
906
+ const selection = selectedTextInspector.current;
907
+ if (!element || !control || !selection) return;
908
+ const matchingElements = getMatchingPreviewFields(element, selection.fieldPath);
909
+ for (const matchingElement of matchingElements.length ? matchingElements : [element]) {
910
+ matchingElement.innerText = value;
911
+ }
912
+ updatePayloadControl(
913
+ control,
914
+ control instanceof HTMLInputElement ? value.replace(/\s+/g, " ") : value
915
+ );
916
+ selectedText.current = value;
917
+ selectedTextInspector.current = { ...selection, text: value };
918
+ setSelectionLabel(`\u0110ang t\u1EF1 \u0111\u1ED9ng l\u01B0u n\u1ED9i dung \xB7 ${selection.fieldPath}`);
919
+ window.requestAnimationFrame(() => {
920
+ element.ownerDocument.defaultView?.dispatchEvent(new Event("resize"));
921
+ });
922
+ }, []);
923
+ const openElements = useCallback(() => {
924
+ setGlobalInspectorSelection(null);
925
+ setTextInspectorSelection(null);
926
+ setInspectorOpen(false);
927
+ const documentTarget = getEditorDocumentTarget();
928
+ if (documentTarget?.kind === "collection" && documentTarget.collection === "posts") {
929
+ setSelectionLabel("Th\xEAm component v\xE0o n\u1ED9i dung b\xE0i vi\u1EBFt");
930
+ window.setTimeout(
931
+ () => getPostBlockTriggerWhenReady((trigger) => {
932
+ blockPickerTrigger.current = trigger;
933
+ setBlockPickerScope("post");
934
+ }),
935
+ 80
936
+ );
937
+ return;
938
+ }
939
+ setSelectionLabel("Th\xEAm component v\xE0o b\u1ED1 c\u1EE5c trang");
940
+ window.setTimeout(
941
+ () => getPageBlockTrigger((trigger) => {
942
+ blockPickerTrigger.current = trigger;
943
+ setBlockPickerScope("page");
944
+ }),
945
+ 80
946
+ );
947
+ }, []);
948
+ const closeBlockPicker = useCallback(() => setBlockPickerScope(null), []);
949
+ const selectBlock = useCallback(
950
+ (option) => {
951
+ const trigger = blockPickerTrigger.current;
952
+ const scopeToInsert = blockPickerScope;
953
+ setBlockPickerScope(null);
954
+ if (!trigger?.isConnected || !scopeToInsert) return;
955
+ if (scopeToInsert === "post") {
956
+ window.setTimeout(() => selectPostBlockWhenReady(trigger, `block-${option.slug}`), 80);
957
+ return;
958
+ }
959
+ document.documentElement.setAttribute("data-wikex-forwarding-block", "true");
960
+ trigger.click();
961
+ window.setTimeout(() => selectPageBlockWhenReady(option.payloadLabel ?? option.label), 50);
962
+ },
963
+ [blockPickerScope]
964
+ );
965
+ const openSections = useCallback(() => {
966
+ setGlobalInspectorSelection(null);
967
+ setTextInspectorSelection(null);
968
+ setInspectorOpen(true);
969
+ const revealSections = (attempt = 0) => {
970
+ const documentTarget = getEditorDocumentTarget();
971
+ const isPostEditor = documentTarget?.kind === "collection" && documentTarget.collection === "posts";
972
+ const target = isPostEditor ? getVisibleRichTextTarget() : getVisibleBlocksField();
973
+ if (!target && attempt < 12) {
974
+ activateEditorTab("content");
975
+ window.setTimeout(() => revealSections(attempt + 1), 100);
976
+ return;
977
+ }
978
+ const resolvedTarget = target ?? getVisibleSectionsTarget();
979
+ scrollEditorTo(resolvedTarget);
980
+ if (!isPostEditor) expandEditorTarget(target);
981
+ };
982
+ window.setTimeout(revealSections, 80);
983
+ }, []);
984
+ useEffect3(() => {
985
+ editor.classList.add("wikex-visual-editor");
986
+ editor.setAttribute("data-wikex-visual-editor", "true");
987
+ editor.setAttribute("data-wikex-editor-route", route);
988
+ return () => {
989
+ editor.classList.remove("wikex-visual-editor", "wikex-editor--inspecting");
990
+ editor.removeAttribute("data-wikex-visual-editor");
991
+ editor.removeAttribute("data-wikex-editor-route");
992
+ editor.removeAttribute("data-wikex-inspecting");
993
+ document.documentElement.removeAttribute("data-wikex-forwarding-block");
994
+ };
995
+ }, [editor, route]);
996
+ useEffect3(() => {
997
+ editor.classList.toggle("wikex-editor--inspecting", inspectorOpen);
998
+ editor.setAttribute("data-wikex-inspecting", String(inspectorOpen));
999
+ }, [editor, inspectorOpen]);
1000
+ useEffect3(() => {
1001
+ let activeIframe = null;
1002
+ let cleanupActiveIframe = () => void 0;
1003
+ let bindFrame = 0;
1004
+ const bindIframe = () => {
1005
+ const iframe = previewWrapper.querySelector("#live-preview-iframe");
1006
+ if (iframe === activeIframe) return;
1007
+ cleanupActiveIframe();
1008
+ activeIframe = iframe;
1009
+ if (!iframe) return;
1010
+ let cleanupFrame = () => void 0;
1011
+ const installFrameEditor = () => {
1012
+ cleanupFrame();
1013
+ let frameDocument = null;
1014
+ try {
1015
+ frameDocument = iframe.contentDocument;
1016
+ } catch {
1017
+ return;
1018
+ }
1019
+ if (!frameDocument?.body) return;
1020
+ const style = frameDocument.createElement("style");
1021
+ style.dataset.wikexEditor = "true";
1022
+ style.textContent = `
1023
+ #wikex-selection-box {
1024
+ border: 1.5px solid #a67c3f;
1025
+ border-radius: 4px;
1026
+ display: none;
1027
+ pointer-events: none;
1028
+ position: fixed;
1029
+ z-index: 30;
1030
+ }
1031
+ .wikex-text-resize-handle {
1032
+ align-items: center;
1033
+ background: #fff;
1034
+ border: 2px solid #a67c3f;
1035
+ border-radius: 999px;
1036
+ box-shadow: 0 3px 12px rgba(15,20,17,.22);
1037
+ cursor: ew-resize;
1038
+ display: none;
1039
+ height: 34px;
1040
+ justify-content: center;
1041
+ padding: 0;
1042
+ position: fixed;
1043
+ touch-action: none;
1044
+ transform: translate(-50%, -50%);
1045
+ width: 12px;
1046
+ z-index: 62;
1047
+ }
1048
+ .wikex-text-resize-handle::after {
1049
+ background: #a67c3f;
1050
+ border-radius: 99px;
1051
+ content: "";
1052
+ height: 18px;
1053
+ width: 2px;
1054
+ }
1055
+ .wikex-text-resize-handle:hover,
1056
+ .wikex-text-resize-handle[data-active="true"] {
1057
+ background: #f3dfb5;
1058
+ box-shadow: 0 0 0 4px rgba(166,124,63,.18), 0 3px 12px rgba(15,20,17,.22);
1059
+ }
1060
+ #wikex-text-width-label {
1061
+ background: #a67c3f;
1062
+ border-radius: 6px;
1063
+ color: #fff;
1064
+ display: none;
1065
+ font: 700 11px/1 ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1066
+ padding: 5px 7px;
1067
+ pointer-events: none;
1068
+ position: fixed;
1069
+ transform: translate(-50%, -100%);
1070
+ white-space: nowrap;
1071
+ z-index: 63;
1072
+ }
1073
+ #wikex-selection-toolbar {
1074
+ align-items: center;
1075
+ background: rgba(20,29,23,.96);
1076
+ border: 1px solid rgba(255,255,255,.12);
1077
+ border-radius: 12px;
1078
+ box-shadow: 0 14px 40px rgba(15,20,17,.24);
1079
+ color: #172019;
1080
+ display: none;
1081
+ font: 600 12px/1.2 ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1082
+ gap: 4px;
1083
+ max-width: calc(100vw - 24px);
1084
+ padding: 5px;
1085
+ position: fixed;
1086
+ z-index: 60;
1087
+ }
1088
+ #wikex-selection-toolbar span {
1089
+ color: rgba(255,255,255,.7);
1090
+ max-width: 220px;
1091
+ overflow: hidden;
1092
+ padding: 0 8px;
1093
+ text-overflow: ellipsis;
1094
+ white-space: nowrap;
1095
+ }
1096
+ #wikex-selection-toolbar button {
1097
+ background: rgba(255,255,255,.1);
1098
+ border: 1px solid transparent;
1099
+ border-radius: 8px;
1100
+ color: #fff;
1101
+ cursor: pointer;
1102
+ font: inherit;
1103
+ min-height: 30px;
1104
+ padding: 6px 10px;
1105
+ transition: background-color 140ms ease, border-color 140ms ease;
1106
+ white-space: nowrap;
1107
+ }
1108
+ #wikex-selection-toolbar button:hover { background: rgba(255,255,255,.17); }
1109
+ #wikex-selection-toolbar button[data-primary="true"] {
1110
+ background: #d2b477;
1111
+ color: #162019;
1112
+ }
1113
+ #wikex-selection-toolbar button[data-primary="true"]:hover { background: #e4cc9b; }
1114
+ #wikex-selection-toolbar button:disabled { cursor: not-allowed; opacity: .45; }
1115
+ [data-wikex-inline-editing="true"] {
1116
+ caret-color: #a67c3f;
1117
+ cursor: text !important;
1118
+ min-width: 1ch;
1119
+ outline: none !important;
1120
+ }
1121
+ [data-wikex-field]:is(h1,h2,h3,h4,h5,h6,p,span,strong),
1122
+ [data-wikex-field] :is(h1,h2,h3,h4,h5,h6,p,span,strong) { cursor: text; }
1123
+ [data-wikex-field]:is(h1,h2,h3,h4,h5,h6,p,span,strong):not([data-wikex-selected="true"]):hover {
1124
+ outline: 1px dashed rgba(166,124,63,.58);
1125
+ outline-offset: 4px;
1126
+ }
1127
+ .admin-bar { display: none !important; }
1128
+ html { --admin-bar-height: 0px !important; }
1129
+ body * { cursor: default; }
1130
+ body a, body button { cursor: pointer; }
1131
+ `;
1132
+ frameDocument.head.appendChild(style);
1133
+ const selectionBox = frameDocument.createElement("div");
1134
+ selectionBox.id = "wikex-selection-box";
1135
+ const leftResizeHandle = frameDocument.createElement("button");
1136
+ leftResizeHandle.ariaLabel = "K\xE9o \u0111\u1EC3 \u0111\u1ED5i chi\u1EC1u r\u1ED9ng t\u1EEB b\xEAn tr\xE1i";
1137
+ leftResizeHandle.className = "wikex-text-resize-handle";
1138
+ leftResizeHandle.dataset.side = "left";
1139
+ leftResizeHandle.title = "K\xE9o \u0111\u1EC3 \u0111\u1ED5i chi\u1EC1u r\u1ED9ng \xB7 Nh\u1EA5p \u0111\xFAp \u0111\u1EC3 tr\u1EA3 v\u1EC1 t\u1EF1 \u0111\u1ED9ng";
1140
+ leftResizeHandle.type = "button";
1141
+ const rightResizeHandle = frameDocument.createElement("button");
1142
+ rightResizeHandle.ariaLabel = "K\xE9o \u0111\u1EC3 \u0111\u1ED5i chi\u1EC1u r\u1ED9ng t\u1EEB b\xEAn ph\u1EA3i";
1143
+ rightResizeHandle.className = "wikex-text-resize-handle";
1144
+ rightResizeHandle.dataset.side = "right";
1145
+ rightResizeHandle.title = "K\xE9o \u0111\u1EC3 \u0111\u1ED5i chi\u1EC1u r\u1ED9ng \xB7 Nh\u1EA5p \u0111\xFAp \u0111\u1EC3 tr\u1EA3 v\u1EC1 t\u1EF1 \u0111\u1ED9ng";
1146
+ rightResizeHandle.type = "button";
1147
+ const widthLabel = frameDocument.createElement("span");
1148
+ widthLabel.id = "wikex-text-width-label";
1149
+ const toolbar = frameDocument.createElement("div");
1150
+ toolbar.id = "wikex-selection-toolbar";
1151
+ const label = frameDocument.createElement("span");
1152
+ const propertiesButton = frameDocument.createElement("button");
1153
+ propertiesButton.dataset.primary = "true";
1154
+ propertiesButton.textContent = "Thu\u1ED9c t\xEDnh";
1155
+ propertiesButton.type = "button";
1156
+ toolbar.append(label, propertiesButton);
1157
+ frameDocument.body.append(
1158
+ selectionBox,
1159
+ leftResizeHandle,
1160
+ rightResizeHandle,
1161
+ widthLabel,
1162
+ toolbar
1163
+ );
1164
+ let selectedElement = null;
1165
+ let inlineInputHandler = null;
1166
+ let inlineBlurHandler = null;
1167
+ let inlineKeyHandler = null;
1168
+ let inlineOriginalText = "";
1169
+ const resizeHandles = [leftResizeHandle, rightResizeHandle];
1170
+ const hideResizeControls = () => {
1171
+ for (const handle of resizeHandles) handle.style.display = "none";
1172
+ widthLabel.style.display = "none";
1173
+ };
1174
+ const updateSelectionPosition = () => {
1175
+ if (!selectedElement?.isConnected) {
1176
+ selectionBox.style.display = "none";
1177
+ toolbar.style.display = "none";
1178
+ hideResizeControls();
1179
+ return;
1180
+ }
1181
+ const bounds = selectedElement.getBoundingClientRect();
1182
+ const isHeaderSelection = Boolean(
1183
+ selectedElement.closest('header, [data-wikex-scope="header"]')
1184
+ );
1185
+ const obstructedTop = isHeaderSelection ? 0 : Array.from(
1186
+ frameDocument?.querySelectorAll(
1187
+ 'header, [data-wikex-scope="header"]'
1188
+ ) ?? []
1189
+ ).reduce((top, candidate) => {
1190
+ const style2 = iframe.contentWindow?.getComputedStyle(candidate);
1191
+ const candidateBounds = candidate.getBoundingClientRect();
1192
+ const obstructsCanvas = (style2?.position === "fixed" || style2?.position === "sticky") && candidateBounds.top <= top + 1 && candidateBounds.bottom > 0;
1193
+ return obstructsCanvas ? Math.max(top, candidateBounds.bottom) : top;
1194
+ }, 0);
1195
+ const visibleLeft = Math.max(0, bounds.left);
1196
+ const visibleTop = Math.max(obstructedTop, 0, bounds.top);
1197
+ const visibleRight = Math.min(iframe.clientWidth, bounds.right);
1198
+ const visibleBottom = Math.min(iframe.clientHeight, bounds.bottom);
1199
+ if (visibleRight <= visibleLeft || visibleBottom <= visibleTop) {
1200
+ selectionBox.style.display = "none";
1201
+ toolbar.style.display = "none";
1202
+ hideResizeControls();
1203
+ return;
1204
+ }
1205
+ selectionBox.style.display = "block";
1206
+ selectionBox.style.height = `${visibleBottom - visibleTop}px`;
1207
+ selectionBox.style.left = `${visibleLeft}px`;
1208
+ selectionBox.style.top = `${visibleTop}px`;
1209
+ selectionBox.style.width = `${visibleRight - visibleLeft}px`;
1210
+ const canResizeText = Boolean(selectedTextResize.current);
1211
+ for (const [handle, horizontalPosition] of [
1212
+ [leftResizeHandle, visibleLeft],
1213
+ [rightResizeHandle, visibleRight]
1214
+ ]) {
1215
+ handle.style.display = canResizeText ? "flex" : "none";
1216
+ handle.style.left = `${Math.max(8, Math.min(iframe.clientWidth - 8, horizontalPosition))}px`;
1217
+ handle.style.top = `${visibleTop + (visibleBottom - visibleTop) / 2}px`;
1218
+ }
1219
+ toolbar.style.display = "flex";
1220
+ const toolbarBounds = toolbar.getBoundingClientRect();
1221
+ const toolbarWidth = Math.min(toolbarBounds.width || 260, iframe.clientWidth - 16);
1222
+ const toolbarHeight = toolbarBounds.height || 42;
1223
+ const belowTop = visibleBottom + 8;
1224
+ const aboveTop = visibleTop - toolbarHeight - 8;
1225
+ const toolbarTop = belowTop + toolbarHeight <= iframe.clientHeight ? belowTop : aboveTop >= obstructedTop + 8 ? aboveTop : Math.min(visibleTop + 8, iframe.clientHeight - toolbarHeight - 8);
1226
+ toolbar.style.left = `${Math.max(8, Math.min(visibleLeft, iframe.clientWidth - toolbarWidth - 8))}px`;
1227
+ toolbar.style.top = `${Math.max(obstructedTop + 8, toolbarTop)}px`;
1228
+ };
1229
+ let activeTextResize = null;
1230
+ const startTextResize = (event, side, handle) => {
1231
+ const element = selectedElement;
1232
+ const selection = selectedTextResize.current;
1233
+ const parent = element?.parentElement;
1234
+ if (!element || !selection || !parent) return;
1235
+ event.preventDefault();
1236
+ event.stopPropagation();
1237
+ event.stopImmediatePropagation();
1238
+ stopInlineEditing(true);
1239
+ const bounds = element.getBoundingClientRect();
1240
+ const parentBounds = parent.getBoundingClientRect();
1241
+ const parentStyle = iframe.contentWindow?.getComputedStyle(parent);
1242
+ const paddingLeft = Number.parseFloat(parentStyle?.paddingLeft || "0") || 0;
1243
+ const paddingRight = Number.parseFloat(parentStyle?.paddingRight || "0") || 0;
1244
+ const contentLeft = parentBounds.left + paddingLeft;
1245
+ const contentWidth = Math.max(
1246
+ MIN_TEXT_WIDTH_PX,
1247
+ parentBounds.width - paddingLeft - paddingRight
1248
+ );
1249
+ const contentRight = contentLeft + contentWidth;
1250
+ const startLeft = Math.max(contentLeft, Math.min(bounds.left, contentRight));
1251
+ const startRight = Math.max(startLeft, Math.min(bounds.right, contentRight));
1252
+ activeTextResize = {
1253
+ contentLeft,
1254
+ contentWidth,
1255
+ element,
1256
+ originalStyle: {
1257
+ boxSizing: element.style.boxSizing,
1258
+ marginLeft: element.style.marginLeft,
1259
+ marginRight: element.style.marginRight,
1260
+ maxWidth: element.style.maxWidth,
1261
+ width: element.style.width
1262
+ },
1263
+ selection,
1264
+ side,
1265
+ startLeft,
1266
+ startRight,
1267
+ width: Math.max(MIN_TEXT_WIDTH_PX, startRight - startLeft),
1268
+ widthOffset: Math.max(0, startLeft - contentLeft)
1269
+ };
1270
+ handle.dataset.active = "true";
1271
+ handle.setPointerCapture(event.pointerId);
1272
+ frameDocument.body.style.cursor = "ew-resize";
1273
+ frameDocument.body.style.userSelect = "none";
1274
+ widthLabel.style.display = "block";
1275
+ widthLabel.style.left = `${bounds.left + bounds.width / 2}px`;
1276
+ widthLabel.style.top = `${Math.max(28, bounds.top - 8)}px`;
1277
+ widthLabel.textContent = `${Math.round(activeTextResize.width)} px`;
1278
+ setSelectionLabel(`\u0110ang ch\u1EC9nh chi\u1EC1u r\u1ED9ng \xB7 ${selection.fieldPath}`);
1279
+ };
1280
+ const moveTextResize = (event) => {
1281
+ const resize = activeTextResize;
1282
+ if (!resize) return;
1283
+ event.preventDefault();
1284
+ event.stopPropagation();
1285
+ const minimumWidth = Math.min(MIN_TEXT_WIDTH_PX, resize.contentWidth);
1286
+ const contentRight = resize.contentLeft + resize.contentWidth;
1287
+ const nextLeft = resize.side === "left" ? Math.max(
1288
+ resize.contentLeft,
1289
+ Math.min(event.clientX, resize.startRight - minimumWidth)
1290
+ ) : resize.startLeft;
1291
+ const nextRight = resize.side === "right" ? Math.min(contentRight, Math.max(event.clientX, resize.startLeft + minimumWidth)) : resize.startRight;
1292
+ const nextWidth = Math.max(minimumWidth, nextRight - nextLeft);
1293
+ const width = Math.min(100, Math.max(5, nextWidth / resize.contentWidth * 100));
1294
+ const widthOffset = Math.min(
1295
+ 100 - width,
1296
+ Math.max(0, (nextLeft - resize.contentLeft) / resize.contentWidth * 100)
1297
+ );
1298
+ resize.width = Math.round(width * 1e3) / 1e3;
1299
+ resize.widthOffset = Math.round(widthOffset * 1e3) / 1e3;
1300
+ resize.element.style.boxSizing = "border-box";
1301
+ resize.element.style.marginLeft = `${resize.widthOffset}%`;
1302
+ resize.element.style.marginRight = "auto";
1303
+ resize.element.style.maxWidth = "none";
1304
+ resize.element.style.width = `${resize.width}%`;
1305
+ updateSelectionPosition();
1306
+ const resizedBounds = resize.element.getBoundingClientRect();
1307
+ widthLabel.style.display = "block";
1308
+ widthLabel.style.left = `${resizedBounds.left + resizedBounds.width / 2}px`;
1309
+ widthLabel.style.top = `${Math.max(28, resizedBounds.top - 8)}px`;
1310
+ widthLabel.textContent = `${Math.round(resizedBounds.width)} px`;
1311
+ };
1312
+ const finishTextResize = (event) => {
1313
+ const resize = activeTextResize;
1314
+ if (!resize) return;
1315
+ event.preventDefault();
1316
+ event.stopPropagation();
1317
+ activeTextResize = null;
1318
+ for (const handle of resizeHandles) delete handle.dataset.active;
1319
+ frameDocument.body.style.cursor = "";
1320
+ frameDocument.body.style.userSelect = "";
1321
+ widthLabel.style.display = "none";
1322
+ textWidthSaveController.current?.abort();
1323
+ const controller = new AbortController();
1324
+ textWidthSaveController.current = controller;
1325
+ setSelectionLabel(`\u0110ang t\u1EF1 \u0111\u1ED9ng l\u01B0u chi\u1EC1u r\u1ED9ng \xB7 ${resize.selection.fieldPath}`);
1326
+ void saveTextStylePatch(
1327
+ resize.selection,
1328
+ { width: resize.width, widthOffset: resize.widthOffset },
1329
+ controller.signal
1330
+ ).then(() => {
1331
+ if (controller.signal.aborted) return;
1332
+ setSelectionLabel(`\u0110\xE3 l\u01B0u chi\u1EC1u r\u1ED9ng \xB7 ${Math.round(resize.width)}%`);
1333
+ }).catch((error) => {
1334
+ if (controller.signal.aborted) return;
1335
+ Object.assign(resize.element.style, resize.originalStyle);
1336
+ updateSelectionPosition();
1337
+ setSelectionLabel(
1338
+ error instanceof Error ? `Kh\xF4ng th\u1EC3 l\u01B0u chi\u1EC1u r\u1ED9ng \xB7 ${error.message}` : "Kh\xF4ng th\u1EC3 l\u01B0u chi\u1EC1u r\u1ED9ng \xB7 Th\u1EED l\u1EA1i"
1339
+ );
1340
+ });
1341
+ };
1342
+ const bindResizeHandle = (handle, side) => {
1343
+ const handlePointerDown = (event) => startTextResize(event, side, handle);
1344
+ const stopHandleClick = (event) => {
1345
+ event.preventDefault();
1346
+ event.stopPropagation();
1347
+ event.stopImmediatePropagation();
1348
+ };
1349
+ const resetTextWidth = (event) => {
1350
+ stopHandleClick(event);
1351
+ const element = selectedElement;
1352
+ const selection = selectedTextResize.current;
1353
+ if (!element || !selection) return;
1354
+ const previousStyle = {
1355
+ boxSizing: element.style.boxSizing,
1356
+ marginLeft: element.style.marginLeft,
1357
+ marginRight: element.style.marginRight,
1358
+ maxWidth: element.style.maxWidth,
1359
+ width: element.style.width
1360
+ };
1361
+ element.style.boxSizing = "";
1362
+ element.style.marginLeft = "";
1363
+ element.style.marginRight = "";
1364
+ element.style.maxWidth = "";
1365
+ element.style.width = "";
1366
+ updateSelectionPosition();
1367
+ textWidthSaveController.current?.abort();
1368
+ const controller = new AbortController();
1369
+ textWidthSaveController.current = controller;
1370
+ setSelectionLabel(`\u0110ang tr\u1EA3 chi\u1EC1u r\u1ED9ng v\u1EC1 t\u1EF1 \u0111\u1ED9ng \xB7 ${selection.fieldPath}`);
1371
+ void saveTextStylePatch(
1372
+ selection,
1373
+ { width: void 0, widthOffset: void 0 },
1374
+ controller.signal
1375
+ ).then(() => {
1376
+ if (!controller.signal.aborted) {
1377
+ setSelectionLabel(`Chi\u1EC1u r\u1ED9ng t\u1EF1 \u0111\u1ED9ng \xB7 ${selection.fieldPath}`);
1378
+ }
1379
+ }).catch((error) => {
1380
+ if (controller.signal.aborted) return;
1381
+ Object.assign(element.style, previousStyle);
1382
+ updateSelectionPosition();
1383
+ setSelectionLabel(
1384
+ error instanceof Error ? `Kh\xF4ng th\u1EC3 \u0111\u1EB7t l\u1EA1i chi\u1EC1u r\u1ED9ng \xB7 ${error.message}` : "Kh\xF4ng th\u1EC3 \u0111\u1EB7t l\u1EA1i chi\u1EC1u r\u1ED9ng \xB7 Th\u1EED l\u1EA1i"
1385
+ );
1386
+ });
1387
+ };
1388
+ handle.addEventListener("click", stopHandleClick);
1389
+ handle.addEventListener("dblclick", resetTextWidth);
1390
+ handle.addEventListener("pointerdown", handlePointerDown);
1391
+ handle.addEventListener("pointermove", moveTextResize);
1392
+ handle.addEventListener("pointerup", finishTextResize);
1393
+ handle.addEventListener("pointercancel", finishTextResize);
1394
+ return () => {
1395
+ handle.removeEventListener("click", stopHandleClick);
1396
+ handle.removeEventListener("dblclick", resetTextWidth);
1397
+ handle.removeEventListener("pointerdown", handlePointerDown);
1398
+ handle.removeEventListener("pointermove", moveTextResize);
1399
+ handle.removeEventListener("pointerup", finishTextResize);
1400
+ handle.removeEventListener("pointercancel", finishTextResize);
1401
+ };
1402
+ };
1403
+ const unbindLeftResizeHandle = bindResizeHandle(leftResizeHandle, "left");
1404
+ const unbindRightResizeHandle = bindResizeHandle(rightResizeHandle, "right");
1405
+ const stopInlineEditing = (commit = true) => {
1406
+ const element = selectedElement;
1407
+ if (!element?.hasAttribute("data-wikex-inline-editing")) return;
1408
+ if (inlineInputHandler) element.removeEventListener("input", inlineInputHandler);
1409
+ if (inlineBlurHandler) element.removeEventListener("blur", inlineBlurHandler);
1410
+ if (inlineKeyHandler) element.removeEventListener("keydown", inlineKeyHandler);
1411
+ const nextText = commit ? readInlineText(element) : inlineOriginalText;
1412
+ if (!commit) element.innerText = inlineOriginalText;
1413
+ element.removeAttribute("contenteditable");
1414
+ element.removeAttribute("data-wikex-inline-editing");
1415
+ if (commit && nextText !== inlineOriginalText) {
1416
+ const control = selectedControl.current;
1417
+ const richTextControl = selectedRichTextControl.current;
1418
+ const globalSelection = selectedGlobal.current;
1419
+ if (control) {
1420
+ updatePayloadControl(
1421
+ control,
1422
+ control instanceof HTMLInputElement ? normalizeText(nextText) : nextText
1423
+ );
1424
+ } else if (richTextControl) {
1425
+ updateRichTextControl(richTextControl, nextText);
1426
+ } else if (globalSelection) {
1427
+ const previousText = inlineOriginalText;
1428
+ const savedElement = element;
1429
+ setSelectionLabel(`\u0110ang l\u01B0u \xB7 ${globalSelection.scope}`);
1430
+ void updateGlobalFields(globalSelection.scope, [
1431
+ { path: globalSelection.fieldPath, value: nextText }
1432
+ ]).then(() => {
1433
+ for (const matchingElement of getMatchingPreviewFields(
1434
+ savedElement,
1435
+ globalSelection.fieldPath
1436
+ )) {
1437
+ matchingElement.innerText = nextText;
1438
+ if (globalSelection.scope === "settings" && globalSelection.fieldPath === "email") {
1439
+ matchingElement.closest("a")?.setAttribute("href", `mailto:${nextText}`);
1440
+ }
1441
+ }
1442
+ selectedGlobal.current = { ...globalSelection, text: nextText };
1443
+ setSelectionLabel(`\u0110\xE3 c\u1EADp nh\u1EADt \xB7 ${globalSelection.scope}`);
1444
+ }).catch(() => {
1445
+ if (savedElement.isConnected && readInlineText(savedElement) === nextText) {
1446
+ savedElement.innerText = previousText;
1447
+ }
1448
+ selectedText.current = previousText;
1449
+ setSelectionLabel("Kh\xF4ng th\u1EC3 l\u01B0u n\u1ED9i dung d\xF9ng chung \xB7 Th\u1EED l\u1EA1i");
1450
+ });
1451
+ }
1452
+ selectedText.current = nextText;
1453
+ if (!globalSelection) {
1454
+ setSelectionLabel(`\u0110\xE3 c\u1EADp nh\u1EADt \xB7 ${selectedFieldPath.current || "n\u1ED9i dung"}`);
1455
+ }
1456
+ } else {
1457
+ setSelectionLabel(selectedFieldPath.current || "Ch\u1ECDn m\u1ED9t ph\u1EA7n t\u1EED tr\xEAn trang");
1458
+ }
1459
+ propertiesButton.textContent = "Thu\u1ED9c t\xEDnh";
1460
+ inlineInputHandler = null;
1461
+ inlineBlurHandler = null;
1462
+ inlineKeyHandler = null;
1463
+ };
1464
+ const startInlineEditing = () => {
1465
+ if (!selectedElement) return;
1466
+ if (!selectedControl.current && !selectedRichTextControl.current && !selectedGlobal.current) {
1467
+ openInspector();
1468
+ return;
1469
+ }
1470
+ stopInlineEditing(false);
1471
+ inlineOriginalText = readInlineText(selectedElement);
1472
+ selectedElement.setAttribute("contenteditable", "plaintext-only");
1473
+ selectedElement.dataset.wikexInlineEditing = "true";
1474
+ selectedElement.focus();
1475
+ inlineInputHandler = () => {
1476
+ updateSelectionPosition();
1477
+ setSelectionLabel("\u0110ang s\u1EEDa tr\u1EF1c ti\u1EBFp \xB7 Enter \u0111\u1EC3 l\u01B0u \xB7 Esc \u0111\u1EC3 h\u1EE7y");
1478
+ };
1479
+ inlineBlurHandler = () => stopInlineEditing(true);
1480
+ inlineKeyHandler = (event) => {
1481
+ if (event.key === "Escape") {
1482
+ event.preventDefault();
1483
+ stopInlineEditing(false);
1484
+ return;
1485
+ }
1486
+ if (event.key === "Enter" && !event.shiftKey) {
1487
+ event.preventDefault();
1488
+ stopInlineEditing(true);
1489
+ }
1490
+ };
1491
+ selectedElement.addEventListener("input", inlineInputHandler);
1492
+ selectedElement.addEventListener("blur", inlineBlurHandler);
1493
+ selectedElement.addEventListener("keydown", inlineKeyHandler);
1494
+ propertiesButton.textContent = "Thu\u1ED9c t\xEDnh";
1495
+ const selection = frameDocument?.getSelection();
1496
+ const range = frameDocument?.createRange();
1497
+ if (selection && range) {
1498
+ range.selectNodeContents(selectedElement);
1499
+ selection.removeAllRanges();
1500
+ selection.addRange(range);
1501
+ }
1502
+ };
1503
+ const selectElement = (element) => {
1504
+ stopInlineEditing(true);
1505
+ selectedElement?.removeAttribute("data-wikex-selected");
1506
+ selectedElement = element;
1507
+ selectedElement.dataset.wikexSelected = "true";
1508
+ selectedPreviewElement.current = element;
1509
+ const baseFieldPath = element.dataset.wikexField ?? element.closest("[data-wikex-field]")?.dataset.wikexField;
1510
+ const text = normalizeText(element.innerText || element.textContent || "");
1511
+ const fieldPath = inferEditableFieldPath(element, baseFieldPath);
1512
+ const documentTarget = getEditorDocumentTarget();
1513
+ const isAggregateSelection = isAggregateInspectorSelection(documentTarget, fieldPath);
1514
+ const supportsTextStyles = Boolean(text) && element.matches(EDITABLE_PREVIEW_SELECTOR);
1515
+ const elementStyleKey = supportsTextStyles ? fieldPath || getEditableElementStyleKey(element) : void 0;
1516
+ const resizeStyleKey = isAggregateSelection ? getEditableElementStyleKey(element) : elementStyleKey;
1517
+ const elementScope = element.closest("[data-wikex-scope]")?.dataset.wikexScope;
1518
+ const configuredGlobalScope = element.dataset.wikexGlobal ?? element.closest("[data-wikex-global]")?.dataset.wikexGlobal;
1519
+ const fieldGlobalScope = isEditableGlobalScope(configuredGlobalScope) ? configuredGlobalScope : isEditableGlobalScope(elementScope) ? elementScope : void 0;
1520
+ const externalGlobalScope = fieldGlobalScope && fieldGlobalScope !== editorScopeRef.current ? fieldGlobalScope : void 0;
1521
+ const control = externalGlobalScope ? null : getFieldControl(fieldPath, text);
1522
+ const richTextControl = control ? null : getRichTextControl(fieldPath, text);
1523
+ const interactiveElement = element.closest(
1524
+ "a, button"
1525
+ );
1526
+ const globalSelection = externalGlobalScope && fieldPath ? {
1527
+ fieldPath,
1528
+ href: interactiveElement instanceof HTMLAnchorElement ? interactiveElement.getAttribute("href") || void 0 : void 0,
1529
+ scope: externalGlobalScope,
1530
+ supportsLink: Boolean(interactiveElement),
1531
+ text
1532
+ } : null;
1533
+ const textInspector = !globalSelection && !isAggregateSelection && documentTarget && elementStyleKey ? {
1534
+ ...documentTarget,
1535
+ canEditText: Boolean(control),
1536
+ fieldPath: elementStyleKey,
1537
+ multiline: control instanceof HTMLTextAreaElement,
1538
+ tagName: element.tagName.toLowerCase(),
1539
+ text
1540
+ } : null;
1541
+ const textResize = !globalSelection && documentTarget && resizeStyleKey && element.matches(RESIZABLE_TEXT_SELECTOR) ? {
1542
+ ...documentTarget,
1543
+ canEditText: false,
1544
+ fieldPath: resizeStyleKey,
1545
+ multiline: false,
1546
+ tagName: element.tagName.toLowerCase(),
1547
+ text
1548
+ } : null;
1549
+ const tagLabel = element.tagName.toLowerCase();
1550
+ const semanticLabel = tagLabel === "img" || element.querySelector("img") ? "H\xECnh \u1EA3nh" : /^h[1-6]$/.test(tagLabel) ? "Ti\xEAu \u0111\u1EC1" : tagLabel === "p" ? "\u0110o\u1EA1n v\u0103n" : interactiveElement ? "Li\xEAn k\u1EBFt / n\xFAt" : "N\u1ED9i dung";
1551
+ const readableLabel = fieldPath || control?.name || `${semanticLabel}${text ? ` \xB7 ${text.slice(0, 36)}` : ""}`;
1552
+ selectedControl.current = control;
1553
+ selectedRichTextControl.current = externalGlobalScope ? null : richTextControl;
1554
+ selectedFieldPath.current = fieldPath;
1555
+ selectedGlobal.current = globalSelection;
1556
+ selectedTextInspector.current = textInspector;
1557
+ selectedTextResize.current = textResize;
1558
+ setGlobalInspectorSelection(globalSelection);
1559
+ setTextInspectorSelection(textInspector);
1560
+ selectedText.current = text;
1561
+ propertiesButton.disabled = false;
1562
+ label.textContent = readableLabel;
1563
+ setSelectionLabel(readableLabel);
1564
+ updateSelectionPosition();
1565
+ };
1566
+ const revealFieldInPreview = (fieldPath, attempt = 0) => {
1567
+ const target = Array.from(
1568
+ frameDocument?.querySelectorAll("[data-wikex-field]") ?? []
1569
+ ).find((element) => element.dataset.wikexField === fieldPath);
1570
+ if (!target) {
1571
+ if (attempt < 24) {
1572
+ window.setTimeout(() => revealFieldInPreview(fieldPath, attempt + 1), 150);
1573
+ }
1574
+ return;
1575
+ }
1576
+ target.scrollIntoView({ behavior: "smooth", block: "center" });
1577
+ selectElement(target);
1578
+ };
1579
+ revealPreviewField.current = revealFieldInPreview;
1580
+ const handleFrameClick = (event) => {
1581
+ const rawTarget = event.target;
1582
+ if (!rawTarget || typeof rawTarget.closest !== "function") return;
1583
+ const rawElement = rawTarget;
1584
+ if (rawElement.closest("#wikex-selection-toolbar, .wikex-text-resize-handle")) {
1585
+ return;
1586
+ }
1587
+ if (rawElement.closest('[data-wikex-inline-editing="true"]')) return;
1588
+ const interactiveCandidate = rawElement.closest(
1589
+ EDITABLE_INTERACTIVE_SELECTOR
1590
+ );
1591
+ const fieldCandidate = rawElement.closest("[data-wikex-field]") ?? interactiveCandidate?.querySelector("[data-wikex-field]");
1592
+ const semanticCandidate = rawElement.closest(EDITABLE_PREVIEW_SELECTOR);
1593
+ const candidate = fieldCandidate?.matches(EDITABLE_PREVIEW_SELECTOR) ? fieldCandidate : semanticCandidate ?? fieldCandidate;
1594
+ if (!candidate || candidate.closest("[data-wikex-no-edit]")) return;
1595
+ const candidateScope = candidate.closest("[data-wikex-scope]")?.dataset.wikexScope;
1596
+ const isSharedGlobalInPage = editorScopeRef.current === "content" && (candidateScope === "header" || candidateScope === "footer");
1597
+ if (candidateScope && candidateScope !== editorScopeRef.current && !isSharedGlobalInPage) {
1598
+ return;
1599
+ }
1600
+ if (interactiveCandidate) event.preventDefault();
1601
+ event.stopPropagation();
1602
+ event.stopImmediatePropagation();
1603
+ selectElement(candidate);
1604
+ };
1605
+ const handleDoubleClick = (event) => {
1606
+ const eventTarget = event.target;
1607
+ if (eventTarget instanceof Element && eventTarget.closest(".wikex-text-resize-handle")) {
1608
+ return;
1609
+ }
1610
+ event.preventDefault();
1611
+ startInlineEditing();
1612
+ };
1613
+ const stopToolbarEvent = (event) => event.stopPropagation();
1614
+ const keepEditorFocus = (event) => event.preventDefault();
1615
+ const handlePropertiesClick = () => {
1616
+ stopInlineEditing(true);
1617
+ openInspector();
1618
+ };
1619
+ propertiesButton.addEventListener("click", handlePropertiesClick);
1620
+ toolbar.addEventListener("click", stopToolbarEvent);
1621
+ toolbar.addEventListener("pointerdown", keepEditorFocus);
1622
+ frameDocument.addEventListener("click", handleFrameClick, true);
1623
+ frameDocument.addEventListener("dblclick", handleDoubleClick, true);
1624
+ frameDocument.addEventListener("scroll", updateSelectionPosition, true);
1625
+ iframe.contentWindow?.addEventListener("resize", updateSelectionPosition);
1626
+ cleanupFrame = () => {
1627
+ stopInlineEditing(false);
1628
+ textWidthSaveController.current?.abort();
1629
+ activeTextResize = null;
1630
+ if (frameDocument?.body) {
1631
+ frameDocument.body.style.cursor = "";
1632
+ frameDocument.body.style.userSelect = "";
1633
+ }
1634
+ selectedElement?.removeAttribute("data-wikex-selected");
1635
+ selectedTextResize.current = null;
1636
+ unbindLeftResizeHandle();
1637
+ unbindRightResizeHandle();
1638
+ propertiesButton.removeEventListener("click", handlePropertiesClick);
1639
+ toolbar.removeEventListener("click", stopToolbarEvent);
1640
+ toolbar.removeEventListener("pointerdown", keepEditorFocus);
1641
+ frameDocument?.removeEventListener("click", handleFrameClick, true);
1642
+ frameDocument?.removeEventListener("dblclick", handleDoubleClick, true);
1643
+ frameDocument?.removeEventListener("scroll", updateSelectionPosition, true);
1644
+ iframe.contentWindow?.removeEventListener("resize", updateSelectionPosition);
1645
+ selectionBox.remove();
1646
+ leftResizeHandle.remove();
1647
+ rightResizeHandle.remove();
1648
+ widthLabel.remove();
1649
+ toolbar.remove();
1650
+ style.remove();
1651
+ if (revealPreviewField.current === revealFieldInPreview) {
1652
+ revealPreviewField.current = () => void 0;
1653
+ }
1654
+ selectedPreviewElement.current = null;
1655
+ selectedTextInspector.current = null;
1656
+ };
1657
+ };
1658
+ iframe.addEventListener("load", installFrameEditor);
1659
+ installFrameEditor();
1660
+ cleanupActiveIframe = () => {
1661
+ iframe.removeEventListener("load", installFrameEditor);
1662
+ cleanupFrame();
1663
+ };
1664
+ };
1665
+ const scheduleBindIframe = () => {
1666
+ if (bindFrame) return;
1667
+ bindFrame = window.requestAnimationFrame(() => {
1668
+ bindFrame = 0;
1669
+ bindIframe();
1670
+ });
1671
+ };
1672
+ bindIframe();
1673
+ const observer = new MutationObserver(scheduleBindIframe);
1674
+ observer.observe(previewWrapper, { childList: true, subtree: true });
1675
+ return () => {
1676
+ observer.disconnect();
1677
+ window.cancelAnimationFrame(bindFrame);
1678
+ cleanupActiveIframe();
1679
+ };
1680
+ }, [openInspector, previewWrapper]);
1681
+ useEffect3(() => {
1682
+ let activeBlocksFieldID = "";
1683
+ let initialized = false;
1684
+ let knownRows = /* @__PURE__ */ new Set();
1685
+ let revealTimer;
1686
+ let scanFrame = 0;
1687
+ const scanRows = () => {
1688
+ const blocksField = getVisibleBlocksField();
1689
+ if (!blocksField) return;
1690
+ const rows = Array.from(
1691
+ blocksField.querySelectorAll(
1692
+ '.blocks-field__rows > [id^="layout-row-"], .blocks-field__rows > [id^="customLayout-row-"]'
1693
+ )
1694
+ );
1695
+ if (!initialized || activeBlocksFieldID !== blocksField.id) {
1696
+ activeBlocksFieldID = blocksField.id;
1697
+ knownRows = new Set(rows.map((row) => row.id));
1698
+ initialized = true;
1699
+ return;
1700
+ }
1701
+ if (!rows.length) {
1702
+ knownRows.clear();
1703
+ return;
1704
+ }
1705
+ const addedRows = rows.filter((row) => !knownRows.has(row.id));
1706
+ knownRows = new Set(rows.map((row) => row.id));
1707
+ const addedRow = addedRows.at(-1);
1708
+ const match = addedRow?.id.match(/^(customLayout|layout)-row-(\d+)$/);
1709
+ if (!addedRow || !match) return;
1710
+ const fieldPath = `${match[1]}.${match[2]}`;
1711
+ setSelectionLabel(fieldPath);
1712
+ scrollEditorTo(addedRow);
1713
+ window.clearTimeout(revealTimer);
1714
+ revealTimer = window.setTimeout(() => revealPreviewField.current(fieldPath), 300);
1715
+ };
1716
+ const scheduleScanRows = () => {
1717
+ if (scanFrame) return;
1718
+ scanFrame = window.requestAnimationFrame(() => {
1719
+ scanFrame = 0;
1720
+ scanRows();
1721
+ });
1722
+ };
1723
+ scanRows();
1724
+ const observer = new MutationObserver(scheduleScanRows);
1725
+ observer.observe(document.body, { childList: true, subtree: true });
1726
+ return () => {
1727
+ observer.disconnect();
1728
+ window.cancelAnimationFrame(scanFrame);
1729
+ window.clearTimeout(revealTimer);
1730
+ };
1731
+ }, []);
1732
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1733
+ createPortal(
1734
+ /* @__PURE__ */ jsxs3(Fragment2, { children: [
1735
+ /* @__PURE__ */ jsxs3("aside", { "aria-label": "C\xF4ng c\u1EE5 d\u1EF1ng trang", className: "wikex-editor-rail", children: [
1736
+ /* @__PURE__ */ jsx3("div", { "aria-label": "Wikex Editor", className: "wikex-editor-rail__brand", children: "W" }),
1737
+ /* @__PURE__ */ jsxs3(
1738
+ "button",
1739
+ {
1740
+ className: "wikex-editor-tool",
1741
+ onClick: isGlobalEditor ? openInspector : openElements,
1742
+ type: "button",
1743
+ children: [
1744
+ /* @__PURE__ */ jsx3(EditorIcon, { name: "elements" }),
1745
+ /* @__PURE__ */ jsx3("span", { children: isGlobalEditor ? "N\u1ED9i dung" : "Th\xEAm kh\u1ED1i" })
1746
+ ]
1747
+ }
1748
+ ),
1749
+ /* @__PURE__ */ jsxs3(
1750
+ "button",
1751
+ {
1752
+ className: "wikex-editor-tool",
1753
+ onClick: isGlobalEditor ? openInspector : openSections,
1754
+ type: "button",
1755
+ children: [
1756
+ /* @__PURE__ */ jsx3(EditorIcon, { name: "sections" }),
1757
+ /* @__PURE__ */ jsx3("span", { children: isGlobalEditor ? "Li\xEAn k\u1EBFt" : "B\u1ED1 c\u1EE5c" })
1758
+ ]
1759
+ }
1760
+ ),
1761
+ /* @__PURE__ */ jsxs3("a", { className: "wikex-editor-tool", href: pagesHref, children: [
1762
+ /* @__PURE__ */ jsx3(EditorIcon, { name: "pages" }),
1763
+ /* @__PURE__ */ jsx3("span", { children: "Trang" })
1764
+ ] })
1765
+ ] }),
1766
+ /* @__PURE__ */ jsxs3("div", { className: "wikex-editor-canvas-title", children: [
1767
+ /* @__PURE__ */ jsx3("strong", { children: "Tr\xECnh ch\u1EC9nh s\u1EEDa Wikex" }),
1768
+ /* @__PURE__ */ jsx3("span", { children: selectionLabel }),
1769
+ /* @__PURE__ */ jsx3("span", { className: "wikex-editor-canvas-title__hint", children: "Nh\u1EA5p \u0111\xFAp v\u0103n b\u1EA3n \u0111\u1EC3 s\u1EEDa" })
1770
+ ] })
1771
+ ] }),
1772
+ previewWrapper
1773
+ ),
1774
+ inspectorOpen && globalInspectorSelection && createPortal(
1775
+ /* @__PURE__ */ jsx3(
1776
+ GlobalInlineInspector,
1777
+ {
1778
+ onApply: applyGlobalInspectorValues,
1779
+ selection: globalInspectorSelection
1780
+ },
1781
+ `${globalInspectorSelection.scope}:${globalInspectorSelection.fieldPath}`
1782
+ ),
1783
+ container
1784
+ ),
1785
+ inspectorOpen && textInspectorSelection && createPortal(
1786
+ /* @__PURE__ */ jsx3(
1787
+ TextStyleInspector,
1788
+ {
1789
+ onApply: applyTextInspectorValues,
1790
+ onTextApply: applyTextInspectorText,
1791
+ selection: textInspectorSelection
1792
+ },
1793
+ `${textInspectorSelection.kind === "collection" ? `${textInspectorSelection.collection}:${textInspectorSelection.documentID}` : `global:${textInspectorSelection.global}`}:${textInspectorSelection.fieldPath}`
1794
+ ),
1795
+ container
1796
+ ),
1797
+ inspectorOpen && createPortal(
1798
+ /* @__PURE__ */ jsx3(
1799
+ "button",
1800
+ {
1801
+ "aria-label": "\u0110\xF3ng b\u1EA3ng thu\u1ED9c t\xEDnh",
1802
+ className: "wikex-inspector-close",
1803
+ onClick: () => setInspectorOpen(false),
1804
+ title: "\u0110\xF3ng b\u1EA3ng thu\u1ED9c t\xEDnh",
1805
+ type: "button",
1806
+ children: /* @__PURE__ */ jsx3("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3("path", { d: "M6 6l12 12M18 6 6 18" }) })
1807
+ }
1808
+ ),
1809
+ container
1810
+ ),
1811
+ blockPickerScope && createPortal(
1812
+ /* @__PURE__ */ jsx3(
1813
+ BlockPickerModal,
1814
+ {
1815
+ onClose: closeBlockPicker,
1816
+ onSelect: selectBlock,
1817
+ options: getBlockPickerOptions(blockPickerScope),
1818
+ title: blockPickerScope === "post" ? "Ch\u1ECDn component cho b\xE0i vi\u1EBFt" : "Ch\u1ECDn component cho trang"
1819
+ }
1820
+ ),
1821
+ document.body
1822
+ )
1823
+ ] });
1824
+ };
1825
+ var clampEditWidth = (width, containerWidth) => {
1826
+ const maximumWidth = Math.max(MIN_EDIT_WIDTH, containerWidth - HANDLE_WIDTH - MIN_PREVIEW_WIDTH);
1827
+ return Math.min(Math.max(width, MIN_EDIT_WIDTH), maximumWidth);
1828
+ };
1829
+ var readStoredRatio = () => {
1830
+ const storedRatio = Number(
1831
+ window.localStorage.getItem(STORAGE_KEY) ?? window.localStorage.getItem(LEGACY_STORAGE_KEY)
1832
+ );
1833
+ return Number.isFinite(storedRatio) && storedRatio > 0 && storedRatio < 1 ? storedRatio : DEFAULT_EDIT_RATIO;
1834
+ };
1835
+ var setEditWidth = (container, requestedWidth) => {
1836
+ const containerWidth = container.getBoundingClientRect().width;
1837
+ const width = clampEditWidth(requestedWidth, containerWidth);
1838
+ const ratio = width / containerWidth;
1839
+ container.style.setProperty("--live-preview-edit-width", `${width}px`);
1840
+ return ratio;
1841
+ };
1842
+ var LivePreviewResizeHandle = ({ container }) => {
1843
+ const dragState = useRef2(null);
1844
+ const latestRatio = useRef2(DEFAULT_EDIT_RATIO);
1845
+ const [isDragging, setIsDragging] = useState3(false);
1846
+ const [ratio, setRatio] = useState3(DEFAULT_EDIT_RATIO);
1847
+ useEffect3(() => {
1848
+ const storedRatio = readStoredRatio();
1849
+ const initialRatio = setEditWidth(
1850
+ container,
1851
+ container.getBoundingClientRect().width * storedRatio
1852
+ );
1853
+ latestRatio.current = initialRatio;
1854
+ setRatio(initialRatio);
1855
+ const resizeObserver = new ResizeObserver(() => {
1856
+ if (!dragState.current) {
1857
+ const nextRatio = setEditWidth(
1858
+ container,
1859
+ container.getBoundingClientRect().width * readStoredRatio()
1860
+ );
1861
+ latestRatio.current = nextRatio;
1862
+ setRatio(nextRatio);
1863
+ }
1864
+ });
1865
+ resizeObserver.observe(container);
1866
+ return () => resizeObserver.disconnect();
1867
+ }, [container]);
1868
+ const finishDragging = (event) => {
1869
+ if (dragState.current?.pointerId !== event.pointerId) return;
1870
+ window.localStorage.setItem(STORAGE_KEY, String(latestRatio.current));
1871
+ dragState.current = null;
1872
+ setIsDragging(false);
1873
+ };
1874
+ const handlePointerDown = (event) => {
1875
+ const bounds = container.getBoundingClientRect();
1876
+ event.preventDefault();
1877
+ event.currentTarget.setPointerCapture(event.pointerId);
1878
+ dragState.current = {
1879
+ containerLeft: bounds.left,
1880
+ pointerId: event.pointerId
1881
+ };
1882
+ setIsDragging(true);
1883
+ };
1884
+ const handlePointerMove = (event) => {
1885
+ const drag = dragState.current;
1886
+ if (!drag || drag.pointerId !== event.pointerId) return;
1887
+ const nextRatio = setEditWidth(container, event.clientX - drag.containerLeft);
1888
+ latestRatio.current = nextRatio;
1889
+ setRatio(nextRatio);
1890
+ };
1891
+ const handleKeyDown = (event) => {
1892
+ if (!["ArrowLeft", "ArrowRight", "Home"].includes(event.key)) return;
1893
+ event.preventDefault();
1894
+ const containerWidth = container.getBoundingClientRect().width;
1895
+ const currentWidth = containerWidth * ratio;
1896
+ const step = event.shiftKey ? 40 : 10;
1897
+ const requestedWidth = event.key === "Home" ? containerWidth * DEFAULT_EDIT_RATIO : currentWidth + (event.key === "ArrowLeft" ? -step : step);
1898
+ const nextRatio = setEditWidth(container, requestedWidth);
1899
+ latestRatio.current = nextRatio;
1900
+ setRatio(nextRatio);
1901
+ window.localStorage.setItem(STORAGE_KEY, String(nextRatio));
1902
+ };
1903
+ const resetWidth = () => {
1904
+ const nextRatio = setEditWidth(
1905
+ container,
1906
+ container.getBoundingClientRect().width * DEFAULT_EDIT_RATIO
1907
+ );
1908
+ latestRatio.current = nextRatio;
1909
+ setRatio(nextRatio);
1910
+ window.localStorage.setItem(STORAGE_KEY, String(nextRatio));
1911
+ };
1912
+ return /* @__PURE__ */ jsx3(
1913
+ "div",
1914
+ {
1915
+ "aria-label": "Thay \u0111\u1ED5i \u0111\u1ED9 r\u1ED9ng Live Preview",
1916
+ "aria-orientation": "vertical",
1917
+ "aria-valuemax": Math.round(
1918
+ (1 - MIN_PREVIEW_WIDTH / container.getBoundingClientRect().width) * 100
1919
+ ),
1920
+ "aria-valuemin": Math.round(MIN_EDIT_WIDTH / container.getBoundingClientRect().width * 100),
1921
+ "aria-valuenow": Math.round(ratio * 100),
1922
+ className: `live-preview-resizer${isDragging ? " live-preview-resizer--dragging" : ""}`,
1923
+ onDoubleClick: resetWidth,
1924
+ onKeyDown: handleKeyDown,
1925
+ onPointerCancel: finishDragging,
1926
+ onPointerDown: handlePointerDown,
1927
+ onPointerMove: handlePointerMove,
1928
+ onPointerUp: finishDragging,
1929
+ role: "separator",
1930
+ tabIndex: 0,
1931
+ title: "K\xE9o \u0111\u1EC3 \u0111\u1ED5i \u0111\u1ED9 r\u1ED9ng preview \xB7 Nh\u1EA5p \u0111\xFAp \u0111\u1EC3 \u0111\u1EB7t l\u1EA1i",
1932
+ children: /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", className: "live-preview-resizer__grip" })
1933
+ }
1934
+ );
1935
+ };
1936
+ var LivePreviewResizer = ({ children }) => {
1937
+ const [targets, setTargets] = useState3(null);
1938
+ useEffect3(() => {
1939
+ let transitionTimer = 0;
1940
+ const clearTransitionState = () => {
1941
+ document.documentElement.classList.remove("wikex-live-preview-switching");
1942
+ document.documentElement.removeAttribute("data-wikex-live-preview-target");
1943
+ };
1944
+ const prepareLivePreviewToggle = (event) => {
1945
+ const eventTarget = event.target;
1946
+ if (!(eventTarget instanceof Element) || !eventTarget.closest("#live-preview-toggler")) {
1947
+ return;
1948
+ }
1949
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
1950
+ const editor = document.querySelector(".collection-edit");
1951
+ if (!editor) return;
1952
+ window.clearTimeout(transitionTimer);
1953
+ document.documentElement.dataset.wikexLivePreviewTarget = editor.classList.contains(
1954
+ "collection-edit--is-live-previewing"
1955
+ ) ? "edit" : "preview";
1956
+ document.documentElement.classList.add("wikex-live-preview-switching");
1957
+ transitionTimer = window.setTimeout(clearTransitionState, LIVE_PREVIEW_TRANSITION_MS + 80);
1958
+ };
1959
+ document.addEventListener("click", prepareLivePreviewToggle, true);
1960
+ return () => {
1961
+ document.removeEventListener("click", prepareLivePreviewToggle, true);
1962
+ window.clearTimeout(transitionTimer);
1963
+ clearTransitionState();
1964
+ };
1965
+ }, []);
1966
+ useEffect3(() => {
1967
+ const syncVisualEditorState = () => {
1968
+ const pathname = window.location.pathname;
1969
+ const isVisualEditor = isVisualEditorRoute(pathname);
1970
+ const editor = document.querySelector(".collection-edit");
1971
+ const isActive = Boolean(
1972
+ isVisualEditor && editor?.classList.contains("collection-edit--is-live-previewing")
1973
+ );
1974
+ editor?.toggleAttribute("data-wikex-visual-editor", isActive);
1975
+ if (editor && isActive) editor.dataset.wikexVisualEditor = "true";
1976
+ document.documentElement.classList.toggle("wikex-visual-preview-active", isActive);
1977
+ };
1978
+ const findTargets = () => {
1979
+ syncVisualEditorState();
1980
+ const previewWindow = document.querySelector(
1981
+ ".collection-edit__main-wrapper > .live-preview-window"
1982
+ );
1983
+ const pathname = window.location.pathname;
1984
+ const isVisualEditor = isVisualEditorRoute(pathname);
1985
+ const scope = getEditorScope(pathname);
1986
+ const container = previewWindow?.parentElement ?? null;
1987
+ const editor = previewWindow?.closest(".collection-edit") ?? null;
1988
+ const previewWrapper = previewWindow?.querySelector(
1989
+ ".live-preview-window__wrapper"
1990
+ );
1991
+ const activeIframe = previewWrapper?.querySelector("#live-preview-iframe");
1992
+ const isLivePreviewing = editor?.classList.contains("collection-edit--is-live-previewing");
1993
+ const nextTargets = isVisualEditor && isLivePreviewing && container && editor && previewWrapper && activeIframe ? { container, editor, previewWrapper, route: pathname, scope } : null;
1994
+ setTargets((currentTargets) => {
1995
+ if (!currentTargets || !nextTargets)
1996
+ return currentTargets === nextTargets ? currentTargets : nextTargets;
1997
+ const targetsAreUnchanged = currentTargets.container === nextTargets.container && currentTargets.editor === nextTargets.editor && currentTargets.previewWrapper === nextTargets.previewWrapper && currentTargets.route === nextTargets.route && currentTargets.scope === nextTargets.scope;
1998
+ return targetsAreUnchanged ? currentTargets : nextTargets;
1999
+ });
2000
+ };
2001
+ findTargets();
2002
+ let scanFrame = 0;
2003
+ const scheduleFindTargets = () => {
2004
+ if (scanFrame) return;
2005
+ scanFrame = window.requestAnimationFrame(() => {
2006
+ scanFrame = 0;
2007
+ findTargets();
2008
+ });
2009
+ };
2010
+ const previewStructureSelector = ".collection-edit, .live-preview-window, .live-preview-window__wrapper, #live-preview-iframe";
2011
+ const touchesPreviewStructure = (mutation) => {
2012
+ if (mutation.type === "attributes" && mutation.target instanceof HTMLElement && mutation.target.classList.contains("collection-edit")) {
2013
+ return true;
2014
+ }
2015
+ if (mutation.type !== "childList") return false;
2016
+ return [...mutation.addedNodes, ...mutation.removedNodes].some(
2017
+ (node) => node instanceof HTMLElement && (node.matches(previewStructureSelector) || Boolean(node.querySelector(previewStructureSelector)))
2018
+ );
2019
+ };
2020
+ const observer = new MutationObserver((mutations) => {
2021
+ if (mutations.some(touchesPreviewStructure)) {
2022
+ window.cancelAnimationFrame(scanFrame);
2023
+ scanFrame = 0;
2024
+ findTargets();
2025
+ return;
2026
+ }
2027
+ scheduleFindTargets();
2028
+ });
2029
+ observer.observe(document.body, {
2030
+ attributeFilter: ["class"],
2031
+ attributes: true,
2032
+ childList: true,
2033
+ subtree: true
2034
+ });
2035
+ return () => {
2036
+ observer.disconnect();
2037
+ window.cancelAnimationFrame(scanFrame);
2038
+ document.documentElement.classList.remove("wikex-visual-preview-active");
2039
+ };
2040
+ }, []);
2041
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
2042
+ children,
2043
+ targets && /* @__PURE__ */ jsxs3(Fragment2, { children: [
2044
+ createPortal(
2045
+ /* @__PURE__ */ jsx3(LivePreviewResizeHandle, { container: targets.container }),
2046
+ targets.container
2047
+ ),
2048
+ /* @__PURE__ */ jsx3(WixEditorChrome, { ...targets }, targets.route)
2049
+ ] })
2050
+ ] });
2051
+ };
2052
+ var LivePreviewEditor_default = LivePreviewResizer;
2053
+
2054
+ export {
2055
+ readGlobal,
2056
+ updateGlobalFields,
2057
+ GlobalInlineInspector,
2058
+ saveTextStylePatch,
2059
+ TextStyleInspector,
2060
+ configureLivePreview,
2061
+ LivePreviewEditor_default
2062
+ };