@primershop/strapi-plugin-status-manager 0.0.16 → 0.0.17

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.
@@ -1,410 +0,0 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { Box, Typography, Flex, TextInput, Button, Dialog, SingleSelect, SingleSelectOption } from '@strapi/design-system';
3
- import { useFetchClient, Layouts, Page } from '@strapi/strapi/admin';
4
- import { useState, useEffect, useCallback } from 'react';
5
- import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
6
- import { draggable, dropTargetForElements, monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
7
- import { pointerOutsideOfPreview } from '@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview';
8
- import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
9
- import { reorder } from '@atlaskit/pragmatic-drag-and-drop/reorder';
10
- import { extractClosestEdge, attachClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
11
- import { Plus, Drag, Trash } from '@strapi/icons';
12
- import { p as pluginPermissions } from './index-BVybZ6IA.mjs';
13
-
14
- const StatusManager = ()=>{
15
- const [statuses, setStatuses] = useState([]);
16
- const [newStatus, setNewStatus] = useState("");
17
- const [statusToDelete, setStatusToDelete] = useState(null);
18
- const [replacementStatus, setReplacementStatus] = useState("");
19
- const { get, post, put } = useFetchClient();
20
- const [instanceId] = useState(()=>Symbol("instance-id"));
21
- // Fetch statuses
22
- useEffect(()=>{
23
- const loadStatuses = async ()=>{
24
- const { data } = await get("primershop-status-manager/statuses");
25
- setStatuses(data);
26
- };
27
- loadStatuses();
28
- }, [
29
- get
30
- ]);
31
- // Validate input (Latin characters only)
32
- const validateInput = (value)=>/^[a-zA-Z\s]+$/.test(value);
33
- // Add new status
34
- const addStatus = async ()=>{
35
- if (!newStatus || !validateInput(newStatus)) return alert("Only Latin characters allowed!");
36
- try {
37
- const { data } = await post("primershop-status-manager/status", {
38
- name: newStatus,
39
- published: false
40
- });
41
- setStatuses([
42
- ...statuses,
43
- data
44
- ]);
45
- setNewStatus("");
46
- } catch (error) {
47
- console.error("Error creating status:", error);
48
- }
49
- };
50
- const reorderItem = useCallback(async ({ startIndex, indexOfTarget, closestEdgeOfTarget })=>{
51
- // Calculate the final index based on the target position and edge
52
- let finishIndex = indexOfTarget;
53
- if (closestEdgeOfTarget === "bottom") {
54
- finishIndex = indexOfTarget + 1;
55
- }
56
- // If moving an item down, we need to adjust for the removed item
57
- if (startIndex < finishIndex) {
58
- finishIndex--;
59
- }
60
- if (finishIndex === startIndex) {
61
- return;
62
- }
63
- const reordered = reorder({
64
- list: statuses,
65
- startIndex,
66
- finishIndex
67
- });
68
- // Send new order to API
69
- const orderedIds = reordered.map((status, index)=>({
70
- documentId: status.documentId,
71
- order: index
72
- }));
73
- await put("/primershop-status-manager/statuses/reorder", {
74
- statuses: orderedIds
75
- });
76
- setStatuses(reordered);
77
- }, [
78
- statuses,
79
- put
80
- ]);
81
- // Setup drag and drop
82
- useEffect(()=>{
83
- const statusElements = document.querySelectorAll("[data-status-id]");
84
- const cleanupFunctions = [];
85
- statusElements.forEach((element)=>{
86
- const statusId = element.getAttribute("data-status-id");
87
- const index = statuses.findIndex((s)=>s.documentId === statusId);
88
- const dragHandle = element.querySelector("[data-drag-handle]");
89
- if (!dragHandle) return;
90
- // Setup draggable
91
- const draggableCleanup = draggable({
92
- element: dragHandle,
93
- getInitialData: ()=>({
94
- statusId,
95
- index,
96
- instanceId
97
- }),
98
- onGenerateDragPreview ({ nativeSetDragImage }) {
99
- setCustomNativeDragPreview({
100
- nativeSetDragImage,
101
- getOffset: pointerOutsideOfPreview({
102
- x: "16px",
103
- y: "8px"
104
- }),
105
- render ({ container }) {
106
- const preview = document.createElement("div");
107
- preview.style.padding = "8px 16px";
108
- preview.style.backgroundColor = "#fff";
109
- preview.style.border = "1px solid #ccc";
110
- preview.style.borderRadius = "4px";
111
- preview.style.boxShadow = "0 2px 4px rgba(0,0,0,0.1)";
112
- const statusNameElement = element.querySelector("[data-status-name]");
113
- preview.textContent = statusNameElement?.textContent || "";
114
- container.appendChild(preview);
115
- return ()=>container.removeChild(preview);
116
- }
117
- });
118
- }
119
- });
120
- // Setup drop target
121
- const dropTargetCleanup = dropTargetForElements({
122
- element: element,
123
- canDrop: ({ source })=>source.data.instanceId === instanceId,
124
- getData ({ input }) {
125
- return attachClosestEdge({
126
- statusId,
127
- index,
128
- instanceId
129
- }, {
130
- element,
131
- input,
132
- allowedEdges: [
133
- "top",
134
- "bottom"
135
- ]
136
- });
137
- },
138
- onDrag ({ source, self }) {
139
- const isSource = source.element === dragHandle;
140
- if (isSource) return;
141
- const closestEdge = extractClosestEdge(self.data);
142
- const sourceIndex = Number(source.data.index);
143
- const isItemBeforeSource = index === sourceIndex - 1;
144
- const isItemAfterSource = index === sourceIndex + 1;
145
- const isDropIndicatorHidden = isItemBeforeSource && closestEdge === "bottom" || isItemAfterSource && closestEdge === "top";
146
- if (isDropIndicatorHidden) return;
147
- // Add visual feedback for drop target
148
- element.style.background = `linear-gradient(${closestEdge === "top" ? 180 : 0}deg, rgba(136,131,214,0.4) 0%, rgba(255,255,255,0) 50%)`;
149
- },
150
- onDragLeave () {
151
- element.style.background = "";
152
- },
153
- onDrop ({ source, self }) {
154
- element.style.background = "";
155
- const sourceData = source.data;
156
- const targetData = self.data;
157
- const indexOfTarget = statuses.findIndex((s)=>s.documentId === targetData.statusId);
158
- if (indexOfTarget < 0) return;
159
- const closestEdgeOfTarget = extractClosestEdge(targetData);
160
- reorderItem({
161
- startIndex: sourceData.index,
162
- indexOfTarget,
163
- closestEdgeOfTarget
164
- });
165
- }
166
- });
167
- // Combine cleanup functions
168
- const combinedCleanup = combine(draggableCleanup, dropTargetCleanup);
169
- cleanupFunctions.push(combinedCleanup);
170
- });
171
- // Monitor for drops
172
- const monitorCleanup = monitorForElements({
173
- canMonitor: ({ source })=>source.data.instanceId === instanceId,
174
- onDrop ({ location, source }) {
175
- const target = location.current.dropTargets[0];
176
- if (!target) return;
177
- const sourceData = source.data;
178
- const targetData = target.data;
179
- const indexOfTarget = statuses.findIndex((s)=>s.documentId === targetData.statusId);
180
- if (indexOfTarget < 0) return;
181
- const closestEdgeOfTarget = extractClosestEdge(targetData);
182
- reorderItem({
183
- startIndex: sourceData.index,
184
- indexOfTarget,
185
- closestEdgeOfTarget
186
- });
187
- }
188
- });
189
- // Cleanup function
190
- return ()=>{
191
- cleanupFunctions.forEach((cleanup)=>cleanup());
192
- monitorCleanup();
193
- };
194
- }, [
195
- statuses,
196
- reorderItem,
197
- instanceId
198
- ]);
199
- // Open delete dialog
200
- const confirmDelete = (status)=>{
201
- setStatusToDelete(status);
202
- };
203
- // Delete status and replace with selected one
204
- const deleteStatus = async ()=>{
205
- if (!replacementStatus) return alert("Select a replacement status!");
206
- const replacementStatusObj = statuses.find((s)=>s.name === replacementStatus);
207
- if (!replacementStatusObj) return alert("Replacement status not found!");
208
- try {
209
- await put("/primershop-status-manager/statuses/delete", {
210
- statusId: statusToDelete?.documentId,
211
- replacementId: replacementStatusObj.documentId
212
- });
213
- // Remove the deleted status from the list
214
- setStatuses(statuses.filter((s)=>s.documentId !== statusToDelete?.documentId));
215
- setStatusToDelete(null);
216
- setReplacementStatus("");
217
- } catch (error) {
218
- console.error("Error deleting status:", error);
219
- }
220
- };
221
- // Toggle publish status
222
- const togglePublish = async (id, published)=>{
223
- try {
224
- await put(`/primershop-status-manager/statuses/${id}`, {
225
- published: !published
226
- });
227
- setStatuses(statuses.map((s)=>s.documentId === id ? {
228
- ...s,
229
- published: !published
230
- } : s));
231
- } catch (error) {
232
- console.error("Error toggling publish status:", error);
233
- }
234
- };
235
- return /*#__PURE__*/ jsxs(Box, {
236
- padding: 4,
237
- children: [
238
- /*#__PURE__*/ jsx(Typography, {
239
- variant: "beta",
240
- children: "Status Manager"
241
- }),
242
- /*#__PURE__*/ jsxs(Flex, {
243
- marginTop: 4,
244
- gap: 2,
245
- children: [
246
- /*#__PURE__*/ jsx(TextInput, {
247
- placeholder: "Enter a status...",
248
- value: newStatus,
249
- onChange: (e)=>setNewStatus(e.target.value)
250
- }),
251
- /*#__PURE__*/ jsx(Button, {
252
- onClick: addStatus,
253
- startIcon: /*#__PURE__*/ jsx(Plus, {}),
254
- children: "Add Status"
255
- })
256
- ]
257
- }),
258
- /*#__PURE__*/ jsx(Box, {
259
- marginTop: 4,
260
- children: statuses.map((status)=>/*#__PURE__*/ jsxs(Flex, {
261
- "data-status-id": status.documentId,
262
- alignItems: "center",
263
- gap: 2,
264
- marginBottom: 2,
265
- paddingBottom: 2,
266
- style: {
267
- borderBottom: `1px solid gray`,
268
- minWidth: 300,
269
- userSelect: "none",
270
- touchAction: "none"
271
- },
272
- children: [
273
- /*#__PURE__*/ jsx(Box, {
274
- "data-drag-handle": true,
275
- style: {
276
- cursor: "grab",
277
- padding: "4px",
278
- display: "flex",
279
- alignItems: "center"
280
- },
281
- children: /*#__PURE__*/ jsx(Drag, {})
282
- }, `dragHandle-${status.documentId}`),
283
- /*#__PURE__*/ jsx(Typography, {
284
- variant: "sigma",
285
- style: {
286
- display: "inline-block",
287
- marginRight: "auto"
288
- },
289
- "data-status-name": true,
290
- children: status.name
291
- }),
292
- /*#__PURE__*/ jsx(Button, {
293
- variant: status.published ? "success-light" : "secondary",
294
- onClick: ()=>togglePublish(status.documentId, status.published),
295
- children: status.published ? "Published" : "Unpublished"
296
- }),
297
- /*#__PURE__*/ jsxs(Dialog.Root, {
298
- onOpenChange: ()=>confirmDelete(status),
299
- children: [
300
- /*#__PURE__*/ jsx(Dialog.Trigger, {
301
- children: /*#__PURE__*/ jsx(Button, {
302
- variant: "tertiary",
303
- startIcon: /*#__PURE__*/ jsx(Trash, {}),
304
- children: "Delete"
305
- })
306
- }),
307
- /*#__PURE__*/ jsxs(Dialog.Content, {
308
- children: [
309
- /*#__PURE__*/ jsx(Dialog.Header, {
310
- children: "Delete status"
311
- }),
312
- statuses.length > 1 && statusToDelete && /*#__PURE__*/ jsxs(Dialog.Body, {
313
- children: [
314
- /*#__PURE__*/ jsx(Typography, {
315
- children: "Choose a replacement status before deleting:"
316
- }),
317
- /*#__PURE__*/ jsx(SingleSelect, {
318
- onChange: (value)=>setReplacementStatus(value),
319
- placeholder: "Select replacement",
320
- children: statuses.filter((s)=>s.documentId !== statusToDelete.documentId).map((s)=>/*#__PURE__*/ jsx(SingleSelectOption, {
321
- value: s.name,
322
- children: s.name
323
- }, `statusChoice-${s.documentId}`))
324
- }),
325
- replacementStatus && /*#__PURE__*/ jsxs(Typography, {
326
- children: [
327
- "Replacing ",
328
- statusToDelete.name,
329
- " with ",
330
- replacementStatus
331
- ]
332
- })
333
- ]
334
- }),
335
- /*#__PURE__*/ jsxs(Dialog.Footer, {
336
- children: [
337
- /*#__PURE__*/ jsx(Dialog.Cancel, {
338
- children: /*#__PURE__*/ jsx(Button, {
339
- fullWidth: true,
340
- variant: "tertiary",
341
- children: "Cancel"
342
- })
343
- }),
344
- /*#__PURE__*/ jsx(Dialog.Action, {
345
- children: /*#__PURE__*/ jsx(Button, {
346
- fullWidth: true,
347
- variant: "danger-light",
348
- onClick: deleteStatus,
349
- children: "Yes, delete"
350
- })
351
- })
352
- ]
353
- })
354
- ]
355
- })
356
- ]
357
- })
358
- ]
359
- }, `status-${status.documentId}`))
360
- }, statuses.length)
361
- ]
362
- });
363
- };
364
-
365
- const HomePage = ()=>{
366
- return /*#__PURE__*/ jsxs(Layouts.Root, {
367
- children: [
368
- /*#__PURE__*/ jsx(Page.Title, {
369
- children: "Status Manager"
370
- }),
371
- /*#__PURE__*/ jsx(Page.Main, {
372
- children: /*#__PURE__*/ jsx(Page.Protect, {
373
- permissions: pluginPermissions.accessStatusManager,
374
- children: /*#__PURE__*/ jsx(Layouts.Content, {
375
- children: /*#__PURE__*/ jsx(Box, {
376
- children: /*#__PURE__*/ jsx(Flex, {
377
- padding: 10,
378
- gap: {
379
- initial: 1,
380
- medium: 4,
381
- large: 8
382
- },
383
- direction: {
384
- initial: "column",
385
- medium: "row"
386
- },
387
- alignItems: {
388
- initial: "center",
389
- medium: "flex-start"
390
- },
391
- children: /*#__PURE__*/ jsxs(Box, {
392
- padding: 1,
393
- children: [
394
- /*#__PURE__*/ jsx(Typography, {
395
- variant: "alpha",
396
- children: "Status manager"
397
- }),
398
- /*#__PURE__*/ jsx(StatusManager, {})
399
- ]
400
- })
401
- })
402
- })
403
- })
404
- })
405
- })
406
- ]
407
- });
408
- };
409
-
410
- export { HomePage };
@@ -1,280 +0,0 @@
1
- import React, { useRef, useEffect, useState, useCallback } from 'react';
2
- import { jsx, jsxs } from 'react/jsx-runtime';
3
- import { CheckCircle } from '@strapi/icons';
4
- import { Flex, Box, Typography, SingleSelect, SingleSelectOption } from '@strapi/design-system';
5
- import { unstable_useContentManagerContext, useFetchClient, useQueryParams } from '@strapi/strapi/admin';
6
-
7
- const PLUGIN_ID = "primershop-status-manager";
8
-
9
- /**
10
- * @type {import('react').FC<{ setPlugin: (id: string) => void }>}
11
- */ const Initializer = ({ setPlugin })=>{
12
- const ref = useRef(setPlugin);
13
- useEffect(()=>{
14
- ref.current(PLUGIN_ID);
15
- }, []);
16
- return null;
17
- };
18
-
19
- const PluginIcon = ()=>/*#__PURE__*/ jsx(CheckCircle, {});
20
-
21
- const ProductStatusField = ()=>{
22
- const { contentType, id } = unstable_useContentManagerContext();
23
- const [statuses, setStatuses] = useState([]);
24
- const [currentStatus, setCurrentStatus] = useState("");
25
- const [message, setMessage] = useState("");
26
- const { get, put } = useFetchClient();
27
- const handleStatusChange = useCallback(async (newStatus)=>{
28
- if (!id) {
29
- setMessage("Save the product first and then change the status");
30
- return;
31
- }
32
- try {
33
- await put(`primershop-status-manager/content-status`, {
34
- contentTypeUid: "api::product.product",
35
- contentDocumentId: id,
36
- statusId: statuses.find((status)=>status.name === newStatus)?.documentId
37
- });
38
- setMessage(`Status updated ${currentStatus ? `from ${currentStatus}` : ""} to ${newStatus}`);
39
- setCurrentStatus(newStatus || "");
40
- } catch (error) {
41
- setMessage("Error updating status");
42
- console.error("Error updating status:", error);
43
- }
44
- }, [
45
- id,
46
- statuses,
47
- currentStatus,
48
- put
49
- ]);
50
- useEffect(()=>{
51
- async function fetchCurrentStatus() {
52
- try {
53
- const { data: productData } = await get(`primershop-status-manager/content-status?contentDocumentId=${id}&contentTypeUid=api::product.product`);
54
- const status = productData?.status;
55
- if (status && status.name) return setCurrentStatus(status.name);
56
- if (statuses.length) return handleStatusChange(statuses[0].name);
57
- } catch (error) {
58
- console.error("Error fetching product status:", error);
59
- }
60
- }
61
- if (id && !currentStatus.length) fetchCurrentStatus();
62
- if (!id && statuses.length) setCurrentStatus(statuses[0].name);
63
- }, [
64
- id,
65
- statuses,
66
- get
67
- ]);
68
- useEffect(()=>{
69
- async function fetchStatuses() {
70
- try {
71
- const { data } = await get("primershop-status-manager/statuses");
72
- setStatuses(data);
73
- } catch (error) {
74
- console.error("Error fetching statuses:", error);
75
- }
76
- }
77
- fetchStatuses();
78
- }, [
79
- get
80
- ]);
81
- return /*#__PURE__*/ jsxs(Flex, {
82
- direction: "column",
83
- justifyContent: "center",
84
- alignItems: "stretch",
85
- width: "100%",
86
- children: [
87
- /*#__PURE__*/ jsx(Box, {
88
- padding: 2,
89
- children: /*#__PURE__*/ jsxs(Typography, {
90
- variant: "sigma",
91
- children: [
92
- contentType?.info.displayName,
93
- " status"
94
- ]
95
- })
96
- }),
97
- /*#__PURE__*/ jsx(SingleSelect, {
98
- placeholder: currentStatus,
99
- onChange: handleStatusChange,
100
- children: statuses.map((status)=>/*#__PURE__*/ jsx(SingleSelectOption, {
101
- value: status.name,
102
- children: status.name
103
- }, status.documentId))
104
- }),
105
- /*#__PURE__*/ jsx(Box, {
106
- padding: 2,
107
- children: /*#__PURE__*/ jsx(Typography, {
108
- variant: "sigma",
109
- children: message
110
- })
111
- })
112
- ]
113
- });
114
- };
115
-
116
- const StatusCell = ({ row })=>{
117
- const [status, setStatus] = useState(null);
118
- useEffect(()=>{
119
- setStatus(row.statusField);
120
- }, [
121
- row
122
- ]);
123
- if (!status) return null;
124
- return /*#__PURE__*/ jsx("span", {
125
- style: {
126
- padding: "4px 8px",
127
- borderRadius: 4,
128
- background: status.published ? "#eafbe7" : "#f0f0ff",
129
- color: status.published ? "#2f6846" : "#271fe0",
130
- border: `1px solid ${status.published ? "#2f6846" : "#271fe0"}`,
131
- fontSize: 14
132
- },
133
- children: status.name
134
- });
135
- };
136
-
137
- const addStatusColumnHook = ({ displayedHeaders, layout })=>{
138
- const statusHeader = {
139
- attribute: {
140
- type: "custom"
141
- },
142
- name: "statusLabel",
143
- label: {
144
- id: "primershop-status-manager.status",
145
- defaultMessage: "Status"
146
- },
147
- searchable: false,
148
- sortable: false,
149
- cellFormatter: (row)=>{
150
- return React.createElement(StatusCell, {
151
- row
152
- });
153
- }
154
- };
155
- return {
156
- displayedHeaders: [
157
- ...displayedHeaders,
158
- statusHeader
159
- ],
160
- layout
161
- };
162
- };
163
-
164
- /**
165
- * Admin permission actions for use in addMenuLink, Page.Protect, and useRBAC.
166
- * Action IDs must match uids registered in server bootstrap (registerMany).
167
- */ const pluginPermissions = {
168
- accessStatusManager: [
169
- {
170
- action: "plugin::primershop-status-manager.main",
171
- subject: null
172
- }
173
- ]
174
- };
175
-
176
- const StatusFilter = ()=>{
177
- const { contentType } = unstable_useContentManagerContext();
178
- const [statuses, setStatuses] = useState([]);
179
- const [selected, setSelected] = useState("");
180
- const { get } = useFetchClient();
181
- const [{ query }, setQuery] = useQueryParams();
182
- const handleStatusChange = useCallback((name)=>{
183
- setQuery({
184
- page: 1,
185
- plugins: {
186
- ...query.plugins,
187
- "primershop-status-manager": {
188
- statusName: name.toLowerCase()
189
- }
190
- }
191
- }, "push", true);
192
- }, [
193
- query.plugins,
194
- setQuery
195
- ]);
196
- useEffect(()=>{
197
- const selectedStatusName = query.plugins?.["primershop-status-manager"]?.statusName;
198
- if (!selectedStatusName) return;
199
- const status = statuses.find((status)=>status.name.toLowerCase() === selectedStatusName);
200
- if (status) {
201
- setSelected(status.name);
202
- }
203
- }, [
204
- query,
205
- statuses
206
- ]);
207
- useEffect(()=>{
208
- async function fetchStatuses() {
209
- try {
210
- const { data } = await get("primershop-status-manager/statuses");
211
- const allStatusesObject = {
212
- documentId: "all",
213
- name: "All"
214
- };
215
- setStatuses([
216
- allStatusesObject,
217
- ...data
218
- ]);
219
- } catch (error) {
220
- console.error("Error fetching statuses:", error);
221
- }
222
- }
223
- fetchStatuses();
224
- }, [
225
- get
226
- ]);
227
- return /*#__PURE__*/ jsx(Flex, {
228
- direction: "column",
229
- justifyContent: "center",
230
- children: /*#__PURE__*/ jsx(SingleSelect, {
231
- size: "S",
232
- placeholder: `${contentType?.info.displayName} status`,
233
- value: selected,
234
- onChange: handleStatusChange,
235
- children: statuses.map((status)=>/*#__PURE__*/ jsx(SingleSelectOption, {
236
- value: status.name,
237
- children: status.name
238
- }, status.documentId))
239
- })
240
- });
241
- };
242
-
243
- const plugin = {
244
- register (app) {
245
- app.registerPlugin({
246
- id: PLUGIN_ID,
247
- initializer: Initializer,
248
- isReady: true,
249
- name: PLUGIN_ID
250
- });
251
- app.addMenuLink({
252
- to: `plugins/${PLUGIN_ID}`,
253
- icon: PluginIcon,
254
- intlLabel: {
255
- id: `${PLUGIN_ID}.plugin.name`,
256
- defaultMessage: "Status manager"
257
- },
258
- permissions: [
259
- pluginPermissions.accessStatusManager[0]
260
- ],
261
- Component: ()=>import('./HomePage-Dx9N0awm.mjs').then((module)=>({
262
- default: module.HomePage
263
- }))
264
- });
265
- },
266
- bootstrap (app) {
267
- app.getPlugin("content-manager").injectComponent("editView", "right-links", {
268
- name: "Status",
269
- Component: ProductStatusField
270
- });
271
- app.registerHook('Admin/CM/pages/ListView/inject-column-in-table', addStatusColumnHook);
272
- const contentManager = app.getPlugin('content-manager');
273
- contentManager.injectComponent('listView', 'actions', {
274
- name: 'status-filter',
275
- Component: StatusFilter
276
- });
277
- }
278
- };
279
-
280
- export { plugin as a, pluginPermissions as p };