@micelio/strapi-plugin-core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +5 -3
  2. package/dist/admin/MicelioLayout-Bmu5xCw9.js +1023 -0
  3. package/dist/admin/MicelioLayout-DMMsKnMo.mjs +1021 -0
  4. package/dist/admin/PreviewHeaderAction-BaduMvYy.mjs +61 -0
  5. package/dist/admin/PreviewHeaderAction-CGHycv4I.js +61 -0
  6. package/dist/admin/{en-D-T8z3qh.js → en-BDVuq0fN.js} +37 -1
  7. package/dist/admin/{en-C2O-S2ym.mjs → en-Bahc6y6N.mjs} +37 -1
  8. package/dist/admin/{es-RV0vkOHh.js → es-DStBGkm7.js} +37 -1
  9. package/dist/admin/{es-CQ2tv9N8.mjs → es-nUHBIoZG.mjs} +37 -1
  10. package/dist/admin/{index-D3TGbaQ7.js → index-D_Dbvo2F.js} +8 -2
  11. package/dist/admin/{index-CiSujTES.mjs → index-Dwr15ZsB.mjs} +8 -2
  12. package/dist/admin/index.js +1 -1
  13. package/dist/admin/index.mjs +1 -1
  14. package/dist/admin/permissions-BAI0qDr9.js +24 -0
  15. package/dist/admin/permissions-CYirM30e.mjs +25 -0
  16. package/dist/admin/src/components/EyeIcon.d.ts +8 -0
  17. package/dist/admin/src/components/PreviewFrame.d.ts +47 -0
  18. package/dist/admin/src/components/PreviewHeaderAction.d.ts +28 -0
  19. package/dist/admin/src/hooks/useEntry.d.ts +31 -0
  20. package/dist/admin/src/hooks/usePreviewSource.d.ts +31 -0
  21. package/dist/admin/src/pages/MicelioLayout.d.ts +2 -1
  22. package/dist/admin/src/pages/PreviewPage.d.ts +2 -0
  23. package/dist/admin/src/permissions.d.ts +5 -0
  24. package/dist/admin/src/translations/en.json.d.ts +37 -1
  25. package/dist/admin/src/utils/fetchError.d.ts +2 -0
  26. package/dist/admin/src/utils/previewAction.d.ts +22 -0
  27. package/dist/admin/src/utils/previewPane.d.ts +59 -0
  28. package/dist/server/index.js +796 -105
  29. package/dist/server/index.mjs +796 -105
  30. package/dist/server/src/constants/preview.d.ts +2 -0
  31. package/dist/server/src/constants/taxonomy.d.ts +13 -0
  32. package/dist/server/src/content-types/category/schema.json.d.ts +78 -0
  33. package/dist/server/src/content-types/index.d.ts +125 -2
  34. package/dist/server/src/content-types/preview-credential/schema.json.d.ts +7 -6
  35. package/dist/server/src/content-types/tag/schema.json.d.ts +46 -0
  36. package/dist/server/src/controllers/index.d.ts +6 -0
  37. package/dist/server/src/editorial/transitions.d.ts +5 -2
  38. package/dist/server/src/index.d.ts +140 -2
  39. package/dist/server/src/migrations/rename-legacy-uids.d.ts +26 -0
  40. package/dist/server/src/migrations/site-backfill.d.ts +13 -0
  41. package/dist/server/src/preview/credentials.d.ts +7 -5
  42. package/dist/server/src/preview/draft-query.d.ts +62 -0
  43. package/dist/server/src/preview/revocation.d.ts +9 -1
  44. package/dist/server/src/preview/shortcut.d.ts +24 -0
  45. package/dist/server/src/routes/index.d.ts +2 -0
  46. package/dist/server/src/routes/taxonomy.d.ts +12 -0
  47. package/dist/server/src/services/index.d.ts +6 -0
  48. package/dist/server/src/services/preview.d.ts +25 -6
  49. package/dist/server/src/site-scope/documents.d.ts +13 -3
  50. package/dist/server/src/types/hooks.d.ts +5 -4
  51. package/dist/server/src/types/preview.d.ts +35 -10
  52. package/package.json +4 -3
  53. package/dist/admin/MicelioLayout-3sKGPS_0.mjs +0 -443
  54. package/dist/admin/MicelioLayout-DXzRS4l3.js +0 -443
@@ -0,0 +1,1023 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const designSystem = require("@strapi/design-system");
5
+ const admin = require("@strapi/strapi/admin");
6
+ const react = require("react");
7
+ const reactIntl = require("react-intl");
8
+ const reactRouterDom = require("react-router-dom");
9
+ const permissions = require("./permissions-BAI0qDr9.js");
10
+ const index = require("./index-D_Dbvo2F.js");
11
+ const styled = require("styled-components");
12
+ const zod = require("zod");
13
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
14
+ const styled__default = /* @__PURE__ */ _interopDefault(styled);
15
+ function useAdminPages() {
16
+ const client = admin.useFetchClient();
17
+ const clientRef = react.useRef(client);
18
+ clientRef.current = client;
19
+ const [state, setState] = react.useState({ pages: [], hasFailed: false, isLoading: true });
20
+ react.useEffect(() => {
21
+ let isCurrent = true;
22
+ void (async () => {
23
+ try {
24
+ const response = await clientRef.current.get(
25
+ "/micelio/admin-pages"
26
+ );
27
+ if (isCurrent) setState({ pages: response.data.data, hasFailed: false, isLoading: false });
28
+ } catch {
29
+ if (isCurrent) setState({ pages: [], hasFailed: true, isLoading: false });
30
+ }
31
+ })();
32
+ return () => {
33
+ isCurrent = false;
34
+ };
35
+ }, []);
36
+ return state;
37
+ }
38
+ const NAV_GROUPS = ["overview", "content", "site", "about"];
39
+ const CORE_NAV_ITEMS = [
40
+ { id: "status", group: "overview", to: "", permission: permissions.STATUS_READ_PERMISSION },
41
+ { id: "about", group: "about", to: "about" }
42
+ ];
43
+ function visibleNavItems(items, permissions2) {
44
+ return items.filter(
45
+ (item) => !item.permission || permissions2.some(({ action }) => action === item.permission?.action)
46
+ );
47
+ }
48
+ function localised(text2, locale) {
49
+ const language = locale.split("-")[0] ?? "en";
50
+ const found = language === "es" ? text2.es : void 0;
51
+ return found && found.trim() !== "" ? found : text2.en;
52
+ }
53
+ function AboutPage() {
54
+ const { t } = permissions.useMessages();
55
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Page.Main, { children: [
56
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Title, { children: t("about.title") }),
57
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Header, { title: t("about.title"), subtitle: t("about.subtitle") }),
58
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: t("about.body") }) })
59
+ ] });
60
+ }
61
+ const PREVIEW_PROTOCOL_VERSION = 1;
62
+ const envelope = zod.z.object({
63
+ v: zod.z.literal(PREVIEW_PROTOCOL_VERSION),
64
+ type: zod.z.string().min(1),
65
+ payload: zod.z.unknown()
66
+ });
67
+ const readyMessage = envelope.extend({
68
+ type: zod.z.literal("ready"),
69
+ payload: zod.z.object({
70
+ /** The envelope versions the frame speaks. */
71
+ v: zod.z.array(zod.z.int().positive()).min(1),
72
+ documentId: zod.z.string().min(1),
73
+ /** `null` for a draft that is not localised. */
74
+ locale: zod.z.string().min(1).nullable(),
75
+ version: zod.z.string().min(1)
76
+ })
77
+ }).meta({
78
+ id: "ready",
79
+ title: "ready",
80
+ description: "Frame → admin: the frame has loaded a draft."
81
+ });
82
+ const loadDraftMessage = envelope.extend({
83
+ type: zod.z.literal("load-draft"),
84
+ payload: zod.z.object({
85
+ documentId: zod.z.string().min(1),
86
+ /** `null` for a draft that is not localised. */
87
+ locale: zod.z.string().min(1).nullable()
88
+ })
89
+ }).meta({
90
+ id: "load-draft",
91
+ title: "load-draft",
92
+ description: "Admin → frame: reload the draft after a save."
93
+ });
94
+ const previewMessage = zod.z.union([readyMessage, loadDraftMessage]);
95
+ function parsePreviewMessage(data) {
96
+ const result = previewMessage.safeParse(data);
97
+ return result.success ? result.data : void 0;
98
+ }
99
+ const DEVICES = [
100
+ { id: "phone", width: 320 },
101
+ { id: "tablet", width: 768 },
102
+ { id: "desktop", width: 1280 }
103
+ ];
104
+ const GRANT_REFRESH_MINUTES = 14;
105
+ const SAVE_POLL_MS = 4e3;
106
+ const SPOKEN_VERSIONS = [PREVIEW_PROTOCOL_VERSION];
107
+ function frameTarget(url, mode) {
108
+ let parsed;
109
+ try {
110
+ parsed = new URL(url);
111
+ } catch {
112
+ return void 0;
113
+ }
114
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return void 0;
115
+ if (mode === "static") parsed.searchParams.set("mode", "static");
116
+ return { src: parsed.toString(), origin: parsed.origin };
117
+ }
118
+ const NON_TEXT_INPUTS = [
119
+ "button",
120
+ "checkbox",
121
+ "radio",
122
+ "submit",
123
+ "reset",
124
+ "image",
125
+ "range",
126
+ "color"
127
+ ];
128
+ function isTypingTarget(target) {
129
+ const element = target;
130
+ if (!element || typeof element.tagName !== "string") return false;
131
+ const tag = element.tagName.toUpperCase();
132
+ if (tag === "INPUT")
133
+ return !NON_TEXT_INPUTS.includes(String(element.type ?? "text").toLowerCase());
134
+ return element.isContentEditable === true || tag === "TEXTAREA" || tag === "SELECT";
135
+ }
136
+ function previewShortcut(event) {
137
+ if (isTypingTarget(event.target)) return void 0;
138
+ if (event.ctrlKey === event.metaKey || !event.shiftKey || event.altKey) return void 0;
139
+ const device = DEVICES[["Digit1", "Digit2", "Digit3"].indexOf(event.code)];
140
+ if (device) return { action: "device", device: device.id };
141
+ if (event.code === "KeyS") return { action: "static" };
142
+ if (event.key === "ArrowLeft") return { action: "back" };
143
+ return void 0;
144
+ }
145
+ const FADE_OUT_MS = 400;
146
+ const NO_VERSIONS = [];
147
+ const Stage = styled__default.default.div`
148
+ position: relative;
149
+ box-sizing: border-box;
150
+ width: min(${({ $width }) => $width}px, 100%);
151
+ max-width: 100%;
152
+ height: 100%;
153
+ min-height: 32rem;
154
+ margin-inline: auto;
155
+ background: #ffffff;
156
+ box-shadow: 0 0 0 1px #dcdce4;
157
+ border-radius: 4px;
158
+ overflow: hidden;
159
+ transition: width var(--m-base) cubic-bezier(0.2, 0, 0, 1);
160
+ `;
161
+ const FrameLayer = styled__default.default.iframe`
162
+ position: absolute;
163
+ inset: 0;
164
+ width: 100%;
165
+ height: 100%;
166
+ border: 0;
167
+ background: #ffffff;
168
+ opacity: ${({ $phase }) => $phase === "shown" ? 1 : 0};
169
+ visibility: ${({ $phase }) => $phase === "loading" ? "hidden" : "visible"};
170
+ pointer-events: ${({ $phase }) => $phase === "shown" ? "auto" : "none"};
171
+ transition: opacity var(--m-slow) linear;
172
+ `;
173
+ const PreviewFrame = react.forwardRef(function PreviewFrame2(props, ref) {
174
+ const { src, origin, documentId, locale, title, width, onChange, onBlocked } = props;
175
+ const [layers, setLayers] = react.useState([]);
176
+ const [isUpdating, setIsUpdating] = react.useState(false);
177
+ const [protocol, setProtocol] = react.useState({ key: -1, protocol: "waiting", versions: NO_VERSIONS });
178
+ const frames = react.useRef(/* @__PURE__ */ new Map());
179
+ const protocols = react.useRef(/* @__PURE__ */ new Map());
180
+ const nextKey = react.useRef(0);
181
+ const shownKey = layers.find((layer) => layer.phase === "shown")?.key;
182
+ const timers = react.useRef([]);
183
+ const onChangeRef = react.useRef(onChange);
184
+ onChangeRef.current = onChange;
185
+ const onBlockedRef = react.useRef(onBlocked);
186
+ onBlockedRef.current = onBlocked;
187
+ react.useEffect(
188
+ () => () => {
189
+ for (const timer of timers.current) window.clearTimeout(timer);
190
+ },
191
+ []
192
+ );
193
+ react.useEffect(() => {
194
+ if (!src) {
195
+ setLayers([]);
196
+ return;
197
+ }
198
+ const key = nextKey.current++;
199
+ setLayers((current) => [
200
+ ...current,
201
+ { key, src, phase: current.length === 0 ? "shown" : "loading" }
202
+ ]);
203
+ }, [src]);
204
+ react.useEffect(() => {
205
+ if (!origin) return;
206
+ function onViolation(event) {
207
+ const isFrameDirective = ["frame-src", "child-src", "default-src"].includes(
208
+ event.effectiveDirective
209
+ );
210
+ if (isFrameDirective && event.blockedURI.startsWith(origin)) onBlockedRef.current();
211
+ }
212
+ document.addEventListener("securitypolicyviolation", onViolation);
213
+ return () => document.removeEventListener("securitypolicyviolation", onViolation);
214
+ }, [origin]);
215
+ react.useEffect(() => {
216
+ for (const key of protocols.current.keys()) {
217
+ if (!layers.some((layer) => layer.key === key)) protocols.current.delete(key);
218
+ }
219
+ }, [layers]);
220
+ const reveal = react.useCallback((key) => {
221
+ setLayers((current) => {
222
+ if (current.find((layer) => layer.key === key)?.phase !== "loading") return current;
223
+ return current.map(
224
+ (layer) => layer.key === key ? { ...layer, phase: "shown" } : layer.phase === "shown" ? { ...layer, phase: "leaving" } : layer
225
+ );
226
+ });
227
+ timers.current.push(
228
+ window.setTimeout(
229
+ () => setLayers((current) => current.filter((layer) => layer.phase !== "leaving")),
230
+ FADE_OUT_MS
231
+ )
232
+ );
233
+ }, []);
234
+ react.useEffect(() => {
235
+ if (!origin) return;
236
+ function onMessage(event) {
237
+ if (event.origin !== origin) return;
238
+ const key = [...frames.current].find(([, frame]) => frame.contentWindow === event.source)?.[0];
239
+ if (key === void 0) return;
240
+ const message = parsePreviewMessage(event.data);
241
+ if (message?.type !== "ready") return;
242
+ if (message.payload.documentId !== documentId || message.payload.locale !== locale) return;
243
+ const versions = message.payload.v;
244
+ const state = {
245
+ protocol: SPOKEN_VERSIONS.some((spoken) => versions.includes(spoken)) ? "ok" : "upgrade",
246
+ versions
247
+ };
248
+ protocols.current.set(key, state);
249
+ setProtocol({ key, ...state });
250
+ setIsUpdating(false);
251
+ }
252
+ window.addEventListener("message", onMessage);
253
+ return () => window.removeEventListener("message", onMessage);
254
+ }, [origin, documentId, locale]);
255
+ react.useImperativeHandle(
256
+ ref,
257
+ () => ({
258
+ loadDraft() {
259
+ const frame = shownKey === void 0 ? void 0 : frames.current.get(shownKey);
260
+ if (!origin || !frame?.contentWindow || protocols.current.get(shownKey ?? -1)?.protocol !== "ok") {
261
+ return false;
262
+ }
263
+ const message = {
264
+ v: PREVIEW_PROTOCOL_VERSION,
265
+ type: "load-draft",
266
+ payload: { documentId, locale }
267
+ };
268
+ frame.contentWindow.postMessage(message, origin);
269
+ setIsUpdating(true);
270
+ return true;
271
+ }
272
+ }),
273
+ [shownKey, origin, documentId, locale]
274
+ );
275
+ const inView = protocol.key === shownKey ? protocol : { protocol: "waiting", versions: NO_VERSIONS };
276
+ const isBusy = isUpdating || layers.some((layer) => layer.phase === "loading");
277
+ react.useEffect(() => {
278
+ onChangeRef.current({ protocol: inView.protocol, versions: inView.versions, isBusy });
279
+ }, [inView.protocol, inView.versions, isBusy]);
280
+ return /* @__PURE__ */ jsxRuntime.jsx(Stage, { $width: width, children: layers.map((layer) => /* @__PURE__ */ jsxRuntime.jsx(
281
+ FrameLayer,
282
+ {
283
+ $phase: layer.phase,
284
+ src: layer.src,
285
+ title,
286
+ sandbox: "allow-scripts allow-same-origin allow-forms",
287
+ referrerPolicy: "no-referrer",
288
+ ref: (frame) => {
289
+ if (frame) frames.current.set(layer.key, frame);
290
+ else frames.current.delete(layer.key);
291
+ },
292
+ onLoad: () => {
293
+ reveal(layer.key);
294
+ if (layer.phase === "shown") setIsUpdating(false);
295
+ }
296
+ },
297
+ layer.key
298
+ )) });
299
+ });
300
+ function statusOf(error) {
301
+ if (typeof error !== "object" || error === null) return void 0;
302
+ const { status, response } = error;
303
+ if (typeof status === "number") return status;
304
+ return typeof response?.status === "number" ? response.status : void 0;
305
+ }
306
+ function text(value) {
307
+ return typeof value === "string" && value !== "" ? value : void 0;
308
+ }
309
+ function toEntry(answer, documentId) {
310
+ const locale = text(answer.data.locale) ?? null;
311
+ const others = (answer.meta?.availableLocales ?? []).flatMap(
312
+ ({ locale: other }) => text(other) ?? []
313
+ );
314
+ return {
315
+ title: text(answer.data.title) ?? text(answer.data.name) ?? text(answer.data.slug) ?? documentId,
316
+ locale,
317
+ locales: locale === null ? [] : [.../* @__PURE__ */ new Set([locale, ...others])],
318
+ updatedAt: text(answer.data.updatedAt) ?? ""
319
+ };
320
+ }
321
+ function useEntry(uid, documentId, locale) {
322
+ const client = admin.useFetchClient();
323
+ const clientRef = react.useRef(client);
324
+ clientRef.current = client;
325
+ const [resource, setResource] = react.useState({ kind: "loading" });
326
+ const scope = `${uid}/${documentId}/${locale ?? ""}`;
327
+ const [saves, setSaves] = react.useState({ scope, count: 0 });
328
+ react.useEffect(() => {
329
+ let isCurrent = true;
330
+ let isReading = false;
331
+ let lastSeen;
332
+ async function read(isFirst) {
333
+ if (isReading) return;
334
+ isReading = true;
335
+ try {
336
+ const response = await clientRef.current.get(
337
+ `/content-manager/collection-types/${uid}/${documentId}`,
338
+ { params: locale ? { locale } : {} }
339
+ );
340
+ if (!isCurrent) return;
341
+ const entry = toEntry(response.data, documentId);
342
+ if (lastSeen !== void 0 && entry.updatedAt !== lastSeen) {
343
+ setSaves((current) => ({ scope, count: current.scope === scope ? current.count + 1 : 1 }));
344
+ }
345
+ lastSeen = entry.updatedAt;
346
+ setResource({ kind: "ready", entry });
347
+ } catch (error) {
348
+ if (isCurrent && isFirst) setResource({ kind: "error", status: statusOf(error) });
349
+ } finally {
350
+ isReading = false;
351
+ }
352
+ }
353
+ setResource({ kind: "loading" });
354
+ setSaves({ scope, count: 0 });
355
+ void read(true);
356
+ const timer = window.setInterval(() => {
357
+ if (document.visibilityState === "visible") void read(false);
358
+ }, SAVE_POLL_MS);
359
+ const onVisible = () => {
360
+ if (document.visibilityState === "visible") void read(false);
361
+ };
362
+ document.addEventListener("visibilitychange", onVisible);
363
+ return () => {
364
+ isCurrent = false;
365
+ window.clearInterval(timer);
366
+ document.removeEventListener("visibilitychange", onVisible);
367
+ };
368
+ }, [uid, documentId, locale]);
369
+ return { resource, savedCount: saves.scope === scope ? saves.count : 0 };
370
+ }
371
+ const REASONS = {
372
+ 400: "unsupported",
373
+ 403: "forbidden",
374
+ 404: "missing",
375
+ 409: "unavailable",
376
+ 429: "busy"
377
+ };
378
+ function usePreviewSource(entry, mode) {
379
+ const client = admin.useFetchClient();
380
+ const clientRef = react.useRef(client);
381
+ clientRef.current = client;
382
+ const [source, setSource] = react.useState({ kind: "loading" });
383
+ const [count, setCount] = react.useState(0);
384
+ const uid = entry?.uid;
385
+ const documentId = entry?.documentId;
386
+ const locale = entry?.locale;
387
+ react.useEffect(() => {
388
+ if (!uid || !documentId) return;
389
+ let isCurrent = true;
390
+ let timer;
391
+ void (async () => {
392
+ try {
393
+ const response = await clientRef.current.post(
394
+ "/micelio/preview/token",
395
+ { uid, documentId, locale }
396
+ );
397
+ const target = frameTarget(response.data.data.url, mode);
398
+ if (!isCurrent) return;
399
+ if (!target) {
400
+ setSource({ kind: "error", reason: "unavailable" });
401
+ return;
402
+ }
403
+ setSource({ kind: "ready", ...target });
404
+ timer = window.setTimeout(
405
+ () => setCount((value) => value + 1),
406
+ GRANT_REFRESH_MINUTES * 6e4
407
+ );
408
+ } catch (error) {
409
+ const status = statusOf(error);
410
+ if (isCurrent) setSource({ kind: "error", reason: status && REASONS[status] || "failed" });
411
+ }
412
+ })();
413
+ return () => {
414
+ isCurrent = false;
415
+ window.clearTimeout(timer);
416
+ };
417
+ }, [uid, documentId, locale, mode, count]);
418
+ const reload = react.useCallback(() => setCount((value) => value + 1), []);
419
+ return { source, reload };
420
+ }
421
+ const Root = styled__default.default.div`
422
+ --m-fast: 120ms;
423
+ --m-base: 200ms;
424
+ --m-slow: 320ms;
425
+ @media (prefers-reduced-motion: reduce) {
426
+ --m-fast: 1ms;
427
+ --m-base: 1ms;
428
+ --m-slow: 1ms;
429
+ }
430
+ `;
431
+ const Columns = styled__default.default.div`
432
+ display: grid;
433
+ grid-template-columns: minmax(16rem, 1fr) minmax(0, 2fr);
434
+ gap: 1.5rem;
435
+ align-items: start;
436
+ @media (max-width: 62rem) {
437
+ grid-template-columns: minmax(0, 1fr);
438
+ }
439
+ `;
440
+ const Select = styled__default.default.select`
441
+ min-height: 2rem;
442
+ padding: 0 0.5rem;
443
+ border: 1px solid #8e8ea9;
444
+ border-radius: 4px;
445
+ background: #ffffff;
446
+ color: #32324d;
447
+ font: inherit;
448
+ `;
449
+ const StageBox = styled__default.default.div`
450
+ height: calc(100vh - 20rem);
451
+ min-height: 32rem;
452
+ `;
453
+ const SHORTCUTS = {
454
+ phone: "Control+Shift+1 Meta+Shift+1",
455
+ tablet: "Control+Shift+2 Meta+Shift+2",
456
+ desktop: "Control+Shift+3 Meta+Shift+3",
457
+ static: "Control+Shift+S Meta+Shift+S"
458
+ };
459
+ function PreviewPage() {
460
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Protect, { permissions: [permissions.PREVIEW_PERMISSION], children: /* @__PURE__ */ jsxRuntime.jsx(PreviewPane, {}) });
461
+ }
462
+ function PreviewPane() {
463
+ const { t, date } = permissions.useMessages();
464
+ const { uid = "", documentId = "" } = reactRouterDom.useParams();
465
+ const [searchParams] = reactRouterDom.useSearchParams();
466
+ const navigate = reactRouterDom.useNavigate();
467
+ const permissions$1 = admin.useAuth("PreviewPage", (state) => state.permissions);
468
+ const [locale, setLocale] = react.useState(searchParams.get("locale"));
469
+ const [device, setDevice] = react.useState("desktop");
470
+ const [mode, setMode] = react.useState("live");
471
+ const [frameState, setFrameState] = react.useState({
472
+ protocol: "waiting",
473
+ versions: [],
474
+ isBusy: false
475
+ });
476
+ const [isBlocked, setIsBlocked] = react.useState(false);
477
+ const frame = react.useRef(null);
478
+ const { resource, savedCount } = useEntry(uid, documentId, locale);
479
+ const entry = resource.kind === "ready" ? resource.entry : void 0;
480
+ const { source, reload } = usePreviewSource(
481
+ entry ? { uid, documentId, locale: entry.locale } : void 0,
482
+ mode
483
+ );
484
+ const canEdit = permissions$1.some(
485
+ ({ action, subject }) => action === "plugin::content-manager.explorer.update" && subject === uid
486
+ );
487
+ const entryPath = `/content-manager/collection-types/${uid}/${documentId}${entry?.locale ? `?plugins[i18n][locale]=${entry.locale}` : ""}`;
488
+ const handled = react.useRef({ locale, count: 0 });
489
+ react.useEffect(() => {
490
+ if (handled.current.locale !== locale) handled.current = { locale, count: 0 };
491
+ if (savedCount === 0 || savedCount === handled.current.count) return;
492
+ handled.current.count = savedCount;
493
+ if (!frame.current?.loadDraft()) reload();
494
+ }, [savedCount, locale, reload]);
495
+ const origin = source.kind === "ready" ? source.origin : void 0;
496
+ react.useEffect(() => setIsBlocked(false), [origin]);
497
+ react.useEffect(() => {
498
+ function onKeyDown(event) {
499
+ const shortcut = previewShortcut(event);
500
+ if (!shortcut) return;
501
+ event.preventDefault();
502
+ if (shortcut.action === "device") setDevice(shortcut.device);
503
+ else if (shortcut.action === "static")
504
+ setMode((current) => current === "live" ? "static" : "live");
505
+ else navigate(entryPath);
506
+ }
507
+ window.addEventListener("keydown", onKeyDown);
508
+ return () => window.removeEventListener("keydown", onKeyDown);
509
+ }, [navigate, entryPath]);
510
+ if (resource.kind === "error") {
511
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Page.Main, { children: [
512
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Title, { children: t("preview.title") }),
513
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Header, { title: t("preview.title") }),
514
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { role: "alert", children: t(resource.status === 404 ? "preview.error.missing" : "preview.error.entry") }) })
515
+ ] });
516
+ }
517
+ const title = entry?.title ?? t("preview.title");
518
+ const width = DEVICES.find((item) => item.id === device)?.width ?? 1280;
519
+ const reason = source.kind === "error" ? source.reason : void 0;
520
+ const isUpgradeNeeded = frameState.protocol === "upgrade";
521
+ return /* @__PURE__ */ jsxRuntime.jsx(Root, { children: /* @__PURE__ */ jsxRuntime.jsxs(admin.Page.Main, { children: [
522
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Title, { children: t("preview.page", { title }) }),
523
+ /* @__PURE__ */ jsxRuntime.jsx(
524
+ admin.Layouts.Header,
525
+ {
526
+ title,
527
+ subtitle: t("preview.subtitle"),
528
+ navigationAction: /* @__PURE__ */ jsxRuntime.jsx(
529
+ designSystem.Link,
530
+ {
531
+ tag: reactRouterDom.NavLink,
532
+ to: entryPath,
533
+ "aria-keyshortcuts": "Control+Shift+ArrowLeft Meta+Shift+ArrowLeft",
534
+ children: t("preview.back")
535
+ }
536
+ )
537
+ }
538
+ ),
539
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(Columns, { children: [
540
+ /* @__PURE__ */ jsxRuntime.jsx(EntryColumn, { entry, canEdit, entryPath, date }),
541
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": t("preview.pane"), children: [
542
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, wrap: "wrap", alignItems: "center", marginBottom: 3, children: [
543
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, role: "group", "aria-label": t("preview.device.group"), children: DEVICES.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
544
+ designSystem.Button,
545
+ {
546
+ type: "button",
547
+ size: "S",
548
+ variant: device === item.id ? "default" : "tertiary",
549
+ "aria-pressed": device === item.id,
550
+ "aria-keyshortcuts": SHORTCUTS[item.id],
551
+ onClick: () => setDevice(item.id),
552
+ children: t(`preview.device.${item.id}`, { width: item.width })
553
+ },
554
+ item.id
555
+ )) }),
556
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { tag: "label", gap: 2, alignItems: "center", children: [
557
+ /* @__PURE__ */ jsxRuntime.jsx(
558
+ "input",
559
+ {
560
+ type: "checkbox",
561
+ checked: mode === "static",
562
+ "aria-keyshortcuts": SHORTCUTS.static,
563
+ onChange: (event) => setMode(event.target.checked ? "static" : "live")
564
+ }
565
+ ),
566
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: t("preview.static") })
567
+ ] }),
568
+ entry && entry.locales.length > 1 ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { tag: "label", gap: 2, alignItems: "center", children: [
569
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: t("preview.locale") }),
570
+ /* @__PURE__ */ jsxRuntime.jsx(
571
+ Select,
572
+ {
573
+ value: entry.locale ?? "",
574
+ onChange: (event) => setLocale(event.target.value),
575
+ children: entry.locales.map((code) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: code, children: code }, code))
576
+ }
577
+ )
578
+ ] }) : null,
579
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { type: "button", size: "S", variant: "tertiary", onClick: reload, children: t("preview.reload") }),
580
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { role: "status", textColor: "neutral700", children: frameState.isBusy ? t("preview.updating") : "" })
581
+ ] }),
582
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral700", tag: "p", children: t("preview.shortcuts") }),
583
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 3, marginBottom: 3, children: [
584
+ reason ? /* @__PURE__ */ jsxRuntime.jsx(Notice, { role: "alert", text: t(`preview.error.${reason}`) }) : null,
585
+ isBlocked ? /* @__PURE__ */ jsxRuntime.jsx(
586
+ Notice,
587
+ {
588
+ role: "alert",
589
+ title: t("preview.blocked.title"),
590
+ text: t("preview.blocked.body", { origin: origin ?? "" })
591
+ }
592
+ ) : null,
593
+ isUpgradeNeeded ? /* @__PURE__ */ jsxRuntime.jsx(
594
+ Notice,
595
+ {
596
+ role: "status",
597
+ title: t("preview.upgrade.title"),
598
+ text: t("preview.upgrade.body", { versions: frameState.versions.join(", ") })
599
+ }
600
+ ) : null
601
+ ] }),
602
+ /* @__PURE__ */ jsxRuntime.jsx(StageBox, { children: isBlocked || source.kind === "error" ? null : /* @__PURE__ */ jsxRuntime.jsx(
603
+ PreviewFrame,
604
+ {
605
+ ref: frame,
606
+ src: source.kind === "ready" ? source.src : void 0,
607
+ origin,
608
+ documentId,
609
+ locale: entry?.locale ?? null,
610
+ title: t("preview.frame.title", { title }),
611
+ width,
612
+ onChange: setFrameState,
613
+ onBlocked: () => setIsBlocked(true)
614
+ }
615
+ ) })
616
+ ] })
617
+ ] }) })
618
+ ] }) });
619
+ }
620
+ function EntryColumn(props) {
621
+ const { t } = permissions.useMessages();
622
+ const { locale } = reactIntl.useIntl();
623
+ const { entry, canEdit, entryPath, date } = props;
624
+ const href = reactRouterDom.useHref(entryPath);
625
+ return /* @__PURE__ */ jsxRuntime.jsx("section", { "aria-label": t("preview.entry.title"), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 3, children: [
626
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", children: t("preview.entry.title") }),
627
+ canEdit ? null : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral700", children: t("preview.entry.readOnly") }),
628
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: t(canEdit ? "preview.entry.body" : "preview.entry.bodyReadOnly") }),
629
+ entry?.locale ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral700", children: t("preview.entry.locale", { locale: entry.locale }) }) : null,
630
+ entry?.updatedAt ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral700", lang: locale, children: t("preview.entry.saved", { date: date(entry.updatedAt) }) }) : null,
631
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { href, isExternal: true, children: t(canEdit ? "preview.entry.open" : "preview.entry.view") })
632
+ ] }) });
633
+ }
634
+ function Notice(props) {
635
+ return /* @__PURE__ */ jsxRuntime.jsxs(
636
+ designSystem.Box,
637
+ {
638
+ role: props.role,
639
+ padding: 3,
640
+ marginBottom: 2,
641
+ background: "warning100",
642
+ borderColor: "warning200",
643
+ hasRadius: true,
644
+ children: [
645
+ props.title ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", tag: "p", children: props.title }) : null,
646
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", children: props.text })
647
+ ]
648
+ }
649
+ );
650
+ }
651
+ function useHostCheckRun(initial) {
652
+ const client = admin.useFetchClient();
653
+ const clientRef = react.useRef(client);
654
+ clientRef.current = client;
655
+ const [report, setReport] = react.useState(initial);
656
+ const [isRunning, setIsRunning] = react.useState(false);
657
+ const [hasFailed, setHasFailed] = react.useState(false);
658
+ const run = react.useCallback(async () => {
659
+ setIsRunning(true);
660
+ setHasFailed(false);
661
+ try {
662
+ const response = await clientRef.current.post(
663
+ "/micelio/status/host-check"
664
+ );
665
+ setReport(response.data.data);
666
+ return response.data.data;
667
+ } catch {
668
+ setHasFailed(true);
669
+ return null;
670
+ } finally {
671
+ setIsRunning(false);
672
+ }
673
+ }, []);
674
+ return { report, isRunning, hasFailed, run };
675
+ }
676
+ function useStatus() {
677
+ const client = admin.useFetchClient();
678
+ const clientRef = react.useRef(client);
679
+ clientRef.current = client;
680
+ const [resource, setResource] = react.useState({ kind: "loading" });
681
+ const [count, setCount] = react.useState(0);
682
+ react.useEffect(() => {
683
+ let isCurrent = true;
684
+ void (async () => {
685
+ try {
686
+ const response = await clientRef.current.get("/micelio/status");
687
+ if (isCurrent) setResource({ kind: "ready", data: response.data.data });
688
+ } catch {
689
+ if (isCurrent) setResource({ kind: "error" });
690
+ }
691
+ })();
692
+ return () => {
693
+ isCurrent = false;
694
+ };
695
+ }, [count]);
696
+ const reload = react.useCallback(() => {
697
+ setResource({ kind: "loading" });
698
+ setCount((value) => value + 1);
699
+ }, []);
700
+ const refresh = react.useCallback(async () => {
701
+ try {
702
+ const response = await clientRef.current.get("/micelio/status");
703
+ setResource({ kind: "ready", data: response.data.data });
704
+ } catch {
705
+ }
706
+ }, []);
707
+ return { resource, reload, refresh };
708
+ }
709
+ function checkText(result, part, isKnown) {
710
+ const fallback = part === "title" ? result.title : result.fix;
711
+ if (fallback === void 0) return null;
712
+ const messageId = `status.hostCheck.check.${result.id}.${part}`;
713
+ return isKnown(messageId) ? { messageId } : { text: fallback };
714
+ }
715
+ function diagnosticsText(report) {
716
+ const lines = [`Micelio ${report.plugin.version} (core release line ${report.plugin.coreLine})`];
717
+ if (report.hostCheck) {
718
+ const failed = report.hostCheck.results.filter(({ status }) => status === "fail").length;
719
+ lines.push(`Host check: ${report.hostCheck.ok ? "ok" : "fails"} (${failed} failed)`);
720
+ } else {
721
+ lines.push("Host check: not run");
722
+ }
723
+ for (const [id, result] of Object.entries(report.plugins)) lines.push(pluginLine(id, result));
724
+ return lines.join("\n");
725
+ }
726
+ function pluginLine(id, result) {
727
+ if (result.ranAt === null) return `${id}: checks not run`;
728
+ const count = (status) => result.checks.filter((check) => check.status === status).length;
729
+ return `${id}: ${count("ok")} ok, ${count("warn")} warn, ${count("fail")} fail`;
730
+ }
731
+ const TONES = {
732
+ success: { backgroundColor: "success100", textColor: "success700" },
733
+ danger: { backgroundColor: "danger100", textColor: "danger700" },
734
+ // warning700 on warning100 is 4.0:1, below the 4.5:1 of WCAG AA.
735
+ warning: { backgroundColor: "warning100", textColor: "neutral800" },
736
+ neutral: { backgroundColor: "neutral150", textColor: "neutral700" }
737
+ };
738
+ const CHECK_TONES = {
739
+ pass: "success",
740
+ fail: "danger",
741
+ skip: "neutral"
742
+ };
743
+ function StatusPage() {
744
+ const { t } = permissions.useMessages();
745
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Protect, { permissions: [permissions.STATUS_READ_PERMISSION], children: /* @__PURE__ */ jsxRuntime.jsxs(admin.Page.Main, { children: [
746
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Title, { children: t("status.title") }),
747
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Header, { title: t("status.title"), subtitle: t("status.subtitle") }),
748
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(StatusContent, {}) })
749
+ ] }) });
750
+ }
751
+ function StatusContent() {
752
+ const { t } = permissions.useMessages();
753
+ const { resource, reload, refresh } = useStatus();
754
+ if (resource.kind === "loading") return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Loading, { children: t("status.loading") });
755
+ if (resource.kind === "error") {
756
+ return /* @__PURE__ */ jsxRuntime.jsx(Section, { title: t("status.error.title"), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 2, children: [
757
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: t("status.error") }),
758
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", size: "S", onClick: reload, children: t("status.retry") })
759
+ ] }) });
760
+ }
761
+ const report = resource.data;
762
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
763
+ /* @__PURE__ */ jsxRuntime.jsx(PluginSection, { report }),
764
+ /* @__PURE__ */ jsxRuntime.jsx(HostCheckSection, { initial: report.hostCheck, onRan: refresh }),
765
+ /* @__PURE__ */ jsxRuntime.jsx(PluginChecksSections, { report }),
766
+ /* @__PURE__ */ jsxRuntime.jsx(FrontendsSection, { frontends: report.frontends }),
767
+ /* @__PURE__ */ jsxRuntime.jsx(SitesSection, { sites: report.sites }),
768
+ /* @__PURE__ */ jsxRuntime.jsx(DiagnosticsButton, { report })
769
+ ] });
770
+ }
771
+ function Section({ title, children }) {
772
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { tag: "section", background: "neutral0", hasRadius: true, shadow: "filterShadow", padding: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
773
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", children: title }),
774
+ children
775
+ ] }) });
776
+ }
777
+ function StatusBadge({ tone, children }) {
778
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { ...TONES[tone], children });
779
+ }
780
+ function Fact({ label, value }) {
781
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
782
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", tag: "dt", children: label }),
783
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "dd", children: value })
784
+ ] });
785
+ }
786
+ function PluginSection({ report }) {
787
+ const { t } = permissions.useMessages();
788
+ return /* @__PURE__ */ jsxRuntime.jsx(Section, { title: t("status.plugin.title"), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { tag: "dl", gap: 8, wrap: "wrap", alignItems: "flex-start", children: [
789
+ /* @__PURE__ */ jsxRuntime.jsx(Fact, { label: t("status.plugin.version"), value: report.plugin.version }),
790
+ /* @__PURE__ */ jsxRuntime.jsx(Fact, { label: t("status.plugin.coreLine"), value: report.plugin.coreLine }),
791
+ /* @__PURE__ */ jsxRuntime.jsx(Fact, { label: t("status.blocks.count"), value: report.blocks.count }),
792
+ /* @__PURE__ */ jsxRuntime.jsx(Fact, { label: t("status.sites.count"), value: report.sites.length })
793
+ ] }) });
794
+ }
795
+ function HostCheckSection({
796
+ initial,
797
+ onRan
798
+ }) {
799
+ const { t, date } = permissions.useMessages();
800
+ const { notifyStatus, notifyAlert } = designSystem.useNotifyAT();
801
+ const { report, isRunning, hasFailed, run } = useHostCheckRun(initial);
802
+ const say = (text2) => "messageId" in text2 ? t(text2.messageId) : text2.text;
803
+ const verdict = (current) => `${t(current.ok ? "status.hostCheck.ok" : "status.hostCheck.failed")} ${t(
804
+ "status.hostCheck.checkedAt",
805
+ { date: date(current.checkedAt), environment: current.environment }
806
+ )}`;
807
+ const runAgain = async () => {
808
+ const answer = await run();
809
+ if (answer) await onRan();
810
+ if (answer) notifyStatus(verdict(answer));
811
+ else notifyAlert(t("status.hostCheck.runFailed"));
812
+ };
813
+ const header = /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, wrap: "wrap", justifyContent: "space-between", alignItems: "center", children: [
814
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: report ? void 0 : "neutral600", children: report ? verdict(report) : t("status.hostCheck.notRun") }),
815
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", size: "S", loading: isRunning, onClick: runAgain, children: t(isRunning ? "status.hostCheck.running" : "status.hostCheck.runAgain") })
816
+ ] });
817
+ const failure = hasFailed ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "danger700", children: t("status.hostCheck.runFailed") }) : null;
818
+ if (!report) {
819
+ return /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: t("status.hostCheck.title"), children: [
820
+ header,
821
+ failure
822
+ ] });
823
+ }
824
+ return /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: t("status.hostCheck.title"), children: [
825
+ header,
826
+ failure,
827
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 3, rowCount: report.results.length + 1, children: [
828
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
829
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.hostCheck.column.check") }) }),
830
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.hostCheck.column.status") }) }),
831
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.hostCheck.column.found") }) })
832
+ ] }) }),
833
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: report.results.map((result) => {
834
+ const title = checkText(result, "title", permissions.hasMessage);
835
+ const fix = result.status === "fail" ? checkText(result, "fix", permissions.hasMessage) : null;
836
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
837
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
838
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: title ? say(title) : result.id }),
839
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t(
840
+ result.required ? "status.hostCheck.required" : "status.hostCheck.optional"
841
+ ) })
842
+ ] }) }),
843
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(StatusBadge, { tone: CHECK_TONES[result.status], children: t(`status.hostCheck.status.${result.status}`) }) }),
844
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
845
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: result.detail }),
846
+ fix ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral800", children: [
847
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
848
+ t("status.hostCheck.fix"),
849
+ ":"
850
+ ] }),
851
+ " ",
852
+ say(fix)
853
+ ] }) : null
854
+ ] }) })
855
+ ] }, result.id);
856
+ }) })
857
+ ] }),
858
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t("status.hostCheck.rerun") })
859
+ ] });
860
+ }
861
+ function FrontendsSection({ frontends }) {
862
+ const { t } = permissions.useMessages();
863
+ if (frontends.length === 0) {
864
+ return /* @__PURE__ */ jsxRuntime.jsx(Section, { title: t("status.frontends.title"), children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: t("status.frontends.none") }) });
865
+ }
866
+ return /* @__PURE__ */ jsxRuntime.jsx(Section, { title: t("status.frontends.title"), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 4, rowCount: frontends.length + 1, children: [
867
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
868
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.frontends.column.origin") }) }),
869
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.frontends.column.sites") }) }),
870
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.frontends.column.health") }) }),
871
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.frontends.column.version") }) })
872
+ ] }) }),
873
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: frontends.map((frontend) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
874
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: frontend.origin }) }),
875
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: frontend.sites.join(", ") }) }),
876
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
877
+ /* @__PURE__ */ jsxRuntime.jsx(StatusBadge, { tone: frontend.health === "reachable" ? "success" : "neutral", children: t(`status.frontends.health.${frontend.health}`) }),
878
+ frontend.health === "unavailable" ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t("status.frontends.unavailable") }) : null
879
+ ] }) }),
880
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: frontend.version ?? t("status.frontends.noVersion") }) })
881
+ ] }, frontend.origin)) })
882
+ ] }) });
883
+ }
884
+ function SitesSection({ sites }) {
885
+ const { t } = permissions.useMessages();
886
+ return /* @__PURE__ */ jsxRuntime.jsx(Section, { title: t("status.sites.title"), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 4, rowCount: sites.length + 1, children: [
887
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
888
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.sites.column.name") }) }),
889
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.sites.column.slug") }) }),
890
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.sites.column.hosts") }) }),
891
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.sites.column.url") }) })
892
+ ] }) }),
893
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: sites.map((site) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
894
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
895
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: site.name }),
896
+ site.isDefault ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: t("status.sites.default") }) : null
897
+ ] }) }),
898
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: site.slug }) }),
899
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: site.hosts.length > 0 ? site.hosts.join(", ") : t("status.sites.noHosts") }) }),
900
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: site.origin ?? t("status.sites.noUrl") }) })
901
+ ] }, site.documentId)) })
902
+ ] }) });
903
+ }
904
+ const PLUGIN_TONES = {
905
+ ok: "success",
906
+ warn: "warning",
907
+ fail: "danger"
908
+ };
909
+ function PluginChecksSections({ report }) {
910
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: Object.entries(report.plugins).map(([id, result]) => /* @__PURE__ */ jsxRuntime.jsx(PluginChecksCard, { result, sites: report.sites }, id)) });
911
+ }
912
+ function PluginChecksCard({ result, sites }) {
913
+ const { t, date } = permissions.useMessages();
914
+ const { locale } = reactIntl.useIntl();
915
+ const slugOf = (documentId) => sites.find((site) => site.documentId === documentId)?.slug ?? documentId;
916
+ return /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: localised(result.label, locale), children: [
917
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: result.ranAt === null ? t("status.plugins.notRun") : t("status.plugins.ranAt", { date: date(result.ranAt) }) }),
918
+ result.ranAt !== null && result.checks.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: t("status.plugins.empty") }) : null,
919
+ result.checks.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 4, rowCount: result.checks.length + 1, children: [
920
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
921
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.plugins.column.site") }) }),
922
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.plugins.column.check") }) }),
923
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.plugins.column.status") }) }),
924
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("status.plugins.column.found") }) })
925
+ ] }) }),
926
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: result.checks.map((check) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
927
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: slugOf(check.site) }) }),
928
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: localised(check.title, locale) }) }),
929
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(StatusBadge, { tone: PLUGIN_TONES[check.status], children: t(`status.plugins.status.${check.status}`) }) }),
930
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
931
+ check.detail ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: localised(check.detail, locale) }) : null,
932
+ check.fix ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral800", children: [
933
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
934
+ t("status.plugins.fix"),
935
+ ":"
936
+ ] }),
937
+ " ",
938
+ localised(check.fix, locale)
939
+ ] }) : null
940
+ ] }) })
941
+ ] }, `${check.site}:${check.id}`)) })
942
+ ] }) : null
943
+ ] });
944
+ }
945
+ function DiagnosticsButton({ report }) {
946
+ const { t } = permissions.useMessages();
947
+ const { notifyStatus, notifyAlert } = designSystem.useNotifyAT();
948
+ const copy = async () => {
949
+ try {
950
+ await navigator.clipboard.writeText(diagnosticsText(report));
951
+ notifyStatus(t("status.diagnostics.copied"));
952
+ } catch {
953
+ notifyAlert(t("status.diagnostics.failed"));
954
+ }
955
+ };
956
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "S", onClick: copy, children: t("status.diagnostics.copy") }) });
957
+ }
958
+ function MicelioLayout() {
959
+ const { t } = permissions.useMessages();
960
+ const { locale } = reactIntl.useIntl();
961
+ const { pages, hasFailed, isLoading } = useAdminPages();
962
+ const isLoadingPermissions = admin.useAuth("MicelioLayout", (state) => state.isLoading);
963
+ const permissions$1 = admin.useAuth("MicelioLayout", (state) => state.permissions);
964
+ const canReadStatus = permissions$1.some(({ action }) => action === permissions.STATUS_READ_PERMISSION.action);
965
+ const coreItems = visibleNavItems(CORE_NAV_ITEMS, permissions$1);
966
+ const linksOf = (group) => [
967
+ ...coreItems.filter((item) => item.group === group).map((item) => /* @__PURE__ */ jsxRuntime.jsx(
968
+ admin.SubNav.Link,
969
+ {
970
+ to: item.to ? `/plugins/${index.PLUGIN_ID}/${item.to}` : `/plugins/${index.PLUGIN_ID}`,
971
+ end: !item.to,
972
+ label: t(`nav.${item.id}`)
973
+ },
974
+ item.id
975
+ )),
976
+ ...pages.filter((page) => page.group === group).map((page) => /* @__PURE__ */ jsxRuntime.jsx(
977
+ admin.SubNav.Link,
978
+ {
979
+ to: `/plugins/${index.PLUGIN_ID}/${page.to}`,
980
+ label: localised(page.label, locale)
981
+ },
982
+ page.id
983
+ ))
984
+ ];
985
+ if (isLoadingPermissions) return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Loading, {});
986
+ return /* @__PURE__ */ jsxRuntime.jsx(
987
+ admin.Layouts.Root,
988
+ {
989
+ sideNav: /* @__PURE__ */ jsxRuntime.jsxs(admin.SubNav.Main, { "aria-label": t("nav.label"), children: [
990
+ /* @__PURE__ */ jsxRuntime.jsx(admin.SubNav.Header, { label: t("nav.title") }),
991
+ /* @__PURE__ */ jsxRuntime.jsxs(admin.SubNav.Sections, { children: [
992
+ NAV_GROUPS.map((group) => ({ group, links: linksOf(group) })).filter(({ links }) => links.length > 0).map(({ group, links }) => /* @__PURE__ */ jsxRuntime.jsx(admin.SubNav.Section, { label: t(`nav.group.${group}`), children: links }, group)),
993
+ hasFailed ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: t("nav.error") }) : null
994
+ ] })
995
+ ] }),
996
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
997
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: canReadStatus ? /* @__PURE__ */ jsxRuntime.jsx(StatusPage, {}) : /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: "about", replace: true }) }),
998
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "about", element: /* @__PURE__ */ jsxRuntime.jsx(AboutPage, {}) }),
999
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "preview/:uid/:documentId", element: /* @__PURE__ */ jsxRuntime.jsx(PreviewPage, {}) }),
1000
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "*", element: /* @__PURE__ */ jsxRuntime.jsx(ExtensionPage, { pages, isLoading }) })
1001
+ ] })
1002
+ }
1003
+ );
1004
+ }
1005
+ function ExtensionPage({ pages, isLoading }) {
1006
+ const { t } = permissions.useMessages();
1007
+ const path = reactRouterDom.useParams()["*"] ?? "";
1008
+ const page = pages.find((candidate) => candidate.to === path);
1009
+ const Component = react.useMemo(() => {
1010
+ const loader = page ? index.pageLoaderOf(page.id) : void 0;
1011
+ return loader ? react.lazy(loader) : null;
1012
+ }, [page]);
1013
+ if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Loading, {});
1014
+ if (!Component) {
1015
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Page.Main, { children: [
1016
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Title, { children: t("page.unavailable.title") }),
1017
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Header, { title: t("page.unavailable.title") }),
1018
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: t("page.unavailable") }) })
1019
+ ] });
1020
+ }
1021
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Loading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(Component, {}) });
1022
+ }
1023
+ exports.default = MicelioLayout;