@antibusy/strapi-plugin-content-manager-sort 1.1.7
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/README.md +115 -0
- package/dist/_chunks/en-B4KWt_jN.js +4 -0
- package/dist/_chunks/en-Byx4XI2L.mjs +4 -0
- package/dist/admin/index.js +780 -0
- package/dist/admin/index.mjs +763 -0
- package/dist/admin/src/components/Initializer.d.ts +5 -0
- package/dist/admin/src/components/PluginIcon.d.ts +2 -0
- package/dist/admin/src/components/SubNavInjector.d.ts +35 -0
- package/dist/admin/src/index.d.ts +10 -0
- package/dist/admin/src/pages/App.d.ts +2 -0
- package/dist/admin/src/pages/HomePage.d.ts +2 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/utils/getTranslation.d.ts +2 -0
- package/dist/server/index.js +84 -0
- package/dist/server/index.mjs +85 -0
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/config/index.d.ts +5 -0
- package/dist/server/src/content-types/index.d.ts +2 -0
- package/dist/server/src/controllers/controller.d.ts +8 -0
- package/dist/server/src/controllers/index.d.ts +9 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/index.d.ts +67 -0
- package/dist/server/src/middlewares/index.d.ts +2 -0
- package/dist/server/src/policies/index.d.ts +2 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/routes/content-api.d.ts +9 -0
- package/dist/server/src/routes/index.d.ts +27 -0
- package/dist/server/src/services/index.d.ts +12 -0
- package/dist/server/src/services/service.d.ts +11 -0
- package/package.json +112 -0
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const react = require("react");
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const ReactDOM = require("react-dom/client");
|
|
5
|
+
const admin = require("@strapi/strapi/admin");
|
|
6
|
+
function _interopNamespace(e) {
|
|
7
|
+
if (e && e.__esModule) return e;
|
|
8
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
9
|
+
if (e) {
|
|
10
|
+
for (const k in e) {
|
|
11
|
+
if (k !== "default") {
|
|
12
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
13
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: () => e[k]
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
n.default = e;
|
|
21
|
+
return Object.freeze(n);
|
|
22
|
+
}
|
|
23
|
+
const ReactDOM__namespace = /* @__PURE__ */ _interopNamespace(ReactDOM);
|
|
24
|
+
const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
|
|
25
|
+
const v = glob[path];
|
|
26
|
+
if (v) {
|
|
27
|
+
return typeof v === "function" ? v() : Promise.resolve(v);
|
|
28
|
+
}
|
|
29
|
+
return new Promise((_, reject) => {
|
|
30
|
+
(typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
|
|
31
|
+
reject.bind(
|
|
32
|
+
null,
|
|
33
|
+
new Error(
|
|
34
|
+
"Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const PLUGIN_ID = "content-manager-sort";
|
|
41
|
+
const Initializer = ({ setPlugin }) => {
|
|
42
|
+
const ref = react.useRef(setPlugin);
|
|
43
|
+
react.useEffect(() => {
|
|
44
|
+
ref.current(PLUGIN_ID);
|
|
45
|
+
}, []);
|
|
46
|
+
return null;
|
|
47
|
+
};
|
|
48
|
+
const defaultDelimiter = " | ";
|
|
49
|
+
const defaultTemplate = "accordion";
|
|
50
|
+
const navQuery = 'nav[aria-label="Content Manager"]';
|
|
51
|
+
const stackNavContainerId = "stack-nav-container";
|
|
52
|
+
const customNavContentSelector = '[data-content-manager-sort-content="true"]';
|
|
53
|
+
const contentManagerLinkSelector = [
|
|
54
|
+
'a[href*="/admin/content-manager/collection-types/"]',
|
|
55
|
+
'a[href*="/admin/content-manager/single-types/"]'
|
|
56
|
+
].join(", ");
|
|
57
|
+
const contentManagerSearchSelector = [
|
|
58
|
+
'input[name="search_contentType"]',
|
|
59
|
+
'input[type="search"][placeholder="Search"]'
|
|
60
|
+
].join(", ");
|
|
61
|
+
const navigationRoots = /* @__PURE__ */ new WeakMap();
|
|
62
|
+
const findContentManagerNavRoot = () => {
|
|
63
|
+
const labelledNav = document.querySelector(navQuery);
|
|
64
|
+
if (labelledNav) return labelledNav;
|
|
65
|
+
const existingNav = document.getElementById(stackNavContainerId);
|
|
66
|
+
const existingRoot = existingNav?.closest("nav, [data-radix-scroll-area-viewport], aside");
|
|
67
|
+
if (existingRoot) return existingRoot;
|
|
68
|
+
const searchInput = document.querySelector(contentManagerSearchSelector);
|
|
69
|
+
const searchRoot = searchInput?.closest("nav, [data-radix-scroll-area-viewport], aside");
|
|
70
|
+
if (searchRoot) return searchRoot;
|
|
71
|
+
const contentManagerLink = document.querySelector(contentManagerLinkSelector);
|
|
72
|
+
return contentManagerLink?.closest("nav, [data-radix-scroll-area-viewport], aside") || null;
|
|
73
|
+
};
|
|
74
|
+
const findContentManagerSearchInput = (root = findContentManagerNavRoot()) => {
|
|
75
|
+
return root?.querySelector(contentManagerSearchSelector) || null;
|
|
76
|
+
};
|
|
77
|
+
const findContentTypeLists = (root = findContentManagerNavRoot()) => {
|
|
78
|
+
if (!root) return [];
|
|
79
|
+
return Array.from(root.querySelectorAll("ol")).filter((ol) => {
|
|
80
|
+
return Boolean(
|
|
81
|
+
ol.querySelector(contentManagerLinkSelector) || ol.querySelector(`#${stackNavContainerId}`)
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
const findStackNavigationList = (root = findContentManagerNavRoot()) => {
|
|
86
|
+
const existingNav = document.getElementById(stackNavContainerId);
|
|
87
|
+
if (existingNav?.parentElement?.tagName === "OL") {
|
|
88
|
+
return existingNav.parentElement;
|
|
89
|
+
}
|
|
90
|
+
const lists = findContentTypeLists(root);
|
|
91
|
+
return lists.find((ol) => ol.querySelector('a[href*="/admin/content-manager/collection-types/"]')) || lists[0] || null;
|
|
92
|
+
};
|
|
93
|
+
const findOriginalContentTypeItems = (root = findContentManagerNavRoot()) => {
|
|
94
|
+
return findContentTypeLists(root).flatMap((ol) => {
|
|
95
|
+
return Array.from(ol.children).filter((child) => {
|
|
96
|
+
return child instanceof HTMLElement && child.tagName === "LI" && child.id !== stackNavContainerId;
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
const findSectionHeaderContainer = (label) => {
|
|
101
|
+
const labelText = label.textContent?.trim();
|
|
102
|
+
let parent = label.parentElement;
|
|
103
|
+
for (let i = 0; i < 8 && parent; i++) {
|
|
104
|
+
const spans = Array.from(parent.querySelectorAll("span"));
|
|
105
|
+
const hasLabel = spans.some((span) => span.textContent?.trim() === labelText);
|
|
106
|
+
const hasCountBadge = spans.some((span) => /^\d+$/.test(span.textContent?.trim() || ""));
|
|
107
|
+
const containsContentTypeList = Boolean(parent.querySelector(contentManagerLinkSelector));
|
|
108
|
+
if (parent.tagName === "DIV" && hasLabel && !containsContentTypeList && (hasCountBadge || i >= 2)) {
|
|
109
|
+
return parent;
|
|
110
|
+
}
|
|
111
|
+
parent = parent.parentElement;
|
|
112
|
+
}
|
|
113
|
+
return label.parentElement;
|
|
114
|
+
};
|
|
115
|
+
const hasCustomNavigationContent = () => {
|
|
116
|
+
return Boolean(
|
|
117
|
+
document.getElementById(stackNavContainerId)?.querySelector(customNavContentSelector)
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
const getPluginConfig = async () => {
|
|
121
|
+
let delimiter = process.env.CONTENT_MANAGER_SORT_DELIMITER || defaultDelimiter;
|
|
122
|
+
let template = process.env.CONTENT_MANAGER_SORT_TEMPLATE || defaultTemplate;
|
|
123
|
+
const config = await fetch(`/content-manager-sort/config`).then((res) => {
|
|
124
|
+
return res.json();
|
|
125
|
+
}).catch(() => ({}));
|
|
126
|
+
if (config) {
|
|
127
|
+
delimiter = config.delimiter || delimiter;
|
|
128
|
+
template = config.template || template;
|
|
129
|
+
}
|
|
130
|
+
return { delimiter, template };
|
|
131
|
+
};
|
|
132
|
+
const formatContentTypes = (delimiter, data) => {
|
|
133
|
+
if (!data || !data.data) return [];
|
|
134
|
+
const defaultSortOrder = data.data.length + 1e3;
|
|
135
|
+
return data.data.filter((ct) => {
|
|
136
|
+
return ct.uid.startsWith("api::") || ct.uid.startsWith("plugin::");
|
|
137
|
+
}).map((ct) => {
|
|
138
|
+
const displayName = ct.info?.displayName || ct.schema?.displayName || ct.apiID;
|
|
139
|
+
const kind = ct.kind || ct.schema?.kind;
|
|
140
|
+
const { sortOrder, cleanDisplayName } = extractSortOrder(displayName, defaultSortOrder);
|
|
141
|
+
const groupName = getContentTypeGroup(delimiter, cleanDisplayName);
|
|
142
|
+
let newDisplayName = cleanDisplayName;
|
|
143
|
+
if (newDisplayName.startsWith(groupName + delimiter)) {
|
|
144
|
+
newDisplayName = newDisplayName.slice((groupName + delimiter).length);
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
uid: ct.uid,
|
|
148
|
+
name: ct.apiID,
|
|
149
|
+
displayName: newDisplayName,
|
|
150
|
+
group: groupName,
|
|
151
|
+
isDisplayed: true,
|
|
152
|
+
href: `/admin/content-manager/${kind === "singleType" ? "single-types" : "collection-types"}/${ct.uid}`,
|
|
153
|
+
kind,
|
|
154
|
+
sortOrder
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
const extractSortOrder = (displayName, defaultSortOrder = 999) => {
|
|
159
|
+
const match = displayName.match(/^\[(\d+)\]\s*/);
|
|
160
|
+
if (match) {
|
|
161
|
+
const sortOrder = parseInt(match[1], 10);
|
|
162
|
+
const cleanDisplayName = displayName.replace(/^\[\d+\]\s*/, "");
|
|
163
|
+
return { sortOrder, cleanDisplayName };
|
|
164
|
+
}
|
|
165
|
+
return { sortOrder: defaultSortOrder, cleanDisplayName: displayName };
|
|
166
|
+
};
|
|
167
|
+
const getContentTypeGroup = (delimiter, displayName) => {
|
|
168
|
+
const entity = displayName.split(delimiter).map((name) => name.trim());
|
|
169
|
+
return entity[0];
|
|
170
|
+
};
|
|
171
|
+
const cleanHeaderText = (text) => {
|
|
172
|
+
return text.replace(/^\[\d+\]\s*/, "");
|
|
173
|
+
};
|
|
174
|
+
const setupHeaderCleaning = () => {
|
|
175
|
+
const cleanHeaderElement = () => {
|
|
176
|
+
const headerDiv = document.querySelector('div[data-strapi-header="true"]');
|
|
177
|
+
if (headerDiv) {
|
|
178
|
+
const h1Element = headerDiv.querySelector("h1");
|
|
179
|
+
if (h1Element && h1Element.textContent) {
|
|
180
|
+
const cleanedText = cleanHeaderText(h1Element.textContent);
|
|
181
|
+
if (cleanedText !== h1Element.textContent) {
|
|
182
|
+
h1Element.textContent = cleanedText;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const cleanNavigationItems = () => {
|
|
188
|
+
const nav = findContentManagerNavRoot();
|
|
189
|
+
if (!nav) return;
|
|
190
|
+
const navLinks = nav.querySelectorAll(contentManagerLinkSelector);
|
|
191
|
+
navLinks.forEach((link) => {
|
|
192
|
+
const textElements = link.querySelectorAll("div, span");
|
|
193
|
+
textElements.forEach((element) => {
|
|
194
|
+
if (element.textContent && element.textContent.match(/^\[\d+\]/)) {
|
|
195
|
+
const cleanedText = cleanHeaderText(element.textContent);
|
|
196
|
+
if (cleanedText !== element.textContent) {
|
|
197
|
+
element.textContent = cleanedText;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
cleanHeaderElement();
|
|
204
|
+
cleanNavigationItems();
|
|
205
|
+
const headerObserver = new MutationObserver((mutations) => {
|
|
206
|
+
for (const mutation of mutations) {
|
|
207
|
+
if (mutation.type === "childList" || mutation.type === "characterData") {
|
|
208
|
+
const target = mutation.target;
|
|
209
|
+
if (target.nodeType === Node.ELEMENT_NODE) {
|
|
210
|
+
const headerDiv = target.closest('div[data-strapi-header="true"]') || target.querySelector('div[data-strapi-header="true"]');
|
|
211
|
+
if (headerDiv) {
|
|
212
|
+
cleanHeaderElement();
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
const navItem = target.closest(contentManagerLinkSelector) || target.querySelector(contentManagerLinkSelector);
|
|
216
|
+
if (navItem) {
|
|
217
|
+
cleanNavigationItems();
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
headerObserver.observe(document.body, {
|
|
225
|
+
childList: true,
|
|
226
|
+
subtree: true,
|
|
227
|
+
characterData: true
|
|
228
|
+
});
|
|
229
|
+
return headerObserver;
|
|
230
|
+
};
|
|
231
|
+
const hideCollectionTypeHeaders = (hide = true, root = findContentManagerNavRoot()) => {
|
|
232
|
+
if (!root) return;
|
|
233
|
+
const allSpans = root.querySelectorAll("span");
|
|
234
|
+
allSpans.forEach((span) => {
|
|
235
|
+
if (span.closest(`#${stackNavContainerId}`)) return;
|
|
236
|
+
const text = span.textContent?.trim();
|
|
237
|
+
if (text === "Collection Types" || text === "Single Types") {
|
|
238
|
+
const headerContainer = findSectionHeaderContainer(span);
|
|
239
|
+
if (headerContainer) {
|
|
240
|
+
headerContainer.style.display = hide ? "none" : "";
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
const setOriginalNavigationVisible = (visible, root = findContentManagerNavRoot()) => {
|
|
246
|
+
if (!root) return;
|
|
247
|
+
findOriginalContentTypeItems(root).forEach((item) => {
|
|
248
|
+
item.style.display = visible ? "" : "none";
|
|
249
|
+
});
|
|
250
|
+
hideCollectionTypeHeaders(!visible, root);
|
|
251
|
+
};
|
|
252
|
+
const syncOriginalNavigationVisibility = (customNavigationReady = hasCustomNavigationContent()) => {
|
|
253
|
+
const root = findContentManagerNavRoot();
|
|
254
|
+
const searchInput = findContentManagerSearchInput(root);
|
|
255
|
+
const isSearching = Boolean(searchInput?.value);
|
|
256
|
+
setOriginalNavigationVisible(isSearching || !customNavigationReady, root);
|
|
257
|
+
return isSearching;
|
|
258
|
+
};
|
|
259
|
+
const organizeByGroups = (contentTypes) => {
|
|
260
|
+
const groupMap = {};
|
|
261
|
+
contentTypes.forEach((contentType) => {
|
|
262
|
+
const groupName = contentType.group || "General";
|
|
263
|
+
if (!groupMap[groupName]) {
|
|
264
|
+
groupMap[groupName] = [];
|
|
265
|
+
}
|
|
266
|
+
groupMap[groupName].push(contentType);
|
|
267
|
+
});
|
|
268
|
+
Object.values(groupMap).forEach((items) => {
|
|
269
|
+
items.sort((a, b) => {
|
|
270
|
+
if (a.sortOrder !== b.sortOrder) {
|
|
271
|
+
return a.sortOrder - b.sortOrder;
|
|
272
|
+
}
|
|
273
|
+
return a.displayName.localeCompare(b.displayName);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
const groups = Object.entries(groupMap).map(([name, items]) => {
|
|
277
|
+
const minSortOrder = Math.min(...items.map((item) => item.sortOrder));
|
|
278
|
+
return {
|
|
279
|
+
name,
|
|
280
|
+
items,
|
|
281
|
+
sortOrder: minSortOrder
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
groups.sort((a, b) => {
|
|
285
|
+
if (a.name === "General") return -1;
|
|
286
|
+
if (b.name === "General") return 1;
|
|
287
|
+
if (a.sortOrder !== b.sortOrder) {
|
|
288
|
+
return a.sortOrder - b.sortOrder;
|
|
289
|
+
}
|
|
290
|
+
return a.name.localeCompare(b.name);
|
|
291
|
+
});
|
|
292
|
+
return groups;
|
|
293
|
+
};
|
|
294
|
+
const navigationFrameStyle = {
|
|
295
|
+
display: "flex",
|
|
296
|
+
flexDirection: "column",
|
|
297
|
+
gap: 8,
|
|
298
|
+
paddingBlock: 4,
|
|
299
|
+
paddingInline: 8,
|
|
300
|
+
listStyle: "none"
|
|
301
|
+
};
|
|
302
|
+
const navigationListStyle = {
|
|
303
|
+
display: "flex",
|
|
304
|
+
flexDirection: "column",
|
|
305
|
+
gap: 2,
|
|
306
|
+
margin: 0,
|
|
307
|
+
padding: 0,
|
|
308
|
+
listStyle: "none"
|
|
309
|
+
};
|
|
310
|
+
const groupTriggerStyle = {
|
|
311
|
+
alignItems: "center",
|
|
312
|
+
background: "transparent",
|
|
313
|
+
border: "none",
|
|
314
|
+
borderRadius: 4,
|
|
315
|
+
color: "#32324d",
|
|
316
|
+
cursor: "pointer",
|
|
317
|
+
display: "flex",
|
|
318
|
+
fontSize: "1.4rem",
|
|
319
|
+
fontWeight: 500,
|
|
320
|
+
height: 32,
|
|
321
|
+
justifyContent: "space-between",
|
|
322
|
+
lineHeight: "1.43",
|
|
323
|
+
paddingBlock: 8,
|
|
324
|
+
paddingInline: 12,
|
|
325
|
+
textAlign: "left",
|
|
326
|
+
width: "100%"
|
|
327
|
+
};
|
|
328
|
+
const getGroupItems = (group) => {
|
|
329
|
+
const collectionTypes = group.items.filter((item) => item.kind === "collectionType");
|
|
330
|
+
const singleTypes = group.items.filter((item) => item.kind === "singleType");
|
|
331
|
+
return [...collectionTypes, ...singleTypes];
|
|
332
|
+
};
|
|
333
|
+
const isContentTypeActive = (contentType) => {
|
|
334
|
+
return window.location.pathname.includes(contentType.uid);
|
|
335
|
+
};
|
|
336
|
+
const navigateToContentType = (href) => {
|
|
337
|
+
if (window.history && window.history.pushState) {
|
|
338
|
+
window.history.pushState({}, "", href);
|
|
339
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
340
|
+
} else {
|
|
341
|
+
window.location.href = href;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
const NavigationFrame = ({ children }) => {
|
|
345
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-content-manager-sort-content": "true", style: navigationFrameStyle, children: [
|
|
346
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
347
|
+
"div",
|
|
348
|
+
{
|
|
349
|
+
style: {
|
|
350
|
+
color: "#666687",
|
|
351
|
+
fontSize: "1.1rem",
|
|
352
|
+
fontWeight: 600,
|
|
353
|
+
lineHeight: 1.45,
|
|
354
|
+
paddingInline: 12,
|
|
355
|
+
textTransform: "uppercase"
|
|
356
|
+
},
|
|
357
|
+
children: "All"
|
|
358
|
+
}
|
|
359
|
+
),
|
|
360
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { style: navigationListStyle, children })
|
|
361
|
+
] });
|
|
362
|
+
};
|
|
363
|
+
const ContentTypeLink = ({
|
|
364
|
+
contentType,
|
|
365
|
+
asMain
|
|
366
|
+
}) => {
|
|
367
|
+
const isActive = isContentTypeActive(contentType);
|
|
368
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { style: { listStyle: "none" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
369
|
+
"a",
|
|
370
|
+
{
|
|
371
|
+
className: `custom-nav-item ${isActive ? "active" : ""}`,
|
|
372
|
+
href: contentType.href,
|
|
373
|
+
style: {
|
|
374
|
+
alignItems: "center",
|
|
375
|
+
backgroundColor: isActive ? "#f0f0ff" : "transparent",
|
|
376
|
+
borderRadius: 4,
|
|
377
|
+
color: isActive ? "#271fe0" : "#32324d",
|
|
378
|
+
display: "flex",
|
|
379
|
+
fontSize: "1.4rem",
|
|
380
|
+
fontWeight: isActive || asMain ? 500 : 400,
|
|
381
|
+
height: 32,
|
|
382
|
+
lineHeight: "1.43",
|
|
383
|
+
minWidth: 0,
|
|
384
|
+
overflow: "hidden",
|
|
385
|
+
paddingInlineStart: asMain ? 12 : 24,
|
|
386
|
+
paddingInlineEnd: 12,
|
|
387
|
+
textDecoration: "none",
|
|
388
|
+
textOverflow: "ellipsis",
|
|
389
|
+
whiteSpace: "nowrap"
|
|
390
|
+
},
|
|
391
|
+
onClick: (event) => {
|
|
392
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
event.preventDefault();
|
|
396
|
+
navigateToContentType(contentType.href);
|
|
397
|
+
},
|
|
398
|
+
children: contentType.displayName
|
|
399
|
+
}
|
|
400
|
+
) });
|
|
401
|
+
};
|
|
402
|
+
const ContentTypeGroupSection = ({ directSingle, group }) => {
|
|
403
|
+
const allTypes = getGroupItems(group);
|
|
404
|
+
const isActiveGroup = allTypes.some(isContentTypeActive);
|
|
405
|
+
const [expanded, setExpanded] = react.useState(isActiveGroup);
|
|
406
|
+
react.useEffect(() => {
|
|
407
|
+
if (isActiveGroup) {
|
|
408
|
+
setExpanded(true);
|
|
409
|
+
}
|
|
410
|
+
}, [isActiveGroup]);
|
|
411
|
+
if (allTypes.length === 0) return null;
|
|
412
|
+
if (directSingle && allTypes.length === 1) {
|
|
413
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ContentTypeLink, { contentType: allTypes[0], asMain: true }, `${allTypes[0].uid}-0`);
|
|
414
|
+
}
|
|
415
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { style: { listStyle: "none" }, children: [
|
|
416
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
417
|
+
"button",
|
|
418
|
+
{
|
|
419
|
+
type: "button",
|
|
420
|
+
"aria-expanded": expanded,
|
|
421
|
+
className: `custom-nav-group-trigger ${expanded ? "expanded" : ""}`,
|
|
422
|
+
style: groupTriggerStyle,
|
|
423
|
+
onClick: () => setExpanded(!expanded),
|
|
424
|
+
children: [
|
|
425
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: group.name }),
|
|
426
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
427
|
+
"svg",
|
|
428
|
+
{
|
|
429
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
430
|
+
viewBox: "0 0 32 32",
|
|
431
|
+
width: 16,
|
|
432
|
+
height: 16,
|
|
433
|
+
fill: "#8e8ea9",
|
|
434
|
+
"aria-hidden": "true",
|
|
435
|
+
style: {
|
|
436
|
+
flexShrink: 0,
|
|
437
|
+
transform: expanded ? "rotate(0deg)" : "rotate(-90deg)",
|
|
438
|
+
transition: "transform 0.2s ease"
|
|
439
|
+
},
|
|
440
|
+
children: /* @__PURE__ */ jsxRuntime.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" })
|
|
441
|
+
}
|
|
442
|
+
)
|
|
443
|
+
]
|
|
444
|
+
}
|
|
445
|
+
),
|
|
446
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
447
|
+
"ul",
|
|
448
|
+
{
|
|
449
|
+
className: `custom-nav-group-items ${expanded ? "expanded" : ""}`,
|
|
450
|
+
style: {
|
|
451
|
+
...navigationListStyle,
|
|
452
|
+
maxHeight: expanded ? 1e3 : 0,
|
|
453
|
+
overflow: "hidden",
|
|
454
|
+
transition: expanded ? "max-height 0.25s ease-in-out" : "max-height 0.18s cubic-bezier(0, 1, 0, 1)"
|
|
455
|
+
},
|
|
456
|
+
children: allTypes.map((item) => /* @__PURE__ */ jsxRuntime.jsx(ContentTypeLink, { contentType: item }, item.uid))
|
|
457
|
+
}
|
|
458
|
+
)
|
|
459
|
+
] });
|
|
460
|
+
};
|
|
461
|
+
const OfficialNavigation = ({ groups }) => {
|
|
462
|
+
return /* @__PURE__ */ jsxRuntime.jsx(NavigationFrame, { children: groups.map((group) => /* @__PURE__ */ jsxRuntime.jsx(ContentTypeGroupSection, { group }, group.name)) });
|
|
463
|
+
};
|
|
464
|
+
const V5Navigation = ({ groups }) => {
|
|
465
|
+
return /* @__PURE__ */ jsxRuntime.jsx(NavigationFrame, { children: groups.map((group, index2) => /* @__PURE__ */ jsxRuntime.jsx(ContentTypeGroupSection, { group, directSingle: true }, `${group.name}-${index2}`)) });
|
|
466
|
+
};
|
|
467
|
+
const AccordionNavigation = ({ groups }) => {
|
|
468
|
+
return /* @__PURE__ */ jsxRuntime.jsx(NavigationFrame, { children: groups.map((group) => /* @__PURE__ */ jsxRuntime.jsx(ContentTypeGroupSection, { group }, group.name)) });
|
|
469
|
+
};
|
|
470
|
+
const NavigationApp = () => {
|
|
471
|
+
const [groups, setGroups] = react.useState([]);
|
|
472
|
+
const [currentPath, setCurrentPath] = react.useState(window.location.pathname);
|
|
473
|
+
const [isSearching, setIsSearching] = react.useState(false);
|
|
474
|
+
const [template, setTemplate] = react.useState(null);
|
|
475
|
+
const initialize = async () => {
|
|
476
|
+
const { delimiter, template: template2 } = await getPluginConfig();
|
|
477
|
+
setTemplate(template2);
|
|
478
|
+
console.log(`Building Navigation with delimiter "${delimiter}" and template "${template2}"`);
|
|
479
|
+
async function fetchAdminPermissions() {
|
|
480
|
+
try {
|
|
481
|
+
const { get } = admin.getFetchClient();
|
|
482
|
+
const { data } = await get("/admin/users/me/permissions");
|
|
483
|
+
console.log(data);
|
|
484
|
+
const list = Array.isArray(data) ? data : data?.data ?? data?.permissions ?? [];
|
|
485
|
+
window.strapiPermissions = list;
|
|
486
|
+
console.log("Successfully fetched permissions:", list.length);
|
|
487
|
+
return list;
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.error("Error fetching permissions with getFetchClient:", error);
|
|
490
|
+
return [];
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const fetchContentTypes = async () => {
|
|
494
|
+
try {
|
|
495
|
+
console.log("Fetching content types from API...");
|
|
496
|
+
const { get } = admin.getFetchClient();
|
|
497
|
+
const response = await get("/content-manager/content-types");
|
|
498
|
+
console.log("Raw API response:", response);
|
|
499
|
+
console.log("response.data:", response.data);
|
|
500
|
+
let rawData = response.data;
|
|
501
|
+
if (rawData && !Array.isArray(rawData) && rawData.data) {
|
|
502
|
+
rawData = rawData.data;
|
|
503
|
+
}
|
|
504
|
+
const contentTypes = formatContentTypes(delimiter, { data: rawData });
|
|
505
|
+
console.log("Formatted content types:", contentTypes.length, contentTypes);
|
|
506
|
+
let permissions = window.strapiPermissions || [];
|
|
507
|
+
if (!permissions || permissions.length === 0) {
|
|
508
|
+
console.log("Fetching permissions...");
|
|
509
|
+
permissions = await fetchAdminPermissions();
|
|
510
|
+
}
|
|
511
|
+
console.log("Permissions:", permissions.length);
|
|
512
|
+
let allowedContentTypes;
|
|
513
|
+
if (permissions.length > 0) {
|
|
514
|
+
allowedContentTypes = contentTypes.filter((ct) => {
|
|
515
|
+
const hasContentManagerRead = permissions.some(
|
|
516
|
+
(p) => p.action === "plugin::content-manager.explorer.read" && p.subject === ct.uid
|
|
517
|
+
);
|
|
518
|
+
const isPluginContentType = ct.uid.startsWith("plugin::");
|
|
519
|
+
const hasPluginRead = isPluginContentType && permissions.some(
|
|
520
|
+
(p) => p.subject === ct.uid && (p.action?.includes("read") || p.action?.includes("find") || p.action === "plugin::content-manager.explorer.read")
|
|
521
|
+
);
|
|
522
|
+
return hasContentManagerRead || hasPluginRead;
|
|
523
|
+
});
|
|
524
|
+
console.log(
|
|
525
|
+
"Allowed content types after permission filter:",
|
|
526
|
+
allowedContentTypes.length,
|
|
527
|
+
allowedContentTypes
|
|
528
|
+
);
|
|
529
|
+
} else {
|
|
530
|
+
console.warn("No permissions available, showing all content types as fallback");
|
|
531
|
+
allowedContentTypes = contentTypes;
|
|
532
|
+
}
|
|
533
|
+
window.strapiContentTypes = allowedContentTypes;
|
|
534
|
+
const groups2 = organizeByGroups(allowedContentTypes);
|
|
535
|
+
console.log("Organized groups:", groups2);
|
|
536
|
+
setGroups(groups2);
|
|
537
|
+
} catch (error) {
|
|
538
|
+
console.error("Error fetching content types:", error);
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
if (window.strapiContentTypes && window.strapiContentTypes.length > 0) {
|
|
542
|
+
console.log("Using cached content types:", window.strapiContentTypes.length);
|
|
543
|
+
const groups2 = organizeByGroups(window.strapiContentTypes);
|
|
544
|
+
console.log("Groups from cache:", groups2);
|
|
545
|
+
setGroups(groups2);
|
|
546
|
+
} else {
|
|
547
|
+
console.log("No cached content types, fetching...");
|
|
548
|
+
fetchContentTypes();
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
react.useEffect(() => {
|
|
552
|
+
const handleLocationChange = () => {
|
|
553
|
+
setCurrentPath(window.location.pathname);
|
|
554
|
+
};
|
|
555
|
+
window.addEventListener("popstate", handleLocationChange);
|
|
556
|
+
const pathCheckInterval = setInterval(() => {
|
|
557
|
+
if (window.location.pathname !== currentPath) {
|
|
558
|
+
handleLocationChange();
|
|
559
|
+
}
|
|
560
|
+
}, 100);
|
|
561
|
+
return () => {
|
|
562
|
+
window.removeEventListener("popstate", handleLocationChange);
|
|
563
|
+
clearInterval(pathCheckInterval);
|
|
564
|
+
};
|
|
565
|
+
}, [currentPath]);
|
|
566
|
+
react.useEffect(() => {
|
|
567
|
+
initialize();
|
|
568
|
+
}, []);
|
|
569
|
+
react.useEffect(() => {
|
|
570
|
+
let searchInput = null;
|
|
571
|
+
let inputObserver = null;
|
|
572
|
+
const syncNavigation = () => {
|
|
573
|
+
setIsSearching(syncOriginalNavigationVisibility());
|
|
574
|
+
};
|
|
575
|
+
const handleSearchInputChange = () => {
|
|
576
|
+
syncNavigation();
|
|
577
|
+
};
|
|
578
|
+
const disconnectInputObserver = () => {
|
|
579
|
+
if (inputObserver) {
|
|
580
|
+
inputObserver.disconnect();
|
|
581
|
+
inputObserver = null;
|
|
582
|
+
}
|
|
583
|
+
if (searchInput) {
|
|
584
|
+
searchInput.removeEventListener("input", handleSearchInputChange);
|
|
585
|
+
searchInput = null;
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
const attachSearchInput = () => {
|
|
589
|
+
const nextSearchInput = findContentManagerSearchInput();
|
|
590
|
+
if (nextSearchInput === searchInput) return;
|
|
591
|
+
disconnectInputObserver();
|
|
592
|
+
searchInput = nextSearchInput;
|
|
593
|
+
if (!searchInput) return;
|
|
594
|
+
searchInput.addEventListener("input", handleSearchInputChange);
|
|
595
|
+
inputObserver = new MutationObserver(handleSearchInputChange);
|
|
596
|
+
inputObserver.observe(searchInput, {
|
|
597
|
+
attributes: true,
|
|
598
|
+
attributeFilter: ["value"]
|
|
599
|
+
});
|
|
600
|
+
};
|
|
601
|
+
attachSearchInput();
|
|
602
|
+
syncNavigation();
|
|
603
|
+
const navObserver = new MutationObserver(() => {
|
|
604
|
+
attachSearchInput();
|
|
605
|
+
syncNavigation();
|
|
606
|
+
});
|
|
607
|
+
navObserver.observe(document.body, {
|
|
608
|
+
childList: true,
|
|
609
|
+
subtree: true
|
|
610
|
+
});
|
|
611
|
+
return () => {
|
|
612
|
+
disconnectInputObserver();
|
|
613
|
+
navObserver.disconnect();
|
|
614
|
+
};
|
|
615
|
+
}, []);
|
|
616
|
+
react.useEffect(() => {
|
|
617
|
+
const frame = window.requestAnimationFrame(() => {
|
|
618
|
+
setIsSearching(syncOriginalNavigationVisibility(groups.length > 0));
|
|
619
|
+
});
|
|
620
|
+
return () => {
|
|
621
|
+
window.cancelAnimationFrame(frame);
|
|
622
|
+
};
|
|
623
|
+
}, [currentPath, groups.length, template]);
|
|
624
|
+
console.log(
|
|
625
|
+
"NavigationApp render - groups:",
|
|
626
|
+
groups.length,
|
|
627
|
+
"isSearching:",
|
|
628
|
+
isSearching,
|
|
629
|
+
"template:",
|
|
630
|
+
template
|
|
631
|
+
);
|
|
632
|
+
if (groups.length === 0) {
|
|
633
|
+
console.log("No groups to display, returning null");
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
return !isSearching ? template === "accordion" ? /* @__PURE__ */ jsxRuntime.jsx(AccordionNavigation, { groups }) : template === "v5" ? /* @__PURE__ */ jsxRuntime.jsx(V5Navigation, { groups }) : /* @__PURE__ */ jsxRuntime.jsx(OfficialNavigation, { groups }) : null;
|
|
637
|
+
};
|
|
638
|
+
async function buildNavigation() {
|
|
639
|
+
console.log("Building stacked navigation...");
|
|
640
|
+
try {
|
|
641
|
+
if (typeof window !== "undefined") {
|
|
642
|
+
setupNavigationReplacement();
|
|
643
|
+
setupHeaderCleaning();
|
|
644
|
+
window.navigationInitialized = window.navigationInitialized || false;
|
|
645
|
+
window.reinitializeNavigation = () => {
|
|
646
|
+
setupNavigationReplacement();
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
} catch (error) {
|
|
650
|
+
console.error("Error building custom navigation:", error);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const renderNavigationApp = (container) => {
|
|
654
|
+
let root = navigationRoots.get(container);
|
|
655
|
+
if (!root) {
|
|
656
|
+
root = ReactDOM__namespace.createRoot(container);
|
|
657
|
+
navigationRoots.set(container, root);
|
|
658
|
+
}
|
|
659
|
+
root.render(/* @__PURE__ */ jsxRuntime.jsx(NavigationApp, {}));
|
|
660
|
+
};
|
|
661
|
+
const setupNavigationReplacement = () => {
|
|
662
|
+
if (window.navObserver) {
|
|
663
|
+
window.navObserver.disconnect();
|
|
664
|
+
window.navObserver = void 0;
|
|
665
|
+
}
|
|
666
|
+
console.log("Setting up navigation observer...");
|
|
667
|
+
const processNavigation = (navigationList2) => {
|
|
668
|
+
console.log("Found Content Manager navigation list", navigationList2);
|
|
669
|
+
const existingNav = document.getElementById(stackNavContainerId);
|
|
670
|
+
if (existingNav) {
|
|
671
|
+
console.log("Custom navigation already exists");
|
|
672
|
+
renderNavigationApp(existingNav);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const navContainer = document.createElement("li");
|
|
676
|
+
navContainer.id = stackNavContainerId;
|
|
677
|
+
navContainer.className = "stack-navigation";
|
|
678
|
+
navContainer.style.listStyle = "none";
|
|
679
|
+
navContainer.style.margin = "0";
|
|
680
|
+
navContainer.style.padding = "0";
|
|
681
|
+
navigationList2.prepend(navContainer);
|
|
682
|
+
console.log("Custom navigation container added, rendering React app...");
|
|
683
|
+
renderNavigationApp(navContainer);
|
|
684
|
+
};
|
|
685
|
+
const navRoot = findContentManagerNavRoot();
|
|
686
|
+
const navigationList = findStackNavigationList(navRoot);
|
|
687
|
+
console.log("Looking for Content Manager nav root. Found:", !!navRoot, "list:", !!navigationList);
|
|
688
|
+
if (navigationList) {
|
|
689
|
+
processNavigation(navigationList);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
console.log("Navigation not found yet - will retry via observer");
|
|
693
|
+
};
|
|
694
|
+
const isPluginEnabled$1 = async () => {
|
|
695
|
+
try {
|
|
696
|
+
const response = await fetch("/content-manager-sort/config");
|
|
697
|
+
return response.ok;
|
|
698
|
+
} catch {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
const setupContinuousNavigationCheck = () => {
|
|
703
|
+
if (window.navigationInitialized) return;
|
|
704
|
+
window.navigationInitialized = true;
|
|
705
|
+
setupNavigationReplacement();
|
|
706
|
+
setupHeaderCleaning();
|
|
707
|
+
if (!window.checkNavigationInterval) {
|
|
708
|
+
window.checkNavigationInterval = window.setInterval(() => {
|
|
709
|
+
if (window.location.pathname.includes("/admin/content-manager")) {
|
|
710
|
+
const navRoot = findContentManagerNavRoot();
|
|
711
|
+
const navContainer = findStackNavigationList(navRoot);
|
|
712
|
+
const customNav = document.getElementById(stackNavContainerId);
|
|
713
|
+
if (navContainer && !customNav) {
|
|
714
|
+
console.log("Navigation detected without custom navigation, reinitializing...");
|
|
715
|
+
setupNavigationReplacement();
|
|
716
|
+
}
|
|
717
|
+
syncOriginalNavigationVisibility(hasCustomNavigationContent());
|
|
718
|
+
const headerDiv = document.querySelector('div[data-strapi-header="true"]');
|
|
719
|
+
if (headerDiv) {
|
|
720
|
+
const h1Element = headerDiv.querySelector("h1");
|
|
721
|
+
if (h1Element && h1Element.textContent && h1Element.textContent.match(/^\[\d+\]/)) {
|
|
722
|
+
const cleanedText = cleanHeaderText(h1Element.textContent);
|
|
723
|
+
h1Element.textContent = cleanedText;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}, 100);
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
if (typeof window !== "undefined") {
|
|
731
|
+
isPluginEnabled$1().then((enabled) => {
|
|
732
|
+
if (enabled) {
|
|
733
|
+
console.log("✅ content-manager-sort is enabled, initializing navigation...");
|
|
734
|
+
document.body.style.overflowY = "hidden";
|
|
735
|
+
setupContinuousNavigationCheck();
|
|
736
|
+
} else {
|
|
737
|
+
console.log("⏸️ content-manager-sort is disabled, skipping initialization.");
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
const isPluginEnabled = async () => {
|
|
742
|
+
try {
|
|
743
|
+
const response = await fetch("/content-manager-sort/config");
|
|
744
|
+
return response.ok;
|
|
745
|
+
} catch {
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
const index = {
|
|
750
|
+
register(app) {
|
|
751
|
+
console.log("🎯 Content Manager Subnavigation Stacker plugin - ADMIN REGISTER function called!");
|
|
752
|
+
app.registerPlugin({
|
|
753
|
+
id: PLUGIN_ID,
|
|
754
|
+
initializer: Initializer,
|
|
755
|
+
isReady: false,
|
|
756
|
+
name: PLUGIN_ID
|
|
757
|
+
});
|
|
758
|
+
isPluginEnabled().then((enabled) => {
|
|
759
|
+
if (enabled) {
|
|
760
|
+
console.log("✅ content-manager-sort is enabled, building navigation...");
|
|
761
|
+
buildNavigation();
|
|
762
|
+
} else {
|
|
763
|
+
console.log("⏸️ content-manager-sort is disabled, skipping navigation injection.");
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
},
|
|
767
|
+
async registerTrads({ locales }) {
|
|
768
|
+
return Promise.all(
|
|
769
|
+
locales.map(async (locale) => {
|
|
770
|
+
try {
|
|
771
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("../_chunks/en-B4KWt_jN.js")) }), `./translations/${locale}.json`, 3);
|
|
772
|
+
return { data, locale };
|
|
773
|
+
} catch {
|
|
774
|
+
return { data: {}, locale };
|
|
775
|
+
}
|
|
776
|
+
})
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
module.exports = index;
|