strapi-plugin-magic-mark 1.1.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 (68) hide show
  1. package/COPYRIGHT_NOTICE.txt +20 -0
  2. package/LICENSE +28 -0
  3. package/README.md +372 -0
  4. package/dist/_chunks/App-7ZH1Reka.mjs +1390 -0
  5. package/dist/_chunks/App-CMSut1pt.js +1392 -0
  6. package/dist/_chunks/de-Bag-366k.mjs +49 -0
  7. package/dist/_chunks/de-Dic_hhjg.js +49 -0
  8. package/dist/_chunks/en-C5BvHqNo.js +54 -0
  9. package/dist/_chunks/en-zokEerzt.mjs +54 -0
  10. package/dist/_chunks/es-BlSQpU1z.js +54 -0
  11. package/dist/_chunks/es-Br1ucP3h.mjs +54 -0
  12. package/dist/_chunks/fr-BHciYPYG.js +54 -0
  13. package/dist/_chunks/fr-Dzo3kt_q.mjs +54 -0
  14. package/dist/_chunks/index-B-Cc7QNW.mjs +322 -0
  15. package/dist/_chunks/index-B11QBtag.js +324 -0
  16. package/dist/_chunks/index-DYHEyGJr.mjs +2020 -0
  17. package/dist/_chunks/index-DkkmdRgb.js +2024 -0
  18. package/dist/_chunks/pt-DQoGyzyD.mjs +54 -0
  19. package/dist/_chunks/pt-Dawo5aUA.js +54 -0
  20. package/dist/admin/index.js +3 -0
  21. package/dist/admin/index.mjs +4 -0
  22. package/dist/admin/src/components/AdvancedFilterButton.d.ts +3 -0
  23. package/dist/admin/src/components/AdvancedFilterModal.d.ts +21 -0
  24. package/dist/admin/src/components/CreateEditModal.d.ts +11 -0
  25. package/dist/admin/src/components/CreateViewModal.d.ts +9 -0
  26. package/dist/admin/src/components/CustomSelect.d.ts +13 -0
  27. package/dist/admin/src/components/FilterPreview.d.ts +6 -0
  28. package/dist/admin/src/components/HomePage.d.ts +0 -0
  29. package/dist/admin/src/components/Initializer.d.ts +6 -0
  30. package/dist/admin/src/components/LicenseGuard.d.ts +6 -0
  31. package/dist/admin/src/components/PluginIcon.d.ts +3 -0
  32. package/dist/admin/src/components/QueryBuilder.d.ts +23 -0
  33. package/dist/admin/src/components/SimpleAdvancedFilterModal.d.ts +15 -0
  34. package/dist/admin/src/components/ViewsListPopover.d.ts +2 -0
  35. package/dist/admin/src/components/ViewsWidget.d.ts +8 -0
  36. package/dist/admin/src/index.d.ts +15 -0
  37. package/dist/admin/src/pages/App.d.ts +3 -0
  38. package/dist/admin/src/pages/HomePage.d.ts +3 -0
  39. package/dist/admin/src/pages/HomePageModern.d.ts +3 -0
  40. package/dist/admin/src/pages/License/index.d.ts +3 -0
  41. package/dist/admin/src/permissions.d.ts +15 -0
  42. package/dist/admin/src/pluginId.d.ts +2 -0
  43. package/dist/admin/src/utils/queryGenerator.d.ts +28 -0
  44. package/dist/admin/src/utils/queryParser.d.ts +25 -0
  45. package/dist/admin/src/utils/queryToStructure.d.ts +20 -0
  46. package/dist/server/index.js +1309 -0
  47. package/dist/server/index.mjs +1307 -0
  48. package/dist/server/src/bootstrap.d.ts +5 -0
  49. package/dist/server/src/config/index.d.ts +5 -0
  50. package/dist/server/src/content-types/index.d.ts +82 -0
  51. package/dist/server/src/controllers/controller.d.ts +11 -0
  52. package/dist/server/src/controllers/index.d.ts +20 -0
  53. package/dist/server/src/controllers/license.d.ts +27 -0
  54. package/dist/server/src/destroy.d.ts +5 -0
  55. package/dist/server/src/index.d.ts +195 -0
  56. package/dist/server/src/middlewares/index.d.ts +4 -0
  57. package/dist/server/src/middlewares/license-check.d.ts +6 -0
  58. package/dist/server/src/policies/index.d.ts +4 -0
  59. package/dist/server/src/policies/license-check.d.ts +6 -0
  60. package/dist/server/src/register.d.ts +5 -0
  61. package/dist/server/src/routes/admin.d.ts +12 -0
  62. package/dist/server/src/routes/content-api.d.ts +5 -0
  63. package/dist/server/src/routes/index.d.ts +18 -0
  64. package/dist/server/src/services/index.d.ts +57 -0
  65. package/dist/server/src/services/license-guard.d.ts +103 -0
  66. package/dist/server/src/services/service.d.ts +33 -0
  67. package/package.json +108 -0
  68. package/strapi-server.js +4 -0
@@ -0,0 +1,2024 @@
1
+ "use strict";
2
+ const React = require("react");
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const icons = require("@strapi/icons");
5
+ const designSystem = require("@strapi/design-system");
6
+ const reactIntl = require("react-intl");
7
+ const reactRouterDom = require("react-router-dom");
8
+ const admin = require("@strapi/strapi/admin");
9
+ const styled = require("styled-components");
10
+ const ReactDOM = require("react-dom");
11
+ const qs = require("qs");
12
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
13
+ const React__default = /* @__PURE__ */ _interopDefault(React);
14
+ const styled__default = /* @__PURE__ */ _interopDefault(styled);
15
+ const ReactDOM__default = /* @__PURE__ */ _interopDefault(ReactDOM);
16
+ const qs__default = /* @__PURE__ */ _interopDefault(qs);
17
+ const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
18
+ const v = glob[path];
19
+ if (v) {
20
+ return typeof v === "function" ? v() : Promise.resolve(v);
21
+ }
22
+ return new Promise((_, reject) => {
23
+ (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
24
+ reject.bind(
25
+ null,
26
+ new Error(
27
+ "Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
28
+ )
29
+ )
30
+ );
31
+ });
32
+ };
33
+ const strapi = {
34
+ name: "magic-mark"
35
+ };
36
+ const pluginPkg = {
37
+ strapi
38
+ };
39
+ const pluginId = "magic-mark";
40
+ const Initializer = ({ setPlugin }) => {
41
+ const ref = React.useRef(setPlugin);
42
+ React.useEffect(() => {
43
+ ref.current(pluginId);
44
+ }, []);
45
+ return null;
46
+ };
47
+ const PluginIcon = () => /* @__PURE__ */ jsxRuntime.jsx(icons.Book, {});
48
+ const ViewsListPopover = ({
49
+ views,
50
+ onViewClick,
51
+ isLoading = false,
52
+ buttonElement
53
+ }) => {
54
+ const { formatMessage } = reactIntl.useIntl();
55
+ return /* @__PURE__ */ jsxRuntime.jsx(
56
+ designSystem.Box,
57
+ {
58
+ hasRadius: true,
59
+ shadow: "popover",
60
+ padding: 3,
61
+ style: {
62
+ position: "absolute",
63
+ top: "100%",
64
+ left: 0,
65
+ marginTop: "8px",
66
+ minWidth: "200px",
67
+ maxWidth: "300px",
68
+ zIndex: 1e3,
69
+ backgroundColor: "white"
70
+ },
71
+ children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", textAlign: "center", children: formatMessage({ id: "loading", defaultMessage: "Loading..." }) }) : views && views.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 1, as: "nav", children: views.map((view) => /* @__PURE__ */ jsxRuntime.jsx(
72
+ designSystem.Button,
73
+ {
74
+ variant: "ghost",
75
+ fullWidth: true,
76
+ onClick: () => onViewClick(view),
77
+ style: {
78
+ textAlign: "left",
79
+ justifyContent: "flex-start"
80
+ },
81
+ children: view.name
82
+ },
83
+ view.id
84
+ )) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage({
85
+ id: `${pluginId}.ViewsWidget.ViewsPopover.emptyList`,
86
+ defaultMessage: "You don't have any private views saved yet..."
87
+ }) })
88
+ }
89
+ );
90
+ };
91
+ const parseQueryString = (queryString) => {
92
+ const result = {
93
+ filters: [],
94
+ sort: [],
95
+ populate: []
96
+ };
97
+ if (!queryString) return result;
98
+ const params = new URLSearchParams(queryString);
99
+ params.forEach((value, key) => {
100
+ console.log("[QueryParser] Parsing:", key, "=", value);
101
+ if (key.startsWith("filters[")) {
102
+ const filter = parseFilterKey(key, value);
103
+ if (filter) {
104
+ result.filters.push(filter);
105
+ }
106
+ } else if (key === "sort") {
107
+ result.sort = value.split(",").map((s) => s.trim());
108
+ } else if (key.startsWith("populate")) {
109
+ const populateField = extractPopulateField(key);
110
+ if (populateField && !result.populate.includes(populateField)) {
111
+ result.populate.push(populateField);
112
+ }
113
+ } else if (key === "page") {
114
+ result.page = parseInt(value);
115
+ } else if (key === "pageSize") {
116
+ result.pageSize = parseInt(value);
117
+ }
118
+ });
119
+ return result;
120
+ };
121
+ const parseFilterKey = (key, value) => {
122
+ try {
123
+ let logic;
124
+ if (key.includes("[$and]")) {
125
+ logic = "AND";
126
+ } else if (key.includes("[$or]")) {
127
+ logic = "OR";
128
+ }
129
+ const fieldMatch = key.match(/\[([^\]$]+)\](?=\[\$)/);
130
+ const field = fieldMatch ? fieldMatch[1] : null;
131
+ const operatorMatch = key.match(/\[\$(\w+)\]/);
132
+ const operator = operatorMatch ? operatorMatch[1] : "eq";
133
+ if (!field) {
134
+ console.warn("[QueryParser] Could not extract field from:", key);
135
+ return null;
136
+ }
137
+ return {
138
+ field: formatFieldName(field),
139
+ operator: formatOperator(operator),
140
+ value,
141
+ logic
142
+ };
143
+ } catch (error) {
144
+ console.error("[QueryParser] Error parsing filter:", key, error);
145
+ return null;
146
+ }
147
+ };
148
+ const extractPopulateField = (key) => {
149
+ const match = key.match(/populate\[([^\]]+)\]/);
150
+ return match ? match[1] : null;
151
+ };
152
+ const formatFieldName = (field) => {
153
+ const formatted = field.replace(/([A-Z])/g, " $1");
154
+ return formatted.charAt(0).toUpperCase() + formatted.slice(1);
155
+ };
156
+ const formatOperator = (operator) => {
157
+ const operatorMap = {
158
+ "eq": "=",
159
+ "ne": "≠",
160
+ "lt": "<",
161
+ "lte": "≤",
162
+ "gt": ">",
163
+ "gte": "≥",
164
+ "in": "in",
165
+ "notIn": "not in",
166
+ "contains": "contains",
167
+ "notContains": "not contains",
168
+ "containsi": "contains (case insensitive)",
169
+ "notContainsi": "not contains (case insensitive)",
170
+ "startsWith": "starts with",
171
+ "endsWith": "ends with",
172
+ "null": "is null",
173
+ "notNull": "is not null"
174
+ };
175
+ return operatorMap[operator] || operator;
176
+ };
177
+ const FilterChip = styled__default.default.div`
178
+ display: inline-flex;
179
+ align-items: center;
180
+ padding: 4px 12px;
181
+ border-radius: 4px;
182
+ background: ${(props) => props.logic === "OR" ? "#FFF4E6" : "#E8F4FD"};
183
+ border: 1px solid ${(props) => props.logic === "OR" ? "#FFB84D" : "#5DA9E9"};
184
+ font-size: 12px;
185
+ margin: 4px;
186
+ gap: 4px;
187
+ `;
188
+ const LogicBadge$1 = styled__default.default.span`
189
+ font-weight: bold;
190
+ color: ${(props) => props.logic === "OR" ? "#D97706" : "#0369A1"};
191
+ font-size: 10px;
192
+ text-transform: uppercase;
193
+ `;
194
+ const PopulateChip = styled__default.default.div`
195
+ display: inline-flex;
196
+ align-items: center;
197
+ padding: 4px 12px;
198
+ border-radius: 4px;
199
+ background: #F0FDF4;
200
+ border: 1px solid #86EFAC;
201
+ font-size: 12px;
202
+ margin: 4px;
203
+ `;
204
+ const SortChip = styled__default.default.div`
205
+ display: inline-flex;
206
+ align-items: center;
207
+ padding: 4px 12px;
208
+ border-radius: 4px;
209
+ background: #FEF3C7;
210
+ border: 1px solid #FCD34D;
211
+ font-size: 12px;
212
+ margin: 4px;
213
+ `;
214
+ const FilterPreview = ({ query }) => {
215
+ const parsed = parseQueryString(query);
216
+ if (!query || parsed.filters.length === 0 && parsed.sort.length === 0 && parsed.populate.length === 0) {
217
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 2, background: "neutral100", borderRadius: "4px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "No filters applied" }) });
218
+ }
219
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 3, background: "neutral100", borderRadius: "4px", children: [
220
+ parsed.filters.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 2, children: [
221
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, children: "Filters:" }),
222
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { wrap: "wrap", gap: 1, children: parsed.filters.map((filter, idx) => /* @__PURE__ */ jsxRuntime.jsxs(FilterChip, { logic: filter.logic, children: [
223
+ filter.logic && /* @__PURE__ */ jsxRuntime.jsx(LogicBadge$1, { logic: filter.logic, children: filter.logic }),
224
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
225
+ filter.field,
226
+ " ",
227
+ filter.operator,
228
+ " ",
229
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: filter.value })
230
+ ] })
231
+ ] }, idx)) })
232
+ ] }),
233
+ parsed.sort.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 2, children: [
234
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, children: "Sorting:" }),
235
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { wrap: "wrap", gap: 1, children: parsed.sort.map((sort, idx) => /* @__PURE__ */ jsxRuntime.jsxs(SortChip, { children: [
236
+ "📊 ",
237
+ sort
238
+ ] }, idx)) })
239
+ ] }),
240
+ parsed.populate.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
241
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, children: "Relations:" }),
242
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { wrap: "wrap", gap: 1, children: parsed.populate.map((field, idx) => /* @__PURE__ */ jsxRuntime.jsxs(PopulateChip, { children: [
243
+ "🔗 ",
244
+ field
245
+ ] }, idx)) })
246
+ ] })
247
+ ] });
248
+ };
249
+ const EmojiPicker = styled__default.default.div`
250
+ display: grid;
251
+ grid-template-columns: repeat(8, 1fr);
252
+ gap: 8px;
253
+ padding: 12px;
254
+ background: #f7f8fa;
255
+ border-radius: 4px;
256
+ max-height: 200px;
257
+ overflow-y: auto;
258
+ `;
259
+ const EmojiButton = styled__default.default.button`
260
+ padding: 8px;
261
+ font-size: 24px;
262
+ border: 2px solid ${(props) => props.isSelected ? "#3945C9" : "#e0e0e0"};
263
+ background: white;
264
+ border-radius: 4px;
265
+ cursor: pointer;
266
+ transition: all 0.2s ease;
267
+
268
+ &:hover {
269
+ border-color: #3945C9;
270
+ background: #f7f8fa;
271
+ }
272
+ `;
273
+ const BOOKMARK_EMOJIS = ["🔖", "📌", "⭐", "💫", "❤️", "🎯", "🚀", "📝", "🔗", "🌟", "💼", "🎨", "📚", "🔔", "✅", "🎁"];
274
+ const CreateEditModal = ({
275
+ bookmark,
276
+ onClose,
277
+ onSuccess,
278
+ pluginId: pluginId2,
279
+ currentPath,
280
+ currentQuery
281
+ }) => {
282
+ const { formatMessage } = reactIntl.useIntl();
283
+ const { post, put, get } = admin.useFetchClient();
284
+ const [name2, setName] = React.useState("");
285
+ const [path, setPath] = React.useState(currentPath || "");
286
+ const [query, setQuery] = React.useState(currentQuery || "");
287
+ const [emoji, setEmoji] = React.useState("🔖");
288
+ const [description, setDescription] = React.useState("");
289
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
290
+ const [showEmojiPicker, setShowEmojiPicker] = React.useState(false);
291
+ const [error, setError] = React.useState("");
292
+ const [isPublic, setIsPublic] = React.useState(false);
293
+ const [sharedWithRoles, setSharedWithRoles] = React.useState([]);
294
+ const [sharedWithUsers, setSharedWithUsers] = React.useState([]);
295
+ const [availableRoles, setAvailableRoles] = React.useState([]);
296
+ const [availableUsers, setAvailableUsers] = React.useState([]);
297
+ const [loadingRoles, setLoadingRoles] = React.useState(false);
298
+ const [loadingUsers, setLoadingUsers] = React.useState(false);
299
+ React.useEffect(() => {
300
+ if (bookmark) {
301
+ setName(bookmark.name);
302
+ setPath(bookmark.path);
303
+ setQuery(bookmark.query || "");
304
+ setEmoji(bookmark.emoji);
305
+ setDescription(bookmark.description || "");
306
+ setIsPublic(bookmark.isPublic || false);
307
+ setSharedWithRoles(bookmark.sharedWithRoles || []);
308
+ setSharedWithUsers(bookmark.sharedWithUsers || []);
309
+ }
310
+ fetchRoles();
311
+ fetchUsers();
312
+ }, [bookmark]);
313
+ const fetchRoles = async () => {
314
+ setLoadingRoles(true);
315
+ try {
316
+ const response = await get(`/${pluginId2}/roles`);
317
+ const roles = response.data?.data?.data || response.data?.data || response.data || [];
318
+ setAvailableRoles(roles);
319
+ } catch (error2) {
320
+ console.error("[Magic-Mark] Error fetching roles:", error2);
321
+ } finally {
322
+ setLoadingRoles(false);
323
+ }
324
+ };
325
+ const fetchUsers = async () => {
326
+ setLoadingUsers(true);
327
+ try {
328
+ const meResponse = await get("/admin/users/me");
329
+ const currentUser = meResponse.data || meResponse;
330
+ const currentUserId = currentUser?.id;
331
+ const currentUserEmail = currentUser?.email;
332
+ const response = await get("/admin/users?pageSize=100&page=1&sort=firstname");
333
+ const allUsers = response.data?.data?.results || response.data?.results || [];
334
+ const users = Array.isArray(allUsers) ? allUsers.filter((u) => {
335
+ const matchById = u.id === currentUserId || u.id === Number(currentUserId) || String(u.id) === String(currentUserId);
336
+ const matchByEmail = u.email === currentUserEmail;
337
+ const shouldExclude = matchById || matchByEmail;
338
+ if (shouldExclude) {
339
+ }
340
+ return !shouldExclude;
341
+ }) : [];
342
+ setAvailableUsers(users);
343
+ } catch (error2) {
344
+ console.error("[Magic-Mark] Error fetching users:", error2);
345
+ try {
346
+ const response = await get(`/${pluginId2}/users`);
347
+ const users = response.data?.data?.data || response.data?.data || response.data || [];
348
+ setAvailableUsers(users);
349
+ } catch (fallbackError) {
350
+ console.warn("[Magic-Mark] Both user endpoints failed, feature disabled");
351
+ setAvailableUsers([]);
352
+ }
353
+ } finally {
354
+ setLoadingUsers(false);
355
+ }
356
+ };
357
+ const validateForm = () => {
358
+ setError("");
359
+ if (!name2.trim()) {
360
+ setError(formatMessage({
361
+ id: `${pluginId2}.error.nameRequired`,
362
+ defaultMessage: "Name is required"
363
+ }));
364
+ return false;
365
+ }
366
+ if (!path.trim()) {
367
+ setError(formatMessage({
368
+ id: `${pluginId2}.error.pathRequired`,
369
+ defaultMessage: "Path is required"
370
+ }));
371
+ return false;
372
+ }
373
+ return true;
374
+ };
375
+ const handleSubmit = async () => {
376
+ if (!validateForm()) return;
377
+ setIsSubmitting(true);
378
+ try {
379
+ const endpoint = bookmark ? `/${pluginId2}/bookmarks/${bookmark.id}` : `/${pluginId2}/bookmarks`;
380
+ const body = {
381
+ name: name2,
382
+ path,
383
+ query,
384
+ emoji,
385
+ description,
386
+ isPublic,
387
+ sharedWithRoles,
388
+ sharedWithUsers
389
+ };
390
+ let result;
391
+ if (bookmark) {
392
+ result = await put(endpoint, body);
393
+ } else {
394
+ result = await post(endpoint, body);
395
+ }
396
+ onSuccess();
397
+ } catch (error2) {
398
+ console.error("[Magic-Mark] Error saving bookmark:", error2);
399
+ setError(formatMessage({
400
+ id: `${pluginId2}.error.save`,
401
+ defaultMessage: "Failed to save bookmark"
402
+ }));
403
+ } finally {
404
+ setIsSubmitting(false);
405
+ }
406
+ };
407
+ const isEditing = !!bookmark;
408
+ return /* @__PURE__ */ jsxRuntime.jsx(
409
+ "div",
410
+ {
411
+ style: {
412
+ position: "fixed",
413
+ top: 0,
414
+ left: 0,
415
+ right: 0,
416
+ bottom: 0,
417
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
418
+ display: "flex",
419
+ alignItems: "center",
420
+ justifyContent: "center",
421
+ zIndex: 999
422
+ },
423
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
424
+ designSystem.Box,
425
+ {
426
+ padding: 6,
427
+ style: {
428
+ backgroundColor: "white",
429
+ borderRadius: "8px",
430
+ maxHeight: "90vh",
431
+ overflow: "auto",
432
+ maxWidth: "600px",
433
+ width: "90%"
434
+ },
435
+ children: [
436
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 6, children: [
437
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h2", variant: "beta", children: isEditing ? formatMessage({
438
+ id: `${pluginId2}.modal.edit`,
439
+ defaultMessage: "Edit Bookmark"
440
+ }) : formatMessage({
441
+ id: `${pluginId2}.modal.create`,
442
+ defaultMessage: "Save as Bookmark"
443
+ }) }),
444
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: onClose, variant: "ghost", type: "button", children: /* @__PURE__ */ jsxRuntime.jsx(icons.Cross, {}) })
445
+ ] }),
446
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(
447
+ designSystem.Box,
448
+ {
449
+ style: {
450
+ display: "flex",
451
+ flexDirection: "column",
452
+ gap: "16px"
453
+ },
454
+ children: [
455
+ error && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 3, background: "danger100", borderRadius: "4px", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "danger600", children: error }) }),
456
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
457
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, children: formatMessage({
458
+ id: `${pluginId2}.form.emoji`,
459
+ defaultMessage: "Choose Icon"
460
+ }) }),
461
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
462
+ /* @__PURE__ */ jsxRuntime.jsx(
463
+ designSystem.Box,
464
+ {
465
+ as: "button",
466
+ padding: 3,
467
+ borderRadius: "4px",
468
+ border: "1px solid #e0e0e0",
469
+ fontSize: "32px",
470
+ onClick: () => setShowEmojiPicker(!showEmojiPicker),
471
+ type: "button",
472
+ style: { cursor: "pointer" },
473
+ children: emoji
474
+ }
475
+ ),
476
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage({
477
+ id: `${pluginId2}.form.selectEmoji`,
478
+ defaultMessage: "Click to select"
479
+ }) })
480
+ ] }),
481
+ showEmojiPicker && /* @__PURE__ */ jsxRuntime.jsx(EmojiPicker, { children: BOOKMARK_EMOJIS.map((e) => /* @__PURE__ */ jsxRuntime.jsx(
482
+ EmojiButton,
483
+ {
484
+ isSelected: emoji === e,
485
+ onClick: () => {
486
+ setEmoji(e);
487
+ setShowEmojiPicker(false);
488
+ },
489
+ type: "button",
490
+ children: e
491
+ },
492
+ e
493
+ )) })
494
+ ] }),
495
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
496
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, children: formatMessage({
497
+ id: `${pluginId2}.form.sharing`,
498
+ defaultMessage: "Sharing Options"
499
+ }) }),
500
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 3, children: /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", cursor: "pointer" }, children: [
501
+ /* @__PURE__ */ jsxRuntime.jsx(
502
+ "input",
503
+ {
504
+ type: "checkbox",
505
+ checked: isPublic,
506
+ onChange: (e) => setIsPublic(e.target.checked),
507
+ style: { marginRight: "8px" }
508
+ }
509
+ ),
510
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "14px" }, children: formatMessage({
511
+ id: `${pluginId2}.form.publicAccess`,
512
+ defaultMessage: "Public (All admin users can see this bookmark)"
513
+ }) })
514
+ ] }) }),
515
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
516
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 2, children: [
517
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage({
518
+ id: `${pluginId2}.form.shareWithRoles`,
519
+ defaultMessage: "Share with specific roles:"
520
+ }) }),
521
+ sharedWithRoles.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", fontWeight: "semiBold", textColor: "primary600", children: [
522
+ sharedWithRoles.length,
523
+ " selected"
524
+ ] })
525
+ ] }),
526
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: {
527
+ maxHeight: "150px",
528
+ overflowY: "auto",
529
+ border: "1px solid #e0e0e0",
530
+ borderRadius: "4px",
531
+ padding: "12px",
532
+ background: isPublic ? "#f9f9f9" : "#fff"
533
+ }, children: loadingRoles ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Loading roles..." }) : availableRoles.length > 0 ? availableRoles.map((role) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 2, children: /* @__PURE__ */ jsxRuntime.jsxs("label", { style: {
534
+ display: "flex",
535
+ alignItems: "center",
536
+ cursor: isPublic ? "not-allowed" : "pointer",
537
+ padding: "6px 8px",
538
+ borderRadius: "4px",
539
+ background: sharedWithRoles.includes(role.id) ? "#eaf5ff" : "transparent",
540
+ border: sharedWithRoles.includes(role.id) ? "1px solid #3945C9" : "1px solid transparent"
541
+ }, children: [
542
+ /* @__PURE__ */ jsxRuntime.jsx(
543
+ "input",
544
+ {
545
+ type: "checkbox",
546
+ checked: sharedWithRoles.includes(role.id),
547
+ onChange: (e) => {
548
+ if (e.target.checked) {
549
+ setSharedWithRoles((prev) => {
550
+ const newRoles = [...prev, role.id];
551
+ return newRoles;
552
+ });
553
+ } else {
554
+ setSharedWithRoles((prev) => {
555
+ const newRoles = prev.filter((id) => id !== role.id);
556
+ return newRoles;
557
+ });
558
+ }
559
+ },
560
+ style: { marginRight: "8px" },
561
+ disabled: isPublic
562
+ }
563
+ ),
564
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: {
565
+ fontSize: "14px",
566
+ fontWeight: sharedWithRoles.includes(role.id) ? 600 : 400,
567
+ color: isPublic ? "#999" : sharedWithRoles.includes(role.id) ? "#3945C9" : "#32324d"
568
+ }, children: [
569
+ role.name || role.code || `Role ${role.id}`,
570
+ role.isCustom && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "11px", marginLeft: "4px", color: "#8C4BFF" }, children: "(Custom)" }),
571
+ role.userCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: "11px", marginLeft: "4px", color: "#666" }, children: [
572
+ "(",
573
+ role.userCount,
574
+ " user",
575
+ role.userCount > 1 ? "s" : "",
576
+ ")"
577
+ ] })
578
+ ] })
579
+ ] }) }, role.id)) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "No roles available. Check console for errors." }) }),
580
+ isPublic && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", style: { marginTop: "4px", fontSize: "0.75rem" }, children: "Role selection disabled when bookmark is public" })
581
+ ] }),
582
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 3, children: [
583
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 2, children: [
584
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage({
585
+ id: `${pluginId2}.form.shareWithUsers`,
586
+ defaultMessage: "Share with specific users:"
587
+ }) }),
588
+ sharedWithUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", fontWeight: "semiBold", textColor: "primary600", children: [
589
+ sharedWithUsers.length,
590
+ " selected"
591
+ ] })
592
+ ] }),
593
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: {
594
+ maxHeight: "150px",
595
+ overflowY: "auto",
596
+ border: "1px solid #e0e0e0",
597
+ borderRadius: "4px",
598
+ padding: "12px",
599
+ background: isPublic ? "#f9f9f9" : "#fff"
600
+ }, children: loadingUsers ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Loading users..." }) : availableUsers.length > 0 ? availableUsers.map((user) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 2, children: /* @__PURE__ */ jsxRuntime.jsxs("label", { style: {
601
+ display: "flex",
602
+ alignItems: "center",
603
+ cursor: isPublic ? "not-allowed" : "pointer",
604
+ padding: "6px 8px",
605
+ borderRadius: "4px",
606
+ background: sharedWithUsers.includes(user.id) ? "#eaf5ff" : "transparent",
607
+ border: sharedWithUsers.includes(user.id) ? "1px solid #3945C9" : "1px solid transparent"
608
+ }, children: [
609
+ /* @__PURE__ */ jsxRuntime.jsx(
610
+ "input",
611
+ {
612
+ type: "checkbox",
613
+ checked: sharedWithUsers.includes(user.id),
614
+ onChange: (e) => {
615
+ if (e.target.checked) {
616
+ setSharedWithUsers((prev) => {
617
+ const newUsers = [...prev, user.id];
618
+ return newUsers;
619
+ });
620
+ } else {
621
+ setSharedWithUsers((prev) => {
622
+ const newUsers = prev.filter((id) => id !== user.id);
623
+ return newUsers;
624
+ });
625
+ }
626
+ },
627
+ style: { marginRight: "8px" },
628
+ disabled: isPublic
629
+ }
630
+ ),
631
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: {
632
+ fontSize: "14px",
633
+ fontWeight: sharedWithUsers.includes(user.id) ? 600 : 400,
634
+ color: isPublic ? "#999" : sharedWithUsers.includes(user.id) ? "#3945C9" : "#32324d"
635
+ }, children: [
636
+ user.firstname || "",
637
+ " ",
638
+ user.lastname || "",
639
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: "12px", color: "#666", marginLeft: "4px" }, children: [
640
+ "(",
641
+ user.email || user.username || "No email",
642
+ ")"
643
+ ] })
644
+ ] })
645
+ ] }) }, user.id)) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "No other users available" }) }),
646
+ isPublic && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", style: { marginTop: "4px", fontSize: "0.75rem" }, children: "User selection disabled when bookmark is public" })
647
+ ] })
648
+ ] }),
649
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
650
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, as: "label", htmlFor: "name", children: [
651
+ formatMessage({
652
+ id: `${pluginId2}.form.name`,
653
+ defaultMessage: "Bookmark Name"
654
+ }),
655
+ " *"
656
+ ] }),
657
+ /* @__PURE__ */ jsxRuntime.jsx(
658
+ designSystem.TextInput,
659
+ {
660
+ id: "name",
661
+ type: "text",
662
+ placeholder: "e.g., Published Articles",
663
+ value: name2,
664
+ onChange: (e) => setName(e.target.value)
665
+ }
666
+ )
667
+ ] }),
668
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
669
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, as: "label", htmlFor: "path", children: formatMessage({
670
+ id: `${pluginId2}.form.path`,
671
+ defaultMessage: "Content Manager Path"
672
+ }) }),
673
+ /* @__PURE__ */ jsxRuntime.jsx(
674
+ designSystem.TextInput,
675
+ {
676
+ id: "path",
677
+ type: "text",
678
+ placeholder: "/content-manager/collection-types/api::article.article",
679
+ value: path,
680
+ readOnly: true,
681
+ disabled: true
682
+ }
683
+ ),
684
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", style: { marginTop: "4px", display: "block" }, children: formatMessage({
685
+ id: `${pluginId2}.form.pathHelp`,
686
+ defaultMessage: "Automatically captured from Content Manager"
687
+ }) })
688
+ ] }),
689
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
690
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, children: formatMessage({
691
+ id: `${pluginId2}.form.filterPreview`,
692
+ defaultMessage: "Captured Filters & Settings"
693
+ }) }),
694
+ /* @__PURE__ */ jsxRuntime.jsx(FilterPreview, { query }),
695
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", style: { marginTop: "8px", display: "block" }, children: [
696
+ "💡 ",
697
+ formatMessage({
698
+ id: `${pluginId2}.form.queryHelp`,
699
+ defaultMessage: "These filters will be restored when you click this bookmark"
700
+ })
701
+ ] })
702
+ ] }),
703
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
704
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, as: "label", htmlFor: "description", children: formatMessage({
705
+ id: `${pluginId2}.form.description`,
706
+ defaultMessage: "Description (Optional)"
707
+ }) }),
708
+ /* @__PURE__ */ jsxRuntime.jsx(
709
+ designSystem.Textarea,
710
+ {
711
+ id: "description",
712
+ placeholder: "Add a description...",
713
+ value: description,
714
+ onChange: (e) => setDescription(e.target.value)
715
+ }
716
+ )
717
+ ] })
718
+ ]
719
+ }
720
+ ) }),
721
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "flex-end", gap: 2, children: [
722
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: onClose, variant: "tertiary", type: "button", children: formatMessage({
723
+ id: `${pluginId2}.button.cancel`,
724
+ defaultMessage: "Cancel"
725
+ }) }),
726
+ /* @__PURE__ */ jsxRuntime.jsx(
727
+ designSystem.Button,
728
+ {
729
+ onClick: handleSubmit,
730
+ loading: isSubmitting,
731
+ type: "button",
732
+ children: isEditing ? formatMessage({
733
+ id: `${pluginId2}.button.update`,
734
+ defaultMessage: "Update"
735
+ }) : formatMessage({
736
+ id: `${pluginId2}.button.save`,
737
+ defaultMessage: "Save Bookmark"
738
+ })
739
+ }
740
+ )
741
+ ] })
742
+ ]
743
+ }
744
+ )
745
+ }
746
+ );
747
+ };
748
+ const ViewsWidget = () => {
749
+ const { formatMessage } = reactIntl.useIntl();
750
+ const navigate = reactRouterDom.useNavigate();
751
+ const { get } = admin.useFetchClient();
752
+ const viewsButtonRef = React.useRef(null);
753
+ const [showCreateModal, setShowCreateModal] = React__default.default.useState(false);
754
+ const [viewsPopoverVisible, setViewsPopoverVisible] = React__default.default.useState(false);
755
+ const [bookmarks, setBookmarks] = React__default.default.useState([]);
756
+ const [isLoadingViews, setIsLoadingViews] = React__default.default.useState(false);
757
+ const [currentPath, setCurrentPath] = React__default.default.useState("");
758
+ const [currentQuery, setCurrentQuery] = React__default.default.useState("");
759
+ const getBookmarks = async () => {
760
+ setIsLoadingViews(true);
761
+ const url = `/magic-mark/bookmarks`;
762
+ try {
763
+ const { data } = await get(url);
764
+ setBookmarks(data.data || []);
765
+ } catch (error) {
766
+ console.error("[Magic-Mark] Error fetching bookmarks:", error);
767
+ setBookmarks([]);
768
+ } finally {
769
+ setIsLoadingViews(false);
770
+ }
771
+ };
772
+ const handleOpenModal = () => {
773
+ let path = window.location.pathname;
774
+ if (path.startsWith("/admin/")) {
775
+ path = path.substring(6);
776
+ }
777
+ const query = window.location.search.substring(1);
778
+ console.log("[ViewsWidget] Captured path:", path);
779
+ console.log("[ViewsWidget] Captured query:", query);
780
+ setCurrentPath(path);
781
+ setCurrentQuery(query);
782
+ setShowCreateModal(true);
783
+ };
784
+ React__default.default.useEffect(() => {
785
+ getBookmarks();
786
+ }, [showCreateModal]);
787
+ const handleBookmarkClick = (bookmark) => {
788
+ if (bookmark.path && bookmark.query) {
789
+ navigate(`${bookmark.path}?${bookmark.query}`);
790
+ } else if (bookmark.path) {
791
+ navigate(bookmark.path);
792
+ }
793
+ setViewsPopoverVisible(false);
794
+ };
795
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, marginRight: 1, children: [
796
+ /* @__PURE__ */ jsxRuntime.jsx(
797
+ designSystem.Button,
798
+ {
799
+ variant: "tertiary",
800
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
801
+ onClick: handleOpenModal,
802
+ children: formatMessage({
803
+ id: `${pluginId}.ViewsWidget.actions.create`,
804
+ defaultMessage: "Save Bookmark"
805
+ })
806
+ }
807
+ ),
808
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
809
+ /* @__PURE__ */ jsxRuntime.jsx(
810
+ designSystem.Button,
811
+ {
812
+ ref: viewsButtonRef,
813
+ variant: "tertiary",
814
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.List, {}),
815
+ onClick: () => setViewsPopoverVisible((s) => !s),
816
+ children: formatMessage({
817
+ id: `${pluginId}.ViewsWidget.actions.showList`,
818
+ defaultMessage: "Magicmark"
819
+ })
820
+ }
821
+ ),
822
+ viewsPopoverVisible && viewsButtonRef.current && /* @__PURE__ */ jsxRuntime.jsx(
823
+ ViewsListPopover,
824
+ {
825
+ views: bookmarks,
826
+ onViewClick: handleBookmarkClick,
827
+ isLoading: isLoadingViews,
828
+ buttonElement: viewsButtonRef.current
829
+ }
830
+ )
831
+ ] }),
832
+ showCreateModal && /* @__PURE__ */ jsxRuntime.jsx(
833
+ CreateEditModal,
834
+ {
835
+ bookmark: null,
836
+ pluginId,
837
+ onClose: () => setShowCreateModal(false),
838
+ onSuccess: () => {
839
+ setShowCreateModal(false);
840
+ getBookmarks();
841
+ },
842
+ currentPath,
843
+ currentQuery
844
+ }
845
+ )
846
+ ] });
847
+ };
848
+ const SelectContainer = styled__default.default.div`
849
+ position: relative;
850
+ width: 100%;
851
+ `;
852
+ const SelectButton = styled__default.default.button`
853
+ width: 100%;
854
+ padding: 10px 36px 10px 12px;
855
+ border: 1px solid ${(props) => props.isOpen ? "#4945ff" : "#dcdce4"};
856
+ border-radius: 4px;
857
+ background: white;
858
+ font-size: 14px;
859
+ text-align: left;
860
+ cursor: pointer;
861
+ display: flex;
862
+ align-items: center;
863
+ justify-content: space-between;
864
+ transition: all 0.2s ease;
865
+
866
+ &:hover {
867
+ border-color: #4945ff;
868
+ }
869
+
870
+ &:focus {
871
+ outline: none;
872
+ border-color: #4945ff;
873
+ box-shadow: 0 0 0 2px rgba(73, 69, 255, 0.1);
874
+ }
875
+
876
+ @media (max-width: 768px) {
877
+ padding: 8px 32px 8px 10px;
878
+ font-size: 13px;
879
+ }
880
+ `;
881
+ const SelectText = styled__default.default.span`
882
+ color: ${(props) => props.$isPlaceholder ? "#8e8ea9" : "#212134"};
883
+ overflow: hidden;
884
+ text-overflow: ellipsis;
885
+ white-space: nowrap;
886
+ `;
887
+ const IconWrapper = styled__default.default.span`
888
+ position: absolute;
889
+ right: 12px;
890
+ top: 50%;
891
+ transform: translateY(-50%) ${(props) => props.$isOpen ? "rotate(180deg)" : "rotate(0deg)"};
892
+ transition: transform 0.2s ease;
893
+ display: flex;
894
+ align-items: center;
895
+ pointer-events: none;
896
+ color: #8e8ea9;
897
+ `;
898
+ const DropdownList = styled__default.default.div`
899
+ position: fixed;
900
+ top: ${(props) => props.top}px;
901
+ left: ${(props) => props.left}px;
902
+ width: ${(props) => props.width}px;
903
+ max-height: 300px;
904
+ overflow-y: auto;
905
+ background: white;
906
+ border: 1px solid #dcdce4;
907
+ border-radius: 4px;
908
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
909
+ z-index: 10000;
910
+ display: ${(props) => props.isOpen ? "block" : "none"};
911
+
912
+ @media (max-width: 768px) {
913
+ max-height: 250px;
914
+ }
915
+ `;
916
+ const DropdownItem = styled__default.default.button`
917
+ width: 100%;
918
+ padding: 10px 12px;
919
+ border: none;
920
+ background: ${(props) => props.$isSelected ? "#f0f0ff" : "white"};
921
+ text-align: left;
922
+ cursor: pointer;
923
+ font-size: 14px;
924
+ color: ${(props) => props.$isSelected ? "#4945ff" : "#212134"};
925
+ transition: background 0.15s ease;
926
+
927
+ &:hover {
928
+ background: #f7f8fa;
929
+ }
930
+
931
+ &:focus {
932
+ outline: none;
933
+ background: #f0f0ff;
934
+ }
935
+
936
+ @media (max-width: 768px) {
937
+ padding: 12px;
938
+ font-size: 13px;
939
+ }
940
+ `;
941
+ const SearchInput = styled__default.default.input`
942
+ width: 100%;
943
+ padding: 10px 12px;
944
+ border: none;
945
+ border-bottom: 1px solid #dcdce4;
946
+ font-size: 14px;
947
+
948
+ &:focus {
949
+ outline: none;
950
+ border-bottom-color: #4945ff;
951
+ }
952
+
953
+ @media (max-width: 768px) {
954
+ padding: 12px;
955
+ font-size: 13px;
956
+ }
957
+ `;
958
+ const CustomSelect = ({
959
+ value,
960
+ onChange,
961
+ options,
962
+ placeholder = "Select...",
963
+ searchable = true
964
+ }) => {
965
+ const [isOpen, setIsOpen] = React.useState(false);
966
+ const [searchTerm, setSearchTerm] = React.useState("");
967
+ const [dropdownPos, setDropdownPos] = React.useState({ top: 0, left: 0, width: 0 });
968
+ const containerRef = React.useRef(null);
969
+ const buttonRef = React.useRef(null);
970
+ const searchInputRef = React.useRef(null);
971
+ const updatePosition = () => {
972
+ if (buttonRef.current) {
973
+ const rect = buttonRef.current.getBoundingClientRect();
974
+ setDropdownPos({
975
+ top: rect.bottom + 4,
976
+ left: rect.left,
977
+ width: rect.width
978
+ });
979
+ }
980
+ };
981
+ React.useEffect(() => {
982
+ if (isOpen) {
983
+ updatePosition();
984
+ window.addEventListener("scroll", updatePosition, true);
985
+ window.addEventListener("resize", updatePosition);
986
+ return () => {
987
+ window.removeEventListener("scroll", updatePosition, true);
988
+ window.removeEventListener("resize", updatePosition);
989
+ };
990
+ }
991
+ }, [isOpen]);
992
+ React.useEffect(() => {
993
+ const handleClickOutside = (event) => {
994
+ const target = event.target;
995
+ if (containerRef.current && !containerRef.current.contains(target) && !target.closest("[data-dropdown-portal]")) {
996
+ setIsOpen(false);
997
+ setSearchTerm("");
998
+ }
999
+ };
1000
+ if (isOpen) {
1001
+ document.addEventListener("mousedown", handleClickOutside);
1002
+ return () => document.removeEventListener("mousedown", handleClickOutside);
1003
+ }
1004
+ }, [isOpen]);
1005
+ React.useEffect(() => {
1006
+ if (isOpen && searchable && searchInputRef.current) {
1007
+ searchInputRef.current.focus();
1008
+ }
1009
+ }, [isOpen, searchable]);
1010
+ const filteredOptions = searchable && searchTerm ? options.filter(
1011
+ (opt) => opt.label.toLowerCase().includes(searchTerm.toLowerCase())
1012
+ ) : options;
1013
+ const selectedOption = options.find((opt) => opt.value === value);
1014
+ const handleSelect = (optionValue) => {
1015
+ console.log("[CustomSelect] Selected:", optionValue);
1016
+ onChange(optionValue);
1017
+ setIsOpen(false);
1018
+ setSearchTerm("");
1019
+ };
1020
+ const dropdownContent = /* @__PURE__ */ jsxRuntime.jsxs(
1021
+ DropdownList,
1022
+ {
1023
+ isOpen,
1024
+ top: dropdownPos.top,
1025
+ left: dropdownPos.left,
1026
+ width: dropdownPos.width,
1027
+ "data-dropdown-portal": "true",
1028
+ children: [
1029
+ searchable && /* @__PURE__ */ jsxRuntime.jsx(
1030
+ SearchInput,
1031
+ {
1032
+ ref: searchInputRef,
1033
+ type: "text",
1034
+ placeholder: "Search...",
1035
+ value: searchTerm,
1036
+ onChange: (e) => setSearchTerm(e.target.value),
1037
+ onClick: (e) => e.stopPropagation()
1038
+ }
1039
+ ),
1040
+ filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 3, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "No options found" }) }) : filteredOptions.map((option) => /* @__PURE__ */ jsxRuntime.jsx(
1041
+ DropdownItem,
1042
+ {
1043
+ type: "button",
1044
+ $isSelected: option.value === value,
1045
+ onClick: () => handleSelect(option.value),
1046
+ children: option.label
1047
+ },
1048
+ option.value
1049
+ ))
1050
+ ]
1051
+ }
1052
+ );
1053
+ return /* @__PURE__ */ jsxRuntime.jsxs(SelectContainer, { ref: containerRef, children: [
1054
+ /* @__PURE__ */ jsxRuntime.jsxs(
1055
+ SelectButton,
1056
+ {
1057
+ ref: buttonRef,
1058
+ type: "button",
1059
+ onClick: () => setIsOpen(!isOpen),
1060
+ isOpen,
1061
+ children: [
1062
+ /* @__PURE__ */ jsxRuntime.jsx(SelectText, { $isPlaceholder: !value, children: selectedOption?.label || placeholder }),
1063
+ /* @__PURE__ */ jsxRuntime.jsx(IconWrapper, { $isOpen: isOpen, children: "▼" })
1064
+ ]
1065
+ }
1066
+ ),
1067
+ isOpen && ReactDOM__default.default.createPortal(dropdownContent, document.body)
1068
+ ] });
1069
+ };
1070
+ const GroupContainer = styled__default.default.div`
1071
+ border: 2px solid ${(props) => props.level === 0 ? "#4945ff" : "#dcdce4"};
1072
+ border-radius: 8px;
1073
+ padding: 16px;
1074
+ margin: 8px 0;
1075
+ background: ${(props) => props.level === 0 ? "#f0f0ff" : "#fafafa"};
1076
+
1077
+ @media (max-width: 768px) {
1078
+ padding: 12px;
1079
+ margin: 6px 0;
1080
+ }
1081
+ `;
1082
+ const ConditionRow = styled__default.default(designSystem.Flex)`
1083
+ padding: 12px;
1084
+ background: white;
1085
+ border: 1px solid #e0e0e0;
1086
+ border-radius: 6px;
1087
+ margin: 8px 0;
1088
+ align-items: center;
1089
+ gap: 12px;
1090
+ flex-wrap: wrap;
1091
+
1092
+ @media (max-width: 768px) {
1093
+ padding: 10px;
1094
+ gap: 8px;
1095
+ }
1096
+
1097
+ @media (max-width: 480px) {
1098
+ flex-direction: column;
1099
+ align-items: stretch;
1100
+
1101
+ > * {
1102
+ width: 100% !important;
1103
+ flex: none !important;
1104
+ }
1105
+ }
1106
+ `;
1107
+ const LogicBadge = styled__default.default.div`
1108
+ padding: 4px 12px;
1109
+ border-radius: 4px;
1110
+ background: ${(props) => props.logic === "OR" ? "#FFF4E6" : "#E8F4FD"};
1111
+ color: ${(props) => props.logic === "OR" ? "#D97706" : "#0369A1"};
1112
+ font-weight: bold;
1113
+ font-size: 11px;
1114
+ text-align: center;
1115
+ min-width: 40px;
1116
+ `;
1117
+ styled__default.default.select`
1118
+ padding: 8px 12px;
1119
+ border: 1px solid #dcdce4;
1120
+ border-radius: 4px;
1121
+ background: white;
1122
+ font-size: 14px;
1123
+ cursor: pointer;
1124
+ flex: 1;
1125
+ min-width: 100px;
1126
+
1127
+ &:focus {
1128
+ outline: none;
1129
+ border-color: #4945ff;
1130
+ }
1131
+
1132
+ @media (max-width: 768px) {
1133
+ font-size: 13px;
1134
+ padding: 6px 8px;
1135
+ min-width: 80px;
1136
+ }
1137
+ `;
1138
+ const StyledInput = styled__default.default.input`
1139
+ padding: 8px 12px;
1140
+ border: 1px solid #dcdce4;
1141
+ border-radius: 4px;
1142
+ font-size: 14px;
1143
+ flex: 2;
1144
+ min-width: 120px;
1145
+
1146
+ &:focus {
1147
+ outline: none;
1148
+ border-color: #4945ff;
1149
+ }
1150
+
1151
+ @media (max-width: 768px) {
1152
+ font-size: 13px;
1153
+ padding: 6px 8px;
1154
+ min-width: 100px;
1155
+ }
1156
+ `;
1157
+ const OPERATORS = [
1158
+ { value: "eq", label: "=" },
1159
+ { value: "ne", label: "≠" },
1160
+ { value: "lt", label: "<" },
1161
+ { value: "lte", label: "≤" },
1162
+ { value: "gt", label: ">" },
1163
+ { value: "gte", label: "≥" },
1164
+ { value: "contains", label: "contains" },
1165
+ { value: "notContains", label: "not contains" }
1166
+ ];
1167
+ const QueryBuilder = ({
1168
+ availableFields,
1169
+ onQueryChange,
1170
+ initialStructure
1171
+ }) => {
1172
+ const [rootGroup, setRootGroup] = React.useState(
1173
+ initialStructure || {
1174
+ id: "root",
1175
+ logic: "AND",
1176
+ conditions: [
1177
+ { id: "1", field: "", operator: "eq", value: "" }
1178
+ ]
1179
+ }
1180
+ );
1181
+ React__default.default.useEffect(() => {
1182
+ if (initialStructure) {
1183
+ console.log("[QueryBuilder] Loading initial structure:", initialStructure);
1184
+ setRootGroup(initialStructure);
1185
+ }
1186
+ }, [initialStructure]);
1187
+ const updateRootGroup = (newGroup) => {
1188
+ setRootGroup(newGroup);
1189
+ onQueryChange(newGroup);
1190
+ };
1191
+ const addCondition = (groupId) => {
1192
+ const newGroup = JSON.parse(JSON.stringify(rootGroup));
1193
+ const group = findGroup(newGroup, groupId);
1194
+ if (group) {
1195
+ group.conditions.push({
1196
+ id: Date.now().toString(),
1197
+ field: "",
1198
+ operator: "eq",
1199
+ value: ""
1200
+ });
1201
+ updateRootGroup(newGroup);
1202
+ }
1203
+ };
1204
+ const addGroup = (parentGroupId) => {
1205
+ const newGroup = JSON.parse(JSON.stringify(rootGroup));
1206
+ const parentGroup = findGroup(newGroup, parentGroupId);
1207
+ if (parentGroup) {
1208
+ parentGroup.conditions.push({
1209
+ id: Date.now().toString(),
1210
+ logic: "AND",
1211
+ conditions: [{ id: Date.now().toString() + "_1", field: "", operator: "eq", value: "" }],
1212
+ isGroup: true
1213
+ });
1214
+ updateRootGroup(newGroup);
1215
+ }
1216
+ };
1217
+ const removeItem = (groupId, itemId) => {
1218
+ const newGroup = JSON.parse(JSON.stringify(rootGroup));
1219
+ const group = findGroup(newGroup, groupId);
1220
+ if (group) {
1221
+ group.conditions = group.conditions.filter((c) => c.id !== itemId);
1222
+ updateRootGroup(newGroup);
1223
+ }
1224
+ };
1225
+ const updateCondition = (conditionId, key, value) => {
1226
+ const newGroup = JSON.parse(JSON.stringify(rootGroup));
1227
+ const condition = findCondition(newGroup, conditionId);
1228
+ if (condition) {
1229
+ condition[key] = value;
1230
+ updateRootGroup(newGroup);
1231
+ }
1232
+ };
1233
+ const toggleGroupLogic = (groupId) => {
1234
+ const newGroup = JSON.parse(JSON.stringify(rootGroup));
1235
+ const group = findGroup(newGroup, groupId);
1236
+ if (group) {
1237
+ group.logic = group.logic === "AND" ? "OR" : "AND";
1238
+ updateRootGroup(newGroup);
1239
+ }
1240
+ };
1241
+ const findGroup = (group, id) => {
1242
+ if (group.id === id) return group;
1243
+ for (const item of group.conditions) {
1244
+ if (item.isGroup) {
1245
+ const found = findGroup(item, id);
1246
+ if (found) return found;
1247
+ }
1248
+ }
1249
+ return null;
1250
+ };
1251
+ const findCondition = (group, id) => {
1252
+ for (const item of group.conditions) {
1253
+ if (!item.isGroup && item.id === id) {
1254
+ return item;
1255
+ }
1256
+ if (item.isGroup) {
1257
+ const found = findCondition(item, id);
1258
+ if (found) return found;
1259
+ }
1260
+ }
1261
+ return null;
1262
+ };
1263
+ const renderGroup = (group, level = 0) => {
1264
+ return /* @__PURE__ */ jsxRuntime.jsxs(GroupContainer, { level, children: [
1265
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, marginBottom: 3, alignItems: "center", children: [
1266
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: level === 0 ? "🔍 Root Group" : "📁 Sub-Group" }),
1267
+ /* @__PURE__ */ jsxRuntime.jsx(
1268
+ designSystem.Button,
1269
+ {
1270
+ variant: group.logic === "AND" ? "default" : "secondary",
1271
+ size: "S",
1272
+ onClick: () => toggleGroupLogic(group.id),
1273
+ children: group.logic
1274
+ }
1275
+ ),
1276
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: group.logic === "AND" ? "All must match" : "Any can match" })
1277
+ ] }),
1278
+ group.conditions.map((item, index2) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
1279
+ index2 > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", marginBottom: 2, children: /* @__PURE__ */ jsxRuntime.jsx(LogicBadge, { logic: group.logic, children: group.logic }) }),
1280
+ item.isGroup ? (
1281
+ // Render nested group
1282
+ renderGroup(item, level + 1)
1283
+ ) : (
1284
+ // Render condition
1285
+ /* @__PURE__ */ jsxRuntime.jsxs(ConditionRow, { children: [
1286
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 2, minWidth: "150px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1287
+ CustomSelect,
1288
+ {
1289
+ value: item.field,
1290
+ onChange: (val) => updateCondition(item.id, "field", val),
1291
+ options: [
1292
+ { value: "", label: "Select field" },
1293
+ ...availableFields.map((f) => ({ value: f.name, label: f.name }))
1294
+ ],
1295
+ placeholder: "Select field",
1296
+ searchable: true
1297
+ }
1298
+ ) }),
1299
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: "100px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1300
+ CustomSelect,
1301
+ {
1302
+ value: item.operator,
1303
+ onChange: (val) => updateCondition(item.id, "operator", val),
1304
+ options: OPERATORS,
1305
+ searchable: false
1306
+ }
1307
+ ) }),
1308
+ /* @__PURE__ */ jsxRuntime.jsx(
1309
+ StyledInput,
1310
+ {
1311
+ value: item.value,
1312
+ onChange: (e) => updateCondition(item.id, "value", e.target.value),
1313
+ placeholder: "Enter value"
1314
+ }
1315
+ ),
1316
+ /* @__PURE__ */ jsxRuntime.jsx(
1317
+ designSystem.Button,
1318
+ {
1319
+ variant: "danger-light",
1320
+ size: "S",
1321
+ onClick: () => removeItem(group.id, item.id),
1322
+ disabled: group.conditions.length === 1 && level === 0,
1323
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1324
+ }
1325
+ )
1326
+ ] })
1327
+ )
1328
+ ] }, item.id)),
1329
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, marginTop: 3, children: [
1330
+ /* @__PURE__ */ jsxRuntime.jsx(
1331
+ designSystem.Button,
1332
+ {
1333
+ variant: "secondary",
1334
+ size: "S",
1335
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
1336
+ onClick: () => addCondition(group.id),
1337
+ children: "Add Condition"
1338
+ }
1339
+ ),
1340
+ level < 2 && /* @__PURE__ */ jsxRuntime.jsx(
1341
+ designSystem.Button,
1342
+ {
1343
+ variant: "tertiary",
1344
+ size: "S",
1345
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
1346
+ onClick: () => addGroup(group.id),
1347
+ children: "Add Group"
1348
+ }
1349
+ )
1350
+ ] })
1351
+ ] }, group.id);
1352
+ };
1353
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: renderGroup(rootGroup) });
1354
+ };
1355
+ const parseQueryToStructure = (queryString) => {
1356
+ const params = new URLSearchParams(queryString);
1357
+ const filterTree = {};
1358
+ params.forEach((value, key) => {
1359
+ if (key.startsWith("filters[")) {
1360
+ const parts = key.match(/\[([^\]]+)\]/g)?.map((p) => p.slice(1, -1)) || [];
1361
+ if (parts.length >= 4) {
1362
+ const logic = parts[0].substring(1);
1363
+ const index2 = parseInt(parts[1]);
1364
+ if (parts[2].startsWith("$")) {
1365
+ const nestedLogic = parts[2].substring(1);
1366
+ const nestedIndex = parseInt(parts[3]);
1367
+ const field = parts[4];
1368
+ const operator = parts[5].substring(1);
1369
+ if (!filterTree[logic]) filterTree[logic] = {};
1370
+ if (!filterTree[logic][index2]) filterTree[logic][index2] = { nested: nestedLogic, items: {} };
1371
+ filterTree[logic][index2].items[nestedIndex] = { field, operator, value };
1372
+ } else {
1373
+ const field = parts[2];
1374
+ const operator = parts[3].substring(1);
1375
+ if (!filterTree[logic]) filterTree[logic] = {};
1376
+ filterTree[logic][index2] = { field, operator, value };
1377
+ }
1378
+ }
1379
+ }
1380
+ });
1381
+ console.log("[QueryParser] Filter tree:", filterTree);
1382
+ const rootLogic = Object.keys(filterTree)[0] || "and";
1383
+ const rootGroup = {
1384
+ id: "root",
1385
+ logic: rootLogic.toUpperCase(),
1386
+ conditions: []
1387
+ };
1388
+ let conditionId = 1;
1389
+ const rootItems = filterTree[rootLogic] || {};
1390
+ Object.keys(rootItems).sort((a, b) => parseInt(a) - parseInt(b)).forEach((index2) => {
1391
+ const item = rootItems[index2];
1392
+ if (item.nested) {
1393
+ const subGroup = {
1394
+ id: `group_${conditionId++}`,
1395
+ logic: item.nested.toUpperCase(),
1396
+ conditions: [],
1397
+ isGroup: true
1398
+ };
1399
+ Object.keys(item.items).sort((a, b) => parseInt(a) - parseInt(b)).forEach((subIndex) => {
1400
+ const subItem = item.items[subIndex];
1401
+ subGroup.conditions.push({
1402
+ id: `condition_${conditionId++}`,
1403
+ field: subItem.field,
1404
+ operator: subItem.operator,
1405
+ value: decodeURIComponent(subItem.value)
1406
+ });
1407
+ });
1408
+ rootGroup.conditions.push(subGroup);
1409
+ } else {
1410
+ rootGroup.conditions.push({
1411
+ id: `condition_${conditionId++}`,
1412
+ field: item.field,
1413
+ operator: item.operator,
1414
+ value: decodeURIComponent(item.value)
1415
+ });
1416
+ }
1417
+ });
1418
+ if (rootGroup.conditions.length === 0) {
1419
+ rootGroup.conditions.push({
1420
+ id: "condition_1",
1421
+ field: "",
1422
+ operator: "eq",
1423
+ value: ""
1424
+ });
1425
+ }
1426
+ console.log("[QueryParser] Built structure:", rootGroup);
1427
+ return rootGroup;
1428
+ };
1429
+ const structureToFilters = (group) => {
1430
+ const logic = group.logic.toLowerCase();
1431
+ const conditions = group.conditions.filter((item) => {
1432
+ if (item.isGroup) return true;
1433
+ const cond = item;
1434
+ return cond.field && cond.value;
1435
+ }).map((item) => {
1436
+ if (item.isGroup) {
1437
+ return structureToFilters(item);
1438
+ } else {
1439
+ const cond = item;
1440
+ return {
1441
+ [cond.field]: {
1442
+ [`$${cond.operator}`]: cond.value
1443
+ }
1444
+ };
1445
+ }
1446
+ });
1447
+ if (conditions.length === 1 && !(conditions[0].$and || conditions[0].$or)) {
1448
+ return conditions[0];
1449
+ }
1450
+ return {
1451
+ [`$${logic}`]: conditions
1452
+ };
1453
+ };
1454
+ const generateQueryString = (structure, sortField, sortOrder, populateFields) => {
1455
+ const queryObject = {};
1456
+ if (structure && structure.conditions.length > 0) {
1457
+ const hasValidConditions = structure.conditions.some((item) => {
1458
+ if (item.isGroup) return true;
1459
+ const cond = item;
1460
+ return cond.field && cond.value;
1461
+ });
1462
+ if (hasValidConditions) {
1463
+ queryObject.filters = structureToFilters(structure);
1464
+ }
1465
+ }
1466
+ if (populateFields && populateFields.length > 0) {
1467
+ const populate = {};
1468
+ populateFields.forEach((p) => {
1469
+ if (p.enabled) {
1470
+ if (p.deep) {
1471
+ populate[p.name] = {
1472
+ populate: "*"
1473
+ };
1474
+ } else {
1475
+ populate[p.name] = true;
1476
+ }
1477
+ }
1478
+ });
1479
+ if (Object.keys(populate).length > 0) {
1480
+ queryObject.populate = populate;
1481
+ }
1482
+ }
1483
+ console.log("[QueryGenerator] Query object:", queryObject);
1484
+ let queryString = qs__default.default.stringify(queryObject, {
1485
+ encodeValuesOnly: true
1486
+ // Pretty URLs
1487
+ });
1488
+ if (sortField) {
1489
+ const sortParam = `sort=${sortField}:${sortOrder || "ASC"}`;
1490
+ queryString = queryString ? `${queryString}&${sortParam}` : sortParam;
1491
+ }
1492
+ console.log("[QueryGenerator] Generated query string:", queryString);
1493
+ return queryString;
1494
+ };
1495
+ const ModalOverlay = styled__default.default.div`
1496
+ position: fixed;
1497
+ top: 0;
1498
+ left: 0;
1499
+ right: 0;
1500
+ bottom: 0;
1501
+ background-color: rgba(0, 0, 0, 0.5);
1502
+ display: flex;
1503
+ align-items: center;
1504
+ justify-content: center;
1505
+ z-index: 999;
1506
+ padding: 20px;
1507
+
1508
+ @media (max-width: 768px) {
1509
+ padding: 10px;
1510
+ }
1511
+ `;
1512
+ const ModalContent = styled__default.default(designSystem.Box)`
1513
+ background: white;
1514
+ border-radius: 8px;
1515
+ max-height: 90vh;
1516
+ overflow-y: auto;
1517
+ max-width: 900px;
1518
+ width: 100%;
1519
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
1520
+
1521
+ @media (max-width: 768px) {
1522
+ max-width: 100%;
1523
+ max-height: 95vh;
1524
+ border-radius: 12px;
1525
+ }
1526
+
1527
+ @media (min-width: 769px) {
1528
+ width: 85%;
1529
+ }
1530
+ `;
1531
+ const SimpleAdvancedFilterModal = ({
1532
+ onClose,
1533
+ onApply,
1534
+ availableFields,
1535
+ availableRelations = [],
1536
+ currentQuery = ""
1537
+ }) => {
1538
+ const [queryStructure, setQueryStructure] = React.useState(null);
1539
+ const [initialStructure, setInitialStructure] = React.useState(null);
1540
+ const [initialFiltersLoaded, setInitialFiltersLoaded] = React.useState(false);
1541
+ const [sortField, setSortField] = React.useState("");
1542
+ const [sortOrder, setSortOrder] = React.useState("ASC");
1543
+ const [populateFields, setPopulateFields] = React.useState(
1544
+ availableRelations.map((rel) => ({
1545
+ name: rel.name,
1546
+ enabled: false,
1547
+ deep: false
1548
+ }))
1549
+ );
1550
+ React__default.default.useEffect(() => {
1551
+ if (!initialFiltersLoaded) {
1552
+ console.log("[SimpleAdvancedFilter] Loading current query:", currentQuery);
1553
+ if (currentQuery) {
1554
+ const parsed = parseQueryToStructure(currentQuery);
1555
+ setInitialStructure(parsed);
1556
+ setQueryStructure(parsed);
1557
+ const params = new URLSearchParams(currentQuery);
1558
+ const updatedPopulate = [...populateFields];
1559
+ params.forEach((value, key) => {
1560
+ if (key.startsWith("populate[")) {
1561
+ const match = key.match(/populate\[([^\]]+)\]/);
1562
+ if (match) {
1563
+ const fieldName = match[1];
1564
+ const index2 = updatedPopulate.findIndex((p) => p.name === fieldName);
1565
+ if (index2 >= 0) {
1566
+ const isDeep = key.includes("[populate]") || value === "*";
1567
+ updatedPopulate[index2] = {
1568
+ ...updatedPopulate[index2],
1569
+ enabled: true,
1570
+ deep: isDeep
1571
+ };
1572
+ }
1573
+ }
1574
+ } else if (key === "sort") {
1575
+ const parts = value.split(":");
1576
+ if (parts.length === 2) {
1577
+ setSortField(parts[0]);
1578
+ setSortOrder(parts[1].toUpperCase());
1579
+ }
1580
+ }
1581
+ });
1582
+ setPopulateFields(updatedPopulate);
1583
+ }
1584
+ setInitialFiltersLoaded(true);
1585
+ console.log("[SimpleAdvancedFilter] Initial structure loaded");
1586
+ }
1587
+ }, [currentQuery]);
1588
+ const togglePopulate = (name2) => {
1589
+ setPopulateFields(populateFields.map(
1590
+ (p) => p.name === name2 ? { ...p, enabled: !p.enabled } : p
1591
+ ));
1592
+ };
1593
+ const toggleDeepPopulate = (name2) => {
1594
+ setPopulateFields(populateFields.map(
1595
+ (p) => p.name === name2 ? { ...p, deep: !p.deep, enabled: true } : p
1596
+ ));
1597
+ };
1598
+ const handleApply = () => {
1599
+ if (!queryStructure) {
1600
+ console.warn("[SimpleAdvancedFilter] No query structure defined");
1601
+ return;
1602
+ }
1603
+ const queryString = generateQueryString(
1604
+ queryStructure,
1605
+ sortField,
1606
+ sortOrder,
1607
+ populateFields
1608
+ );
1609
+ console.log("[SimpleAdvancedFilter] Applying query:", queryString);
1610
+ onApply(queryString);
1611
+ onClose();
1612
+ };
1613
+ return /* @__PURE__ */ jsxRuntime.jsx(ModalOverlay, { onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsxs(ModalContent, { padding: 6, onClick: (e) => e.stopPropagation(), children: [
1614
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 4, children: [
1615
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { as: "h2", variant: "beta", children: "🔍 Advanced Filters" }),
1616
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: onClose, variant: "ghost", type: "button", children: /* @__PURE__ */ jsxRuntime.jsx(icons.Cross, {}) })
1617
+ ] }),
1618
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", marginBottom: 4, children: "Build complex queries by combining AND/OR groups. Each group can contain conditions or nested groups." }),
1619
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
1620
+ QueryBuilder,
1621
+ {
1622
+ availableFields,
1623
+ onQueryChange: setQueryStructure,
1624
+ initialStructure
1625
+ }
1626
+ ) }),
1627
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 4, padding: 3, background: "warning100", borderRadius: "4px", children: [
1628
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "12px", display: "block" }, children: "📊 Sorting:" }),
1629
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", style: { flexWrap: "wrap" }, children: [
1630
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: 2, minWidth: "200px" }, children: [
1631
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", style: { marginBottom: "4px", display: "block" }, children: "Sort by field:" }),
1632
+ /* @__PURE__ */ jsxRuntime.jsxs(
1633
+ "select",
1634
+ {
1635
+ value: sortField,
1636
+ onChange: (e) => setSortField(e.target.value),
1637
+ style: {
1638
+ width: "100%",
1639
+ padding: "8px 12px",
1640
+ border: "1px solid #dcdce4",
1641
+ borderRadius: "4px",
1642
+ background: "white",
1643
+ fontSize: "14px",
1644
+ cursor: "pointer"
1645
+ },
1646
+ children: [
1647
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "No sorting" }),
1648
+ availableFields.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f.name, children: f.name }, f.name))
1649
+ ]
1650
+ }
1651
+ )
1652
+ ] }),
1653
+ sortField && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: 1, minWidth: "120px" }, children: [
1654
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", style: { marginBottom: "4px", display: "block" }, children: "Order:" }),
1655
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
1656
+ /* @__PURE__ */ jsxRuntime.jsx(
1657
+ designSystem.Button,
1658
+ {
1659
+ variant: sortOrder === "ASC" ? "default" : "secondary",
1660
+ size: "S",
1661
+ onClick: () => setSortOrder("ASC"),
1662
+ children: "↑ ASC"
1663
+ }
1664
+ ),
1665
+ /* @__PURE__ */ jsxRuntime.jsx(
1666
+ designSystem.Button,
1667
+ {
1668
+ variant: sortOrder === "DESC" ? "default" : "secondary",
1669
+ size: "S",
1670
+ onClick: () => setSortOrder("DESC"),
1671
+ children: "↓ DESC"
1672
+ }
1673
+ )
1674
+ ] })
1675
+ ] })
1676
+ ] }),
1677
+ sortField && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "warning700", style: { marginTop: "8px", display: "block", fontSize: "12px" }, children: [
1678
+ "📊 Results will be sorted by ",
1679
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: sortField }),
1680
+ " in ",
1681
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: sortOrder }),
1682
+ " order"
1683
+ ] })
1684
+ ] }),
1685
+ availableRelations.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 4, padding: 3, background: "neutral100", borderRadius: "4px", children: [
1686
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "12px", display: "block" }, children: "🔗 Populate Relations:" }),
1687
+ populateFields.map((field) => /* @__PURE__ */ jsxRuntime.jsxs(
1688
+ designSystem.Flex,
1689
+ {
1690
+ gap: 2,
1691
+ alignItems: "center",
1692
+ marginBottom: 2,
1693
+ style: { flexWrap: "wrap" },
1694
+ children: [
1695
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: "120px" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: field.name }) }),
1696
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
1697
+ /* @__PURE__ */ jsxRuntime.jsx(
1698
+ designSystem.Button,
1699
+ {
1700
+ variant: field.enabled && !field.deep ? "default" : "secondary",
1701
+ size: "S",
1702
+ onClick: () => togglePopulate(field.name),
1703
+ children: field.enabled && !field.deep ? "✓ On" : "Enable"
1704
+ }
1705
+ ),
1706
+ /* @__PURE__ */ jsxRuntime.jsx(
1707
+ designSystem.Button,
1708
+ {
1709
+ variant: field.deep ? "default" : "tertiary",
1710
+ size: "S",
1711
+ onClick: () => toggleDeepPopulate(field.name),
1712
+ children: field.deep ? "🌲" : "Deep"
1713
+ }
1714
+ )
1715
+ ] })
1716
+ ]
1717
+ },
1718
+ field.name
1719
+ )),
1720
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", style: { marginTop: "12px", display: "block", fontSize: "12px" }, children: [
1721
+ "💡 ",
1722
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Deep populate" }),
1723
+ " loads all nested relations recursively"
1724
+ ] })
1725
+ ] }),
1726
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 4, padding: 3, background: "neutral100", borderRadius: "4px", children: [
1727
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, children: "Generated Query:" }),
1728
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontFamily: "monospace", style: { wordBreak: "break-all", fontSize: "11px" }, children: queryStructure ? generateQueryString(queryStructure, sortField, sortOrder, populateFields) : "No filters defined" })
1729
+ ] }),
1730
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "flex-end", gap: 2, children: [
1731
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: onClose, variant: "tertiary", children: "Cancel" }),
1732
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleApply, variant: "default", children: "Apply Filters" })
1733
+ ] })
1734
+ ] }) });
1735
+ };
1736
+ const AdvancedFilterButton = () => {
1737
+ const [showModal, setShowModal] = React.useState(false);
1738
+ const navigate = reactRouterDom.useNavigate();
1739
+ const location = reactRouterDom.useLocation();
1740
+ const { get } = admin.useFetchClient();
1741
+ const [availableFields, setAvailableFields] = React.useState([]);
1742
+ const [availableRelations, setAvailableRelations] = React.useState([]);
1743
+ const [hasActiveFilters, setHasActiveFilters] = React.useState(false);
1744
+ React.useEffect(() => {
1745
+ const params = new URLSearchParams(location.search);
1746
+ let hasFilters = false;
1747
+ params.forEach((value, key) => {
1748
+ if (key.startsWith("filters[")) {
1749
+ hasFilters = true;
1750
+ }
1751
+ });
1752
+ setHasActiveFilters(hasFilters);
1753
+ }, [location.search]);
1754
+ const extractContentTypeUid = () => {
1755
+ const match = location.pathname.match(/collection-types\/([^/?]+)/);
1756
+ return match ? match[1] : null;
1757
+ };
1758
+ React.useEffect(() => {
1759
+ const fetchSchema = async () => {
1760
+ const uid = extractContentTypeUid();
1761
+ if (!uid) {
1762
+ console.log("[AdvancedFilter] No content type UID found in URL");
1763
+ return;
1764
+ }
1765
+ try {
1766
+ console.log("[AdvancedFilter] Fetching schema for:", uid);
1767
+ let schemaData;
1768
+ try {
1769
+ const response = await get(`/content-manager/content-types/${uid}/configuration`);
1770
+ schemaData = response.data;
1771
+ console.log("[AdvancedFilter] Configuration response:", schemaData);
1772
+ } catch (e) {
1773
+ console.log("[AdvancedFilter] Configuration endpoint failed, trying direct strapi schema");
1774
+ }
1775
+ const contentTypeData = schemaData?.data?.contentType || schemaData?.contentType || {};
1776
+ const metadatas = contentTypeData.metadatas || {};
1777
+ const layouts = contentTypeData.layouts || {};
1778
+ console.log("[AdvancedFilter] Metadatas:", metadatas);
1779
+ console.log("[AdvancedFilter] Layouts:", layouts);
1780
+ let allFieldNames = /* @__PURE__ */ new Set();
1781
+ Object.keys(metadatas).forEach((key) => allFieldNames.add(key));
1782
+ if (layouts.edit) {
1783
+ layouts.edit.forEach((row) => {
1784
+ row.forEach((field) => {
1785
+ if (field.name) allFieldNames.add(field.name);
1786
+ });
1787
+ });
1788
+ }
1789
+ if (layouts.list) {
1790
+ layouts.list.forEach((fieldName) => allFieldNames.add(fieldName));
1791
+ }
1792
+ const attributes = {};
1793
+ allFieldNames.forEach((name2) => {
1794
+ const metadata = metadatas[name2];
1795
+ attributes[name2] = {
1796
+ type: metadata?.edit?.type || "string"
1797
+ };
1798
+ });
1799
+ console.log("[AdvancedFilter] Reconstructed attributes:", attributes);
1800
+ if (attributes && Object.keys(attributes).length > 0) {
1801
+ const fields = [];
1802
+ const relations = [];
1803
+ const fieldNames = /* @__PURE__ */ new Set();
1804
+ Object.keys(attributes).forEach((key) => {
1805
+ const attr = attributes[key];
1806
+ if (attr.type === "relation") {
1807
+ relations.push({ name: key });
1808
+ } else if (!fieldNames.has(key)) {
1809
+ fieldNames.add(key);
1810
+ fields.push({
1811
+ name: key,
1812
+ type: attr.type || "string"
1813
+ });
1814
+ }
1815
+ });
1816
+ ["id", "createdAt", "updatedAt"].forEach((defaultField) => {
1817
+ if (!fieldNames.has(defaultField)) {
1818
+ fields.unshift({
1819
+ name: defaultField,
1820
+ type: defaultField === "id" ? "integer" : "datetime"
1821
+ });
1822
+ }
1823
+ });
1824
+ setAvailableFields(fields);
1825
+ setAvailableRelations(relations);
1826
+ console.log("[AdvancedFilter] Extracted fields:", fields);
1827
+ console.log("[AdvancedFilter] Extracted relations:", relations);
1828
+ } else {
1829
+ console.warn("[AdvancedFilter] No attributes found in schema response");
1830
+ setAvailableFields([
1831
+ { name: "id", type: "integer" },
1832
+ { name: "createdAt", type: "datetime" },
1833
+ { name: "updatedAt", type: "datetime" }
1834
+ ]);
1835
+ }
1836
+ } catch (error) {
1837
+ console.error("[AdvancedFilter] Error fetching schema:", error);
1838
+ setAvailableFields([
1839
+ { name: "id", type: "integer" },
1840
+ { name: "createdAt", type: "datetime" },
1841
+ { name: "updatedAt", type: "datetime" }
1842
+ ]);
1843
+ }
1844
+ };
1845
+ fetchSchema();
1846
+ }, [location.pathname]);
1847
+ const handleApplyFilters = (queryString) => {
1848
+ console.log("[AdvancedFilter] Applying filters:", queryString);
1849
+ const currentPath = location.pathname;
1850
+ const currentParams = new URLSearchParams(location.search);
1851
+ const newParams = new URLSearchParams(queryString);
1852
+ const cleanParams = new URLSearchParams();
1853
+ currentParams.forEach((value, key) => {
1854
+ if (!key.startsWith("filters[") && !key.startsWith("populate[")) {
1855
+ cleanParams.set(key, value);
1856
+ }
1857
+ });
1858
+ newParams.forEach((value, key) => {
1859
+ cleanParams.set(key, value);
1860
+ });
1861
+ navigate(`${currentPath}?${cleanParams.toString()}`);
1862
+ };
1863
+ const handleClearFilters = () => {
1864
+ const currentPath = location.pathname;
1865
+ const currentParams = new URLSearchParams(location.search);
1866
+ const cleanParams = new URLSearchParams();
1867
+ currentParams.forEach((value, key) => {
1868
+ if (!key.startsWith("filters[") && !key.startsWith("populate[")) {
1869
+ cleanParams.set(key, value);
1870
+ }
1871
+ });
1872
+ navigate(`${currentPath}${cleanParams.toString() ? "?" + cleanParams.toString() : ""}`);
1873
+ };
1874
+ const getCurrentFilters = () => {
1875
+ const params = new URLSearchParams(location.search);
1876
+ const currentQuery = params.toString();
1877
+ console.log("[AdvancedFilter] Current query from URL:", currentQuery);
1878
+ return currentQuery;
1879
+ };
1880
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1881
+ /* @__PURE__ */ jsxRuntime.jsx(
1882
+ designSystem.Button,
1883
+ {
1884
+ variant: hasActiveFilters ? "default" : "secondary",
1885
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Filter, {}),
1886
+ onClick: () => setShowModal(true),
1887
+ size: "S",
1888
+ children: hasActiveFilters ? "🔍 Filters Active" : "Advanced Filters"
1889
+ }
1890
+ ),
1891
+ hasActiveFilters && /* @__PURE__ */ jsxRuntime.jsx(
1892
+ designSystem.Button,
1893
+ {
1894
+ variant: "danger-light",
1895
+ onClick: handleClearFilters,
1896
+ size: "S",
1897
+ children: "Clear All"
1898
+ }
1899
+ ),
1900
+ showModal && /* @__PURE__ */ jsxRuntime.jsx(
1901
+ SimpleAdvancedFilterModal,
1902
+ {
1903
+ onClose: () => setShowModal(false),
1904
+ onApply: handleApplyFilters,
1905
+ availableFields,
1906
+ availableRelations,
1907
+ currentQuery: getCurrentFilters()
1908
+ }
1909
+ )
1910
+ ] });
1911
+ };
1912
+ const name = pluginPkg.strapi.name;
1913
+ const prefixPluginTranslations = (data, pluginId2) => {
1914
+ const prefixed = {};
1915
+ Object.keys(data).forEach((key) => {
1916
+ prefixed[`${pluginId2}.${key}`] = data[key];
1917
+ });
1918
+ return prefixed;
1919
+ };
1920
+ const index = {
1921
+ register(app) {
1922
+ console.log(`[${pluginId}] Registering plugin...`);
1923
+ app.addMenuLink({
1924
+ to: `/plugins/${pluginId}`,
1925
+ icon: PluginIcon,
1926
+ intlLabel: {
1927
+ id: `${pluginId}.Admin.MainMenu.PluginName`,
1928
+ defaultMessage: name
1929
+ },
1930
+ Component: () => Promise.resolve().then(() => require("./App-CMSut1pt.js")),
1931
+ permissions: []
1932
+ });
1933
+ app.createSettingSection(
1934
+ {
1935
+ id: pluginId,
1936
+ intlLabel: {
1937
+ id: `${pluginId}.settings.title`,
1938
+ defaultMessage: "MagicMark"
1939
+ }
1940
+ },
1941
+ [
1942
+ {
1943
+ intlLabel: {
1944
+ id: `${pluginId}.settings.license`,
1945
+ defaultMessage: "License"
1946
+ },
1947
+ id: "magic-mark-license",
1948
+ to: `${pluginId}/license`,
1949
+ Component: () => Promise.resolve().then(() => require(
1950
+ /* webpackChunkName: "magic-mark-license" */
1951
+ "./index-B11QBtag.js"
1952
+ )),
1953
+ permissions: []
1954
+ }
1955
+ ]
1956
+ );
1957
+ app.registerPlugin({
1958
+ id: pluginId,
1959
+ initializer: Initializer,
1960
+ isReady: true,
1961
+ name
1962
+ });
1963
+ },
1964
+ async bootstrap(app) {
1965
+ console.log(`[${pluginId}] Bootstrapping plugin...`);
1966
+ try {
1967
+ const contentManagerPlugin = app.getPlugin("content-manager");
1968
+ if (contentManagerPlugin) {
1969
+ console.log(`[${pluginId}] Injecting content manager components...`);
1970
+ contentManagerPlugin.injectComponent("listView", "actions", {
1971
+ name: "magic-mark-advanced-filter",
1972
+ Component: AdvancedFilterButton
1973
+ });
1974
+ contentManagerPlugin.injectComponent("listView", "actions", {
1975
+ name: "magic-mark-views-widget",
1976
+ Component: ViewsWidget
1977
+ });
1978
+ console.log(`[${pluginId}] Successfully injected content manager components`);
1979
+ }
1980
+ } catch (error) {
1981
+ console.error(`[${pluginId}] Error injecting content manager components:`, error);
1982
+ }
1983
+ const env = typeof process === "undefined" ? {} : process.env;
1984
+ if (env.STRAPI_ADMIN_FAVORITE_VIEWS_INJECT_TO) {
1985
+ for (const entry of env.STRAPI_ADMIN_FAVORITE_VIEWS_INJECT_TO.split(",")) {
1986
+ const parts = entry.split("::");
1987
+ if (parts.length < 2) continue;
1988
+ const [, pluginAndPath] = parts;
1989
+ const [pluginName, container, block] = pluginAndPath.split(".");
1990
+ const plugin = app.getPlugin(pluginName);
1991
+ if (!plugin) continue;
1992
+ try {
1993
+ plugin.injectComponent(container, block, {
1994
+ name: "favorite-views-widget",
1995
+ Component: ViewsWidget
1996
+ });
1997
+ } catch (error) {
1998
+ console.error(`[${pluginId}] Error injecting component to ${pluginName}:`, error);
1999
+ }
2000
+ }
2001
+ }
2002
+ },
2003
+ async registerTrads({ locales }) {
2004
+ const importedTrads = await Promise.all(
2005
+ locales.map((locale) => {
2006
+ return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/de.json": () => Promise.resolve().then(() => require("./de-Dic_hhjg.js")), "./translations/en.json": () => Promise.resolve().then(() => require("./en-C5BvHqNo.js")), "./translations/es.json": () => Promise.resolve().then(() => require("./es-BlSQpU1z.js")), "./translations/fr.json": () => Promise.resolve().then(() => require("./fr-BHciYPYG.js")), "./translations/pt.json": () => Promise.resolve().then(() => require("./pt-Dawo5aUA.js")) }), `./translations/${locale}.json`, 3).then(({ default: data }) => {
2007
+ return {
2008
+ data: prefixPluginTranslations(data, pluginId),
2009
+ locale
2010
+ };
2011
+ }).catch(() => {
2012
+ return {
2013
+ data: {},
2014
+ locale
2015
+ };
2016
+ });
2017
+ })
2018
+ );
2019
+ return Promise.resolve(importedTrads);
2020
+ }
2021
+ };
2022
+ exports.CreateEditModal = CreateEditModal;
2023
+ exports.index = index;
2024
+ exports.pluginId = pluginId;