strapi-plugin-cm-subnav-stacker 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,911 @@
1
+ import { useRef, useEffect, useState } from "react";
2
+ import { jsx, Fragment, jsxs } from "react/jsx-runtime";
3
+ import * as ReactDOM from "react-dom/client";
4
+ import { DesignSystemProvider, Box, Accordion, SubNav, SubNavHeader, SubNavSections, SubNavSection, Link, Button, SubNavLink } from "@strapi/design-system";
5
+ import { getFetchClient } from "@strapi/strapi/admin";
6
+ const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
7
+ const v = glob[path];
8
+ if (v) {
9
+ return typeof v === "function" ? v() : Promise.resolve(v);
10
+ }
11
+ return new Promise((_, reject) => {
12
+ (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
13
+ reject.bind(
14
+ null,
15
+ new Error(
16
+ "Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
17
+ )
18
+ )
19
+ );
20
+ });
21
+ };
22
+ const PLUGIN_ID = "cm-subnav-stacker";
23
+ const Initializer = ({ setPlugin }) => {
24
+ const ref = useRef(setPlugin);
25
+ useEffect(() => {
26
+ ref.current(PLUGIN_ID);
27
+ }, []);
28
+ return null;
29
+ };
30
+ const defaultDelimiter = " | ";
31
+ const defaultTemplate = "accordion";
32
+ const navQuery = 'nav[aria-label="Content Manager"]';
33
+ const getPluginConfig = async () => {
34
+ let delimiter = process.env.CM_SUBNAV_STACKER_DELIMITER || defaultDelimiter;
35
+ let template = process.env.CM_SUBNAV_STACKER_TEMPLATE || defaultTemplate;
36
+ const config = await fetch(`/cm-subnav-stacker/config`).then((res) => {
37
+ return res.json();
38
+ }).catch(() => ({}));
39
+ if (config) {
40
+ delimiter = config.delimiter || delimiter;
41
+ template = config.template || template;
42
+ }
43
+ return { delimiter, template };
44
+ };
45
+ const formatContentTypes = (delimiter, data) => {
46
+ if (!data || !data.data) return [];
47
+ const defaultSortOrder = data.data.length + 1e3;
48
+ return data.data.filter((ct) => ct.uid.startsWith("api::")).map((ct) => {
49
+ const displayName = ct.schema.displayName || ct.apiID;
50
+ const kind = ct.schema.kind;
51
+ const { sortOrder, cleanDisplayName } = extractSortOrder(displayName, defaultSortOrder);
52
+ const groupName = getContentTypeGroup(delimiter, cleanDisplayName);
53
+ let newDisplayName = cleanDisplayName;
54
+ if (newDisplayName.startsWith(groupName + delimiter)) {
55
+ newDisplayName = newDisplayName.slice((groupName + delimiter).length);
56
+ }
57
+ return {
58
+ uid: ct.uid,
59
+ name: ct.apiID,
60
+ displayName: newDisplayName,
61
+ group: groupName,
62
+ isDisplayed: true,
63
+ href: `/admin/content-manager/${kind === "singleType" ? "single-types" : "collection-types"}/${ct.uid}`,
64
+ kind,
65
+ sortOrder
66
+ };
67
+ });
68
+ };
69
+ const extractSortOrder = (displayName, defaultSortOrder = 999) => {
70
+ const match = displayName.match(/^\[(\d+)\]\s*/);
71
+ if (match) {
72
+ const sortOrder = parseInt(match[1], 10);
73
+ const cleanDisplayName = displayName.replace(/^\[\d+\]\s*/, "");
74
+ return { sortOrder, cleanDisplayName };
75
+ }
76
+ return { sortOrder: defaultSortOrder, cleanDisplayName: displayName };
77
+ };
78
+ const getContentTypeGroup = (delimiter, displayName) => {
79
+ const entity = displayName.split(delimiter).map((name) => name.trim());
80
+ return entity[0];
81
+ };
82
+ const cleanHeaderText = (text) => {
83
+ return text.replace(/^\[\d+\]\s*/, "");
84
+ };
85
+ const setupHeaderCleaning = () => {
86
+ const cleanHeaderElement = () => {
87
+ const headerDiv = document.querySelector('div[data-strapi-header="true"]');
88
+ if (headerDiv) {
89
+ const h1Element = headerDiv.querySelector("h1");
90
+ if (h1Element && h1Element.textContent) {
91
+ const cleanedText = cleanHeaderText(h1Element.textContent);
92
+ if (cleanedText !== h1Element.textContent) {
93
+ h1Element.textContent = cleanedText;
94
+ }
95
+ }
96
+ }
97
+ };
98
+ const cleanNavigationItems = () => {
99
+ let nav = document.querySelector(navQuery);
100
+ if (!nav) {
101
+ nav = document.querySelector("nav");
102
+ }
103
+ if (!nav) return;
104
+ const navLinks = nav.querySelectorAll('a[href*="/admin/content-manager/"]');
105
+ navLinks.forEach((link) => {
106
+ const textElements = link.querySelectorAll("div, span");
107
+ textElements.forEach((element) => {
108
+ if (element.textContent && element.textContent.match(/^\[\d+\]/)) {
109
+ const cleanedText = cleanHeaderText(element.textContent);
110
+ if (cleanedText !== element.textContent) {
111
+ element.textContent = cleanedText;
112
+ }
113
+ }
114
+ });
115
+ });
116
+ };
117
+ cleanHeaderElement();
118
+ cleanNavigationItems();
119
+ const headerObserver = new MutationObserver((mutations) => {
120
+ for (const mutation of mutations) {
121
+ if (mutation.type === "childList" || mutation.type === "characterData") {
122
+ const target = mutation.target;
123
+ if (target.nodeType === Node.ELEMENT_NODE) {
124
+ const headerDiv = target.closest('div[data-strapi-header="true"]') || target.querySelector('div[data-strapi-header="true"]');
125
+ if (headerDiv) {
126
+ cleanHeaderElement();
127
+ break;
128
+ }
129
+ const navItem = target.closest('a[href*="/admin/content-manager/"]') || target.querySelector('a[href*="/admin/content-manager/"]');
130
+ if (navItem) {
131
+ cleanNavigationItems();
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ }
137
+ });
138
+ headerObserver.observe(document.body, {
139
+ childList: true,
140
+ subtree: true,
141
+ characterData: true
142
+ });
143
+ return headerObserver;
144
+ };
145
+ const hideCollectionTypeHeaders = () => {
146
+ let nav = document.querySelector(navQuery);
147
+ if (!nav) {
148
+ nav = document.querySelector("nav");
149
+ }
150
+ if (!nav) return;
151
+ const allSpans = nav.querySelectorAll("span");
152
+ allSpans.forEach((span) => {
153
+ const text = span.textContent?.trim();
154
+ if (text === "Collection Types" || text === "Single Types") {
155
+ let parent = span.parentElement;
156
+ for (let i = 0; i < 8 && parent; i++) {
157
+ parent.querySelector("span");
158
+ const allSpansInParent = parent.querySelectorAll("span");
159
+ let hasCountBadge = false;
160
+ allSpansInParent.forEach((s) => {
161
+ const spanText = s.textContent?.trim();
162
+ if (spanText && /^\d+$/.test(spanText)) {
163
+ hasCountBadge = true;
164
+ }
165
+ });
166
+ if (hasCountBadge && parent.textContent?.includes(text)) {
167
+ const parentOl = parent.querySelector("ol");
168
+ if (!parentOl) {
169
+ parent.style.display = "none";
170
+ break;
171
+ }
172
+ }
173
+ parent = parent.parentElement;
174
+ }
175
+ }
176
+ });
177
+ };
178
+ const organizeByGroups = (contentTypes) => {
179
+ const groupMap = {};
180
+ contentTypes.forEach((contentType) => {
181
+ const groupName = contentType.group || "General";
182
+ if (!groupMap[groupName]) {
183
+ groupMap[groupName] = [];
184
+ }
185
+ groupMap[groupName].push(contentType);
186
+ });
187
+ Object.values(groupMap).forEach((items) => {
188
+ items.sort((a, b) => {
189
+ if (a.sortOrder !== b.sortOrder) {
190
+ return a.sortOrder - b.sortOrder;
191
+ }
192
+ return a.displayName.localeCompare(b.displayName);
193
+ });
194
+ });
195
+ const groups = Object.entries(groupMap).map(([name, items]) => {
196
+ const minSortOrder = Math.min(...items.map((item) => item.sortOrder));
197
+ return {
198
+ name,
199
+ items,
200
+ sortOrder: minSortOrder
201
+ };
202
+ });
203
+ groups.sort((a, b) => {
204
+ if (a.name === "General") return -1;
205
+ if (b.name === "General") return 1;
206
+ if (a.sortOrder !== b.sortOrder) {
207
+ return a.sortOrder - b.sortOrder;
208
+ }
209
+ return a.name.localeCompare(b.name);
210
+ });
211
+ return groups;
212
+ };
213
+ const OfficialNavigation = ({ groups }) => {
214
+ return /* @__PURE__ */ jsx(Box, { background: "neutral100", children: /* @__PURE__ */ jsxs(SubNav, { "aria-label": "Content Manager", style: { background: "transparent", height: "auto" }, children: [
215
+ /* @__PURE__ */ jsx(SubNavHeader, { label: "All" }),
216
+ /* @__PURE__ */ jsx(SubNavSections, { children: groups.map((group) => /* @__PURE__ */ jsx(OfficialContentTypeGroupSection, { group }, group.name)) })
217
+ ] }) });
218
+ };
219
+ const OfficialContentTypeGroupSection = ({ group }) => {
220
+ if (group.items.length === 0) return null;
221
+ const collectionTypes = group.items.filter((item) => item.kind === "collectionType");
222
+ const singleTypes = group.items.filter((item) => item.kind === "singleType");
223
+ const allTypes = [...collectionTypes, ...singleTypes];
224
+ return /* @__PURE__ */ jsx(SubNavSection, { label: group.name, collapsable: true, children: allTypes.map((item) => /* @__PURE__ */ jsx(OfficialContentTypeLink, { contentType: item }, item.uid)) });
225
+ };
226
+ const OfficialContentTypeLink = ({ contentType }) => {
227
+ const isActive = window.location.pathname.includes(contentType.uid);
228
+ return /* @__PURE__ */ jsx(
229
+ SubNavLink,
230
+ {
231
+ href: contentType.href,
232
+ active: isActive,
233
+ style: { background: "transparent" },
234
+ className: isActive ? "active" : "",
235
+ onClick: (e) => {
236
+ e.preventDefault();
237
+ if (window.history && window.history.pushState) {
238
+ window.history.pushState({}, "", contentType.href);
239
+ window.dispatchEvent(new PopStateEvent("popstate"));
240
+ } else {
241
+ window.location.href = contentType.href;
242
+ }
243
+ },
244
+ children: contentType.displayName
245
+ }
246
+ );
247
+ };
248
+ const V5Navigation = ({ groups }) => {
249
+ return /* @__PURE__ */ jsxs("div", { style: {
250
+ gap: 8,
251
+ alignItems: "stretch",
252
+ flexDirection: "column",
253
+ display: "flex"
254
+ }, children: [
255
+ /* @__PURE__ */ jsx("div", { style: {
256
+ paddingInlineStart: "20px",
257
+ paddingInlineEnd: "20px"
258
+ }, children: /* @__PURE__ */ jsx("div", { style: {
259
+ alignItems: "center",
260
+ justifyContent: "space-between",
261
+ flexDirection: "row",
262
+ display: "flex"
263
+ }, children: /* @__PURE__ */ jsx("div", { style: {
264
+ alignItems: "center",
265
+ flexDirection: "row",
266
+ display: "flex"
267
+ }, children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("span", { style: {
268
+ fontWeight: 600,
269
+ fontSize: "1.1rem",
270
+ lineHeight: 1.45,
271
+ textTransform: "uppercase",
272
+ color: "#666687"
273
+ }, children: "All" }) }) }) }) }),
274
+ /* @__PURE__ */ jsx(
275
+ "ol",
276
+ {
277
+ style: {
278
+ gap: 2,
279
+ alignItems: "stretch",
280
+ flexDirection: "column",
281
+ display: "flex",
282
+ marginInlineStart: 8,
283
+ marginInlineEnd: 8
284
+ },
285
+ children: groups.map((group, index2) => /* @__PURE__ */ jsx(V5ContentTypeGroupSection, { group }, `${group.name}-${index2}`))
286
+ }
287
+ )
288
+ ] });
289
+ };
290
+ const V5ContentTypeGroupSection = ({ group }) => {
291
+ if (group.items.length === 0) return null;
292
+ const collectionTypes = group.items.filter((item) => item.kind === "collectionType");
293
+ const singleTypes = group.items.filter((item) => item.kind === "singleType");
294
+ const allTypes = [...collectionTypes, ...singleTypes];
295
+ const [expanded, setExpanded] = useState(false);
296
+ useEffect(() => {
297
+ allTypes.some((item) => window.location.pathname.includes(item.uid)) && setExpanded(true);
298
+ }, []);
299
+ return allTypes.length === 1 ? /* @__PURE__ */ jsx(V5ContentTypeLink, { contentType: allTypes[0], asMain: true }, `${allTypes[0].uid}-0`) : /* @__PURE__ */ jsxs("li", { children: [
300
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
301
+ "div",
302
+ {
303
+ style: {
304
+ alignItems: "center",
305
+ justifyContent: "space-between",
306
+ flexDirection: "row",
307
+ display: "flex"
308
+ },
309
+ children: /* @__PURE__ */ jsxs(
310
+ "button",
311
+ {
312
+ style: {
313
+ cursor: "pointer",
314
+ width: "100%",
315
+ border: "none",
316
+ padding: 0,
317
+ background: "transparent",
318
+ display: "flex",
319
+ alignItems: "center",
320
+ height: 32,
321
+ borderRadius: 4,
322
+ paddingLeft: 12,
323
+ paddingRight: 12,
324
+ paddingTop: 8,
325
+ paddingBottom: 8
326
+ },
327
+ className: `${expanded ? "expanded" : ""}`,
328
+ onClick: () => setExpanded(!expanded),
329
+ children: [
330
+ /* @__PURE__ */ jsx("div", { style: {
331
+ width: "100%",
332
+ textAlign: "left",
333
+ paddingInlineEnd: "8px"
334
+ }, children: /* @__PURE__ */ jsx("span", { style: {
335
+ fontSize: "1.4rem",
336
+ lineHeight: "1.43",
337
+ color: "#32324d",
338
+ fontWeight: 500
339
+ }, children: group.name }) }),
340
+ /* @__PURE__ */ jsx(
341
+ "svg",
342
+ {
343
+ xmlns: "http://www.w3.org/2000/svg",
344
+ viewBox: "0 0 32 32",
345
+ width: 16,
346
+ height: 16,
347
+ fill: "#8e8ea9",
348
+ "aria-hidden": "true",
349
+ style: {
350
+ transform: expanded ? "rotate(0deg)" : "rotate(-90deg)",
351
+ transition: "transform 0.5s"
352
+ },
353
+ children: /* @__PURE__ */ jsx("path", { d: "m27.061 13.061-10 10a1.503 1.503 0 0 1-2.125 0l-10-10a1.503 1.503 0 1 1 2.125-2.125L16 19.875l8.939-8.94a1.502 1.502 0 1 1 2.125 2.125z" })
354
+ }
355
+ )
356
+ ]
357
+ }
358
+ )
359
+ }
360
+ ) }),
361
+ /* @__PURE__ */ jsx(
362
+ "ul",
363
+ {
364
+ style: {
365
+ gap: 2,
366
+ alignItems: "stretch",
367
+ flexDirection: "column",
368
+ display: "flex",
369
+ maxHeight: expanded ? 1e3 : 0,
370
+ overflow: "hidden",
371
+ transition: expanded ? "max-height 1s ease-in-out" : "max-height 0.5s cubic-bezier(0, 1, 0, 1)"
372
+ },
373
+ className: `custom-nav-group-items ${expanded ? "expanded" : ""}`,
374
+ children: allTypes.map((item, index2) => /* @__PURE__ */ jsx(V5ContentTypeLink, { contentType: item }, `${item.uid}-${index2}`))
375
+ }
376
+ )
377
+ ] });
378
+ };
379
+ const V5ContentTypeLink = ({ contentType, asMain }) => {
380
+ const isActive = window.location.pathname.includes(contentType.uid);
381
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
382
+ Link,
383
+ {
384
+ style: {
385
+ display: "flex",
386
+ alignItems: "center",
387
+ justifyContent: "space-between",
388
+ textDecoration: "none",
389
+ height: 32,
390
+ color: isActive ? "#271fe0" : "#32324d",
391
+ backgroundColor: isActive ? "#f0f0ff" : "transparent",
392
+ fontWeight: isActive ? 500 : "normal",
393
+ borderRadius: "4px"
394
+ },
395
+ className: `custom-nav-item ${isActive ? "active" : ""}`,
396
+ href: contentType.href,
397
+ onClick: (e) => {
398
+ e.preventDefault();
399
+ if (window.history && window.history.pushState) {
400
+ window.history.pushState({}, "", contentType.href);
401
+ window.dispatchEvent(new PopStateEvent("popstate"));
402
+ } else {
403
+ window.location.href = contentType.href;
404
+ }
405
+ },
406
+ children: /* @__PURE__ */ jsx("div", { style: {
407
+ paddingLeft: asMain ? "12px" : "24px"
408
+ }, children: /* @__PURE__ */ jsx(
409
+ "div",
410
+ {
411
+ style: {
412
+ gap: 4,
413
+ alignItems: "center",
414
+ justifyContent: "space-between",
415
+ flexDirection: "row",
416
+ display: "flex"
417
+ },
418
+ children: /* @__PURE__ */ jsx("div", { style: {
419
+ textOverflow: "ellipsis",
420
+ whiteSpace: "nowrap",
421
+ overflow: "hidden",
422
+ fontSize: "1.4rem",
423
+ lineHeight: "1.43",
424
+ color: isActive ? "#271fe0" : "#32324d",
425
+ fontWeight: asMain ? 500 : "normal"
426
+ }, children: contentType.displayName })
427
+ }
428
+ ) })
429
+ }
430
+ ) });
431
+ };
432
+ const AccordionNavigation = ({ groups }) => {
433
+ const value = groups.find(
434
+ (group) => group.items.find((item) => window.location.pathname.includes(item.uid))
435
+ )?.name;
436
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
437
+ Accordion.Root,
438
+ {
439
+ defaultValue: value,
440
+ style: { border: "none", borderRadius: 0 },
441
+ children: groups.map((group) => /* @__PURE__ */ jsx(AccordionContentTypeGroupSection, { group }, group.name))
442
+ }
443
+ ) });
444
+ };
445
+ const AccordionContentTypeGroupSection = ({ group }) => {
446
+ if (group.items.length === 0) return null;
447
+ const collectionTypes = group.items.filter((item) => item.kind === "collectionType");
448
+ const singleTypes = group.items.filter((item) => item.kind === "singleType");
449
+ const allTypes = [...collectionTypes, ...singleTypes];
450
+ return /* @__PURE__ */ jsxs(
451
+ Accordion.Item,
452
+ {
453
+ value: `${group.name}`,
454
+ style: { border: "none", borderRadius: 0 },
455
+ children: [
456
+ /* @__PURE__ */ jsx(Accordion.Header, { children: /* @__PURE__ */ jsx(Accordion.Trigger, { children: group.name }) }),
457
+ /* @__PURE__ */ jsx(Accordion.Content, { children: allTypes.map((item) => /* @__PURE__ */ jsx(AccordionContentTypeLink, { contentType: item }, item.uid)) })
458
+ ]
459
+ },
460
+ group.name
461
+ );
462
+ };
463
+ const AccordionContentTypeLink = ({ contentType }) => {
464
+ const isActive = window.location.pathname.includes(contentType.uid);
465
+ return /* @__PURE__ */ jsx(
466
+ Link,
467
+ {
468
+ href: contentType.href,
469
+ style: { textDecoration: "none", display: "block" },
470
+ onClick: (e) => {
471
+ e.preventDefault();
472
+ if (window.history && window.history.pushState) {
473
+ window.history.pushState({}, "", contentType.href);
474
+ window.dispatchEvent(new PopStateEvent("popstate"));
475
+ } else {
476
+ window.location.href = contentType.href;
477
+ }
478
+ },
479
+ children: /* @__PURE__ */ jsx(
480
+ Button,
481
+ {
482
+ variant: isActive ? "secondary" : "ghost",
483
+ fullWidth: true,
484
+ size: "L",
485
+ style: {
486
+ paddingLeft: 56,
487
+ justifyContent: "start",
488
+ borderRadius: 0,
489
+ borderLeft: "none",
490
+ borderRight: "none",
491
+ textOverflow: "ellipsis",
492
+ whiteSpace: "nowrap",
493
+ overflow: "hidden"
494
+ },
495
+ children: contentType.displayName
496
+ }
497
+ )
498
+ }
499
+ );
500
+ };
501
+ const NavigationApp = () => {
502
+ const [groups, setGroups] = useState([]);
503
+ const [currentPath, setCurrentPath] = useState(window.location.pathname);
504
+ const [isSearching, setIsSearching] = useState(false);
505
+ const [template, setTemplate] = useState(null);
506
+ const initialize = async () => {
507
+ const { delimiter, template: template2 } = await getPluginConfig();
508
+ setTemplate(template2);
509
+ console.log(`Building Navigation with delimiter "${delimiter}" and template "${template2}"`);
510
+ async function fetchAdminPermissions() {
511
+ try {
512
+ const { get } = getFetchClient();
513
+ const { data } = await get("/admin/users/me/permissions");
514
+ const list = Array.isArray(data) ? data : data?.data ?? data?.permissions ?? [];
515
+ window.strapiPermissions = list;
516
+ console.log("Successfully fetched permissions:", list.length);
517
+ return list;
518
+ } catch (error) {
519
+ console.error("Error fetching permissions with getFetchClient:", error);
520
+ return [];
521
+ }
522
+ }
523
+ const fetchContentTypes = async () => {
524
+ try {
525
+ console.log("Fetching content types from API...");
526
+ const { get } = getFetchClient();
527
+ const response = await get("/content-type-builder/content-types");
528
+ console.log("Raw API response:", response);
529
+ console.log("response.data:", response.data);
530
+ let rawData = response.data;
531
+ if (rawData && !Array.isArray(rawData) && rawData.data) {
532
+ rawData = rawData.data;
533
+ }
534
+ const contentTypes = formatContentTypes(delimiter, { data: rawData });
535
+ console.log("Formatted content types:", contentTypes.length, contentTypes);
536
+ let permissions = window.strapiPermissions || [];
537
+ if (!permissions || permissions.length === 0) {
538
+ console.log("Fetching permissions...");
539
+ permissions = await fetchAdminPermissions();
540
+ }
541
+ console.log("Permissions:", permissions.length);
542
+ let allowedContentTypes;
543
+ if (permissions.length > 0) {
544
+ allowedContentTypes = contentTypes.filter(
545
+ (ct) => permissions.some(
546
+ (p) => p.action === "plugin::content-manager.explorer.read" && p.subject === ct.uid
547
+ )
548
+ );
549
+ console.log("Allowed content types after permission filter:", allowedContentTypes.length, allowedContentTypes);
550
+ } else {
551
+ console.warn("No permissions available, showing all content types as fallback");
552
+ allowedContentTypes = contentTypes;
553
+ }
554
+ window.strapiContentTypes = allowedContentTypes;
555
+ const groups2 = organizeByGroups(allowedContentTypes);
556
+ console.log("Organized groups:", groups2);
557
+ setGroups(groups2);
558
+ } catch (error) {
559
+ console.error("Error fetching content types:", error);
560
+ }
561
+ };
562
+ if (window.strapiContentTypes && window.strapiContentTypes.length > 0) {
563
+ console.log("Using cached content types:", window.strapiContentTypes.length);
564
+ const groups2 = organizeByGroups(window.strapiContentTypes);
565
+ console.log("Groups from cache:", groups2);
566
+ setGroups(groups2);
567
+ } else {
568
+ console.log("No cached content types, fetching...");
569
+ fetchContentTypes();
570
+ }
571
+ };
572
+ useEffect(() => {
573
+ const handleLocationChange = () => {
574
+ setCurrentPath(window.location.pathname);
575
+ };
576
+ window.addEventListener("popstate", handleLocationChange);
577
+ const pathCheckInterval = setInterval(() => {
578
+ if (window.location.pathname !== currentPath) {
579
+ handleLocationChange();
580
+ }
581
+ }, 100);
582
+ return () => {
583
+ window.removeEventListener("popstate", handleLocationChange);
584
+ clearInterval(pathCheckInterval);
585
+ };
586
+ }, [currentPath]);
587
+ useEffect(() => {
588
+ initialize();
589
+ const hideCollectionHeaders = (hide = true) => {
590
+ let nav = document.querySelector(navQuery);
591
+ if (!nav) {
592
+ nav = document.querySelector("nav");
593
+ }
594
+ if (!nav) return;
595
+ const allSpans = nav.querySelectorAll("span");
596
+ allSpans.forEach((span) => {
597
+ const text = span.textContent?.trim();
598
+ if (text === "Collection Types" || text === "Single Types") {
599
+ let parent = span.parentElement;
600
+ for (let i = 0; i < 5 && parent; i++) {
601
+ if (parent.classList.contains("hvPsis") || parent.tagName === "DIV" && parent.querySelector("span")?.textContent?.match(/^\d+$/)) {
602
+ parent.style.display = hide ? "none" : "";
603
+ break;
604
+ }
605
+ parent = parent.parentElement;
606
+ }
607
+ }
608
+ });
609
+ };
610
+ hideCollectionHeaders(true);
611
+ const toggleIsSearching = (isSearching2) => {
612
+ let nav = document.querySelector(navQuery);
613
+ if (!nav) {
614
+ nav = document.querySelector("nav");
615
+ }
616
+ if (!nav) return;
617
+ const allOls = nav.querySelectorAll("ol");
618
+ const otherNavs = [];
619
+ allOls.forEach((ol) => {
620
+ const liChildren = Array.from(ol.children).filter(
621
+ (el) => el.tagName === "LI" && el.id !== "stack-nav-container"
622
+ );
623
+ otherNavs.push(...liChildren);
624
+ });
625
+ console.log("toggleIsSearching called with:", isSearching2, "Found other nav items:", otherNavs.length);
626
+ setIsSearching(isSearching2);
627
+ if (isSearching2) {
628
+ otherNavs.forEach((nav2) => {
629
+ nav2.style.display = "initial";
630
+ });
631
+ hideCollectionHeaders(false);
632
+ } else {
633
+ otherNavs.forEach((nav2) => {
634
+ nav2.style.display = "none";
635
+ });
636
+ hideCollectionHeaders(true);
637
+ }
638
+ const customNavContainer = document.getElementById("stack-nav-container");
639
+ if (customNavContainer) {
640
+ const customNavItems = customNavContainer.querySelectorAll("li");
641
+ console.log("Found custom nav items to toggle:", customNavItems.length);
642
+ customNavItems.forEach((item) => {
643
+ if (isSearching2) {
644
+ item.style.display = "none";
645
+ } else {
646
+ item.style.display = "";
647
+ }
648
+ });
649
+ }
650
+ };
651
+ const checkSearchInput = () => {
652
+ if (window.location.pathname === currentPath) {
653
+ const searchNav = document.querySelector(`${navQuery} input`);
654
+ if (searchNav) {
655
+ const value = searchNav.value;
656
+ toggleIsSearching(!!value);
657
+ return searchNav;
658
+ } else {
659
+ toggleIsSearching(false);
660
+ return null;
661
+ }
662
+ }
663
+ return null;
664
+ };
665
+ const searchInput = checkSearchInput();
666
+ let inputObserver = null;
667
+ if (searchInput) {
668
+ searchInput.addEventListener("input", () => {
669
+ const value = searchInput.value;
670
+ toggleIsSearching(!!value);
671
+ });
672
+ inputObserver = new MutationObserver((mutations) => {
673
+ mutations.forEach((mutation) => {
674
+ if (mutation.type === "attributes" && mutation.attributeName === "value") {
675
+ const value = searchInput.value;
676
+ toggleIsSearching(!!value);
677
+ }
678
+ });
679
+ });
680
+ inputObserver.observe(searchInput, {
681
+ attributes: true,
682
+ attributeFilter: ["value"]
683
+ });
684
+ }
685
+ const navObserver = new MutationObserver((mutations) => {
686
+ for (const mutation of mutations) {
687
+ if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
688
+ const searchInput2 = checkSearchInput();
689
+ if (searchInput2 && !inputObserver) {
690
+ searchInput2.addEventListener("input", () => {
691
+ const value = searchInput2.value;
692
+ toggleIsSearching(!!value);
693
+ });
694
+ inputObserver = new MutationObserver((mutations2) => {
695
+ mutations2.forEach((mutation2) => {
696
+ if (mutation2.type === "attributes" && mutation2.attributeName === "value") {
697
+ const value = searchInput2.value;
698
+ toggleIsSearching(!!value);
699
+ }
700
+ });
701
+ });
702
+ inputObserver.observe(searchInput2, {
703
+ attributes: true,
704
+ attributeFilter: ["value"]
705
+ });
706
+ break;
707
+ }
708
+ }
709
+ }
710
+ });
711
+ navObserver.observe(document.body, {
712
+ childList: true,
713
+ subtree: true
714
+ });
715
+ return () => {
716
+ if (inputObserver) {
717
+ inputObserver.disconnect();
718
+ }
719
+ navObserver.disconnect();
720
+ };
721
+ }, []);
722
+ console.log("NavigationApp render - groups:", groups.length, "isSearching:", isSearching, "template:", template);
723
+ if (groups.length === 0) {
724
+ console.log("No groups to display, returning null");
725
+ return null;
726
+ }
727
+ return !isSearching ? /* @__PURE__ */ jsx(DesignSystemProvider, { children: template === "accordion" ? /* @__PURE__ */ jsx(AccordionNavigation, { groups }) : template === "v5" ? /* @__PURE__ */ jsx(V5Navigation, { groups }) : /* @__PURE__ */ jsx(OfficialNavigation, { groups }) }) : /* @__PURE__ */ jsx(Fragment, {});
728
+ };
729
+ async function buildNavigation() {
730
+ console.log("Building stacked navigation...");
731
+ try {
732
+ if (typeof window !== "undefined") {
733
+ setupNavigationReplacement();
734
+ setupHeaderCleaning();
735
+ window.navigationInitialized = window.navigationInitialized || false;
736
+ window.reinitializeNavigation = () => {
737
+ setupNavigationReplacement();
738
+ };
739
+ }
740
+ } catch (error) {
741
+ console.error("Error building custom navigation:", error);
742
+ }
743
+ }
744
+ const setupNavigationReplacement = () => {
745
+ if (window.navObserver) {
746
+ window.navObserver.disconnect();
747
+ window.navObserver = void 0;
748
+ }
749
+ console.log("Setting up navigation observer...");
750
+ const processNavigation = (originalNav) => {
751
+ console.log("Found Content Manager navigation", originalNav);
752
+ const existingNav = document.getElementById("stack-nav-container");
753
+ if (existingNav) {
754
+ console.log("Custom navigation already exists");
755
+ if (existingNav.children.length === 0) {
756
+ const root2 = ReactDOM.createRoot(existingNav);
757
+ root2.render(/* @__PURE__ */ jsx(NavigationApp, {}));
758
+ }
759
+ return;
760
+ }
761
+ const navContainer = document.createElement("li");
762
+ navContainer.id = "stack-nav-container";
763
+ navContainer.className = "stack-navigation";
764
+ originalNav.prepend(navContainer);
765
+ console.log("Custom navigation container added, rendering React app...");
766
+ const root = ReactDOM.createRoot(navContainer);
767
+ root.render(/* @__PURE__ */ jsx(NavigationApp, {}));
768
+ };
769
+ let nav = document.querySelector(navQuery);
770
+ console.log("Looking for nav element with aria-label:", navQuery, "Found:", !!nav);
771
+ if (!nav) {
772
+ nav = document.querySelector("nav");
773
+ console.log("Fallback: Looking for any nav element, Found:", !!nav);
774
+ }
775
+ if (!nav) {
776
+ const allNavs = Array.from(document.querySelectorAll("nav"));
777
+ nav = allNavs.find((n) => {
778
+ const text = n.textContent || "";
779
+ return text.includes("Collection Types") || text.includes("Single Types");
780
+ }) || null;
781
+ console.log("Fallback: Looking for nav with Collection Types text, Found:", !!nav);
782
+ }
783
+ if (nav) {
784
+ const allOls = Array.from(nav.querySelectorAll("ol"));
785
+ console.log("Found", allOls.length, "ol elements in nav");
786
+ const mainOl = allOls.find((ol) => {
787
+ const hasStack = ol.querySelector("#stack-nav-container");
788
+ const hasCollectionTypes = ol.textContent?.includes("Collection Types");
789
+ const hasSingleTypes = ol.textContent?.includes("Single Types");
790
+ if (hasStack) return true;
791
+ return (hasCollectionTypes || hasSingleTypes) && ol.parentElement?.closest("ol") === null;
792
+ });
793
+ console.log("Found main ol:", !!mainOl);
794
+ if (mainOl) {
795
+ processNavigation(mainOl);
796
+ return;
797
+ }
798
+ if (allOls.length > 0) {
799
+ console.log("Fallback: using first ol element");
800
+ processNavigation(allOls[0]);
801
+ return;
802
+ }
803
+ }
804
+ console.log("Navigation not found yet - will retry via observer");
805
+ };
806
+ const isPluginEnabled$1 = async () => {
807
+ try {
808
+ const response = await fetch("/cm-subnav-stacker/config");
809
+ return response.ok;
810
+ } catch {
811
+ return false;
812
+ }
813
+ };
814
+ const setupContinuousNavigationCheck = () => {
815
+ if (window.navigationInitialized) return;
816
+ window.navigationInitialized = true;
817
+ setupNavigationReplacement();
818
+ setupHeaderCleaning();
819
+ if (!window.checkNavigationInterval) {
820
+ window.checkNavigationInterval = window.setInterval(() => {
821
+ if (window.location.pathname.includes("/admin/content-manager")) {
822
+ let navElement = document.querySelector(navQuery);
823
+ if (!navElement) {
824
+ navElement = document.querySelector("nav");
825
+ }
826
+ if (!navElement) {
827
+ const allNavs = Array.from(document.querySelectorAll("nav"));
828
+ navElement = allNavs.find((n) => {
829
+ const text = n.textContent || "";
830
+ return text.includes("Collection Types") || text.includes("Single Types");
831
+ }) || null;
832
+ }
833
+ let navContainer = null;
834
+ if (navElement) {
835
+ const allOls = Array.from(navElement.querySelectorAll("ol"));
836
+ navContainer = allOls.find((ol) => {
837
+ const hasStack = ol.querySelector("#stack-nav-container");
838
+ const hasCollectionTypes = ol.textContent?.includes("Collection Types");
839
+ return hasStack || hasCollectionTypes && ol.parentElement?.closest("ol") === null;
840
+ }) || allOls[0] || null;
841
+ }
842
+ const customNav = document.getElementById("stack-nav-container");
843
+ if (navContainer && !customNav) {
844
+ console.log("Navigation detected without custom navigation, reinitializing...");
845
+ setupNavigationReplacement();
846
+ }
847
+ hideCollectionTypeHeaders();
848
+ const headerDiv = document.querySelector('div[data-strapi-header="true"]');
849
+ if (headerDiv) {
850
+ const h1Element = headerDiv.querySelector("h1");
851
+ if (h1Element && h1Element.textContent && h1Element.textContent.match(/^\[\d+\]/)) {
852
+ const cleanedText = cleanHeaderText(h1Element.textContent);
853
+ h1Element.textContent = cleanedText;
854
+ }
855
+ }
856
+ }
857
+ }, 100);
858
+ }
859
+ };
860
+ if (typeof window !== "undefined") {
861
+ isPluginEnabled$1().then((enabled) => {
862
+ if (enabled) {
863
+ console.log("✅ cm-subnav-stacker is enabled, initializing navigation...");
864
+ setupContinuousNavigationCheck();
865
+ } else {
866
+ console.log("⏸️ cm-subnav-stacker is disabled, skipping initialization.");
867
+ }
868
+ });
869
+ }
870
+ const isPluginEnabled = async () => {
871
+ try {
872
+ const response = await fetch("/cm-subnav-stacker/config");
873
+ return response.ok;
874
+ } catch {
875
+ return false;
876
+ }
877
+ };
878
+ const index = {
879
+ register(app) {
880
+ console.log("🎯 Content Manager Subnavigation Stacker plugin - ADMIN REGISTER function called!");
881
+ app.registerPlugin({
882
+ id: PLUGIN_ID,
883
+ initializer: Initializer,
884
+ isReady: false,
885
+ name: PLUGIN_ID
886
+ });
887
+ isPluginEnabled().then((enabled) => {
888
+ if (enabled) {
889
+ console.log("✅ cm-subnav-stacker is enabled, building navigation...");
890
+ buildNavigation();
891
+ } else {
892
+ console.log("⏸️ cm-subnav-stacker is disabled, skipping navigation injection.");
893
+ }
894
+ });
895
+ },
896
+ async registerTrads({ locales }) {
897
+ return Promise.all(
898
+ locales.map(async (locale) => {
899
+ try {
900
+ const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("../_chunks/en-Byx4XI2L.mjs") }), `./translations/${locale}.json`, 3);
901
+ return { data, locale };
902
+ } catch {
903
+ return { data: {}, locale };
904
+ }
905
+ })
906
+ );
907
+ }
908
+ };
909
+ export {
910
+ index as default
911
+ };