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