strapi-plugin-bulk-updater 1.3.10 → 1.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/index.js +96 -2
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +96 -2
- package/dist/admin/index.mjs.map +1 -1
- package/dist/server/index.js +389 -29775
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +389 -29765
- package/dist/server/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/admin/index.js
CHANGED
|
@@ -4,6 +4,61 @@ const icons = require("@strapi/icons");
|
|
|
4
4
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
5
5
|
const React__default = /* @__PURE__ */ _interopDefault(React);
|
|
6
6
|
const PLUGIN_ID = "bulk-updater";
|
|
7
|
+
function escapeHtml(str) {
|
|
8
|
+
return (str || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
9
|
+
}
|
|
10
|
+
function showQuotaErrorToast(message) {
|
|
11
|
+
if (typeof document === "undefined") return;
|
|
12
|
+
const existing = document.getElementById("strapi-quota-validation-banner");
|
|
13
|
+
if (existing) existing.remove();
|
|
14
|
+
let title = "Validation Error";
|
|
15
|
+
let detail = message;
|
|
16
|
+
const match = message.match(/^(Validation Error \([^)]+\)):\s*(.+)$/s);
|
|
17
|
+
if (match) {
|
|
18
|
+
title = match[1];
|
|
19
|
+
detail = match[2];
|
|
20
|
+
}
|
|
21
|
+
const banner = document.createElement("div");
|
|
22
|
+
banner.id = "strapi-quota-validation-banner";
|
|
23
|
+
banner.style.position = "fixed";
|
|
24
|
+
banner.style.top = "24px";
|
|
25
|
+
banner.style.left = "50%";
|
|
26
|
+
banner.style.transform = "translateX(-50%)";
|
|
27
|
+
banner.style.zIndex = "999999";
|
|
28
|
+
banner.style.minWidth = "420px";
|
|
29
|
+
banner.style.maxWidth = "720px";
|
|
30
|
+
banner.style.backgroundColor = "#D02B20";
|
|
31
|
+
banner.style.color = "#FFFFFF";
|
|
32
|
+
banner.style.padding = "14px 20px";
|
|
33
|
+
banner.style.borderRadius = "6px";
|
|
34
|
+
banner.style.boxShadow = "0 10px 25px rgba(0,0,0,0.35)";
|
|
35
|
+
banner.style.display = "flex";
|
|
36
|
+
banner.style.alignItems = "flex-start";
|
|
37
|
+
banner.style.gap = "12px";
|
|
38
|
+
banner.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
39
|
+
banner.style.fontSize = "14px";
|
|
40
|
+
banner.style.lineHeight = "1.45";
|
|
41
|
+
banner.style.transition = "opacity 0.2s ease-in-out";
|
|
42
|
+
banner.innerHTML = `
|
|
43
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink: 0; margin-top: 1px;">
|
|
44
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
45
|
+
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
46
|
+
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
47
|
+
</svg>
|
|
48
|
+
<div style="flex: 1;">
|
|
49
|
+
<div style="font-weight: 700; margin-bottom: 2px;">${escapeHtml(title)}</div>
|
|
50
|
+
<div style="opacity: 0.95;">${escapeHtml(detail)}</div>
|
|
51
|
+
</div>
|
|
52
|
+
<button style="background: none; border: none; color: #FFF; cursor: pointer; padding: 0 4px; font-size: 18px; line-height: 1; opacity: 0.8;" onclick="this.parentElement.remove()">×</button>
|
|
53
|
+
`;
|
|
54
|
+
document.body.appendChild(banner);
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
if (banner.parentElement) {
|
|
57
|
+
banner.style.opacity = "0";
|
|
58
|
+
setTimeout(() => banner.remove(), 250);
|
|
59
|
+
}
|
|
60
|
+
}, 1e4);
|
|
61
|
+
}
|
|
7
62
|
const index = {
|
|
8
63
|
register(app) {
|
|
9
64
|
app.addMenuLink({
|
|
@@ -49,6 +104,45 @@ const index = {
|
|
|
49
104
|
});
|
|
50
105
|
},
|
|
51
106
|
bootstrap(app) {
|
|
107
|
+
if (typeof window !== "undefined" && !window.__bulk_updater_interceptor_installed) {
|
|
108
|
+
window.__bulk_updater_interceptor_installed = true;
|
|
109
|
+
if (window.fetch) {
|
|
110
|
+
const origFetch = window.fetch;
|
|
111
|
+
window.fetch = async function(...args) {
|
|
112
|
+
const response = await origFetch.apply(this, args);
|
|
113
|
+
if (response.status === 400 || response.status === 422 || response.status === 500) {
|
|
114
|
+
try {
|
|
115
|
+
const clone = response.clone();
|
|
116
|
+
const data = await clone.json();
|
|
117
|
+
const errMsg = data?.error?.message || data?.message;
|
|
118
|
+
if (typeof errMsg === "string" && (errMsg.startsWith("Validation Error") || errMsg.includes("cannot appear more than"))) {
|
|
119
|
+
showQuotaErrorToast(errMsg);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return response;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (window.XMLHttpRequest) {
|
|
128
|
+
const origOpen = XMLHttpRequest.prototype.open;
|
|
129
|
+
XMLHttpRequest.prototype.open = function(...args) {
|
|
130
|
+
this.addEventListener("load", function() {
|
|
131
|
+
if (this.status === 400 || this.status === 422 || this.status === 500) {
|
|
132
|
+
try {
|
|
133
|
+
const data = JSON.parse(this.responseText);
|
|
134
|
+
const errMsg = data?.error?.message || data?.message;
|
|
135
|
+
if (typeof errMsg === "string" && (errMsg.startsWith("Validation Error") || errMsg.includes("cannot appear more than"))) {
|
|
136
|
+
showQuotaErrorToast(errMsg);
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
return origOpen.apply(this, args);
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
52
146
|
try {
|
|
53
147
|
const contentManagerPlugin = app.getPlugin("content-manager");
|
|
54
148
|
if (contentManagerPlugin?.apis?.addDocumentAction) {
|
|
@@ -58,7 +152,7 @@ const index = {
|
|
|
58
152
|
}
|
|
59
153
|
const hasHistory = actions.some((a) => a.type === "history" || a.id === "content-history");
|
|
60
154
|
if (hasHistory) return actions;
|
|
61
|
-
const customHistoryAction = ({ model, document }) => {
|
|
155
|
+
const customHistoryAction = ({ model, document: document2 }) => {
|
|
62
156
|
if (window.strapi?.features?.isEnabled?.("cms-content-history")) {
|
|
63
157
|
return null;
|
|
64
158
|
}
|
|
@@ -71,7 +165,7 @@ const index = {
|
|
|
71
165
|
window.location.href = `${currentPath}/history${window.location.search}`;
|
|
72
166
|
}
|
|
73
167
|
},
|
|
74
|
-
disabled: !
|
|
168
|
+
disabled: !document2 || !document2.id || !model?.startsWith("api::"),
|
|
75
169
|
position: "header"
|
|
76
170
|
};
|
|
77
171
|
};
|
package/dist/admin/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../admin/src/pluginId.ts","../../admin/src/index.ts"],"sourcesContent":["export const PLUGIN_ID = 'bulk-updater';\n","import React from 'react';\nimport { PLUGIN_ID } from './pluginId';\nimport { Pencil, ClockCounterClockwise } from '@strapi/icons';\n\nexport default {\n register(app: any) {\n app.addMenuLink({\n to: `plugins/${PLUGIN_ID}`,\n icon: Pencil,\n intlLabel: {\n id: `${PLUGIN_ID}.plugin.name`,\n defaultMessage: 'Bulk Attribute Updater',\n },\n Component: () => import('./pages/BulkUpdater'),\n permissions: [],\n position: 3,\n });\n\n app.addSettingsLink('global', {\n id: `${PLUGIN_ID}-settings`,\n intlLabel: {\n id: `${PLUGIN_ID}.settings.name`,\n defaultMessage: 'Bulk Updater Settings',\n },\n to: `${PLUGIN_ID}`,\n Component: () => import('./pages/SettingsPage'),\n permissions: [],\n });\n\n app.registerPlugin({\n id: PLUGIN_ID,\n name: PLUGIN_ID,\n });\n\n app.customFields.register({\n name: 'scoped-select',\n pluginId: PLUGIN_ID,\n type: 'string',\n intlLabel: {\n id: `${PLUGIN_ID}.scoped-select.label`,\n defaultMessage: 'Scoped Quota Select',\n },\n intlDescription: {\n id: `${PLUGIN_ID}.scoped-select.description`,\n defaultMessage: 'Dropdown with dynamic cross-component usage quota enforcement',\n },\n components: {\n Input: async () => import('./components/ScopedQuotaSelectInput'),\n },\n });\n },\n bootstrap(app: any) {\n try {\n const contentManagerPlugin = app.getPlugin('content-manager');\n if (contentManagerPlugin?.apis?.addDocumentAction) {\n contentManagerPlugin.apis.addDocumentAction((actions: any[]) => {\n if (window.strapi?.features?.isEnabled?.('cms-content-history')) {\n return actions;\n }\n\n const hasHistory = actions.some((a: any) => a.type === 'history' || a.id === 'content-history');\n if (hasHistory) return actions;\n\n const customHistoryAction = ({ model, document }: any) => {\n if (window.strapi?.features?.isEnabled?.('cms-content-history')) {\n return null;\n }\n\n return {\n icon: React.createElement(ClockCounterClockwise, {}),\n label: 'Content History',\n onClick: () => {\n const currentPath = window.location.pathname.replace(/\\/$/, '');\n if (!currentPath.endsWith('/history')) {\n window.location.href = `${currentPath}/history${window.location.search}`;\n }\n },\n disabled: !document || !document.id || !model?.startsWith('api::'),\n position: 'header',\n };\n };\n customHistoryAction.type = 'history';\n customHistoryAction.position = 'header';\n\n const deleteIndex = actions.findIndex((a: any) => a.type === 'delete');\n if (deleteIndex !== -1) {\n actions.splice(deleteIndex, 0, customHistoryAction);\n } else {\n actions.push(customHistoryAction);\n }\n return actions;\n });\n }\n } catch {}\n },\n};\n"],"names":["Pencil","React","ClockCounterClockwise"],"mappings":";;;;;AAAO,MAAM,YAAY;ACIzB,MAAA,QAAe;AAAA,EACb,SAAS,KAAU;AACjB,QAAI,YAAY;AAAA,MACd,IAAI,WAAW,SAAS;AAAA,MACxB,MAAMA,MAAAA;AAAAA,MACN,WAAW;AAAA,QACT,IAAI,GAAG,SAAS;AAAA,QAChB,gBAAgB;AAAA,MAAA;AAAA,MAElB,WAAW,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,QAAO,oCAAqB,CAAA;AAAA,MAC7C,aAAa,CAAA;AAAA,MACb,UAAU;AAAA,IAAA,CACX;AAED,QAAI,gBAAgB,UAAU;AAAA,MAC5B,IAAI,GAAG,SAAS;AAAA,MAChB,WAAW;AAAA,QACT,IAAI,GAAG,SAAS;AAAA,QAChB,gBAAgB;AAAA,MAAA;AAAA,MAElB,IAAI,GAAG,SAAS;AAAA,MAChB,WAAW,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,QAAO,qCAAsB,CAAA;AAAA,MAC9C,aAAa,CAAA;AAAA,IAAC,CACf;AAED,QAAI,eAAe;AAAA,MACjB,IAAI;AAAA,MACJ,MAAM;AAAA,IAAA,CACP;AAED,QAAI,aAAa,SAAS;AAAA,MACxB,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM;AAAA,MACN,WAAW;AAAA,QACT,IAAI,GAAG,SAAS;AAAA,QAChB,gBAAgB;AAAA,MAAA;AAAA,MAElB,iBAAiB;AAAA,QACf,IAAI,GAAG,SAAS;AAAA,QAChB,gBAAgB;AAAA,MAAA;AAAA,MAElB,YAAY;AAAA,QACV,OAAO,YAAY,QAAA,QAAA,EAAA,KAAA,MAAA,QAAO,+CAAqC,CAAA;AAAA,MAAA;AAAA,IACjE,CACD;AAAA,EAAA;AAAA,EAEH,UAAU,KAAU;AAClB,QAAI;AACF,YAAM,uBAAuB,IAAI,UAAU,iBAAiB;AAC5D,UAAI,sBAAsB,MAAM,mBAAmB;AACjD,6BAAqB,KAAK,kBAAkB,CAAC,YAAmB;AAC9D,cAAI,OAAO,QAAQ,UAAU,YAAY,qBAAqB,GAAG;AAC/D,mBAAO;AAAA,UAAA;AAGT,gBAAM,aAAa,QAAQ,KAAK,CAAC,MAAW,EAAE,SAAS,aAAa,EAAE,OAAO,iBAAiB;AAC9F,cAAI,WAAY,QAAO;AAEvB,gBAAM,sBAAsB,CAAC,EAAE,OAAO,eAAoB;AACxD,gBAAI,OAAO,QAAQ,UAAU,YAAY,qBAAqB,GAAG;AAC/D,qBAAO;AAAA,YAAA;AAGT,mBAAO;AAAA,cACL,MAAMC,eAAAA,QAAM,cAAcC,MAAAA,uBAAuB,CAAA,CAAE;AAAA,cACnD,OAAO;AAAA,cACP,SAAS,MAAM;AACb,sBAAM,cAAc,OAAO,SAAS,SAAS,QAAQ,OAAO,EAAE;AAC9D,oBAAI,CAAC,YAAY,SAAS,UAAU,GAAG;AACrC,yBAAO,SAAS,OAAO,GAAG,WAAW,WAAW,OAAO,SAAS,MAAM;AAAA,gBAAA;AAAA,cACxE;AAAA,cAEF,UAAU,CAAC,YAAY,CAAC,SAAS,MAAM,CAAC,OAAO,WAAW,OAAO;AAAA,cACjE,UAAU;AAAA,YAAA;AAAA,UACZ;AAEF,8BAAoB,OAAO;AAC3B,8BAAoB,WAAW;AAE/B,gBAAM,cAAc,QAAQ,UAAU,CAAC,MAAW,EAAE,SAAS,QAAQ;AACrE,cAAI,gBAAgB,IAAI;AACtB,oBAAQ,OAAO,aAAa,GAAG,mBAAmB;AAAA,UAAA,OAC7C;AACL,oBAAQ,KAAK,mBAAmB;AAAA,UAAA;AAElC,iBAAO;AAAA,QAAA,CACR;AAAA,MAAA;AAAA,IACH,QACM;AAAA,IAAA;AAAA,EAAC;AAEb;;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../admin/src/pluginId.ts","../../admin/src/index.ts"],"sourcesContent":["export const PLUGIN_ID = 'bulk-updater';\n","import React from 'react';\nimport { PLUGIN_ID } from './pluginId';\nimport { Pencil, ClockCounterClockwise } from '@strapi/icons';\n\nfunction escapeHtml(str: string): string {\n return (str || '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction showQuotaErrorToast(message: string) {\n if (typeof document === 'undefined') return;\n const existing = document.getElementById('strapi-quota-validation-banner');\n if (existing) existing.remove();\n\n let title = 'Validation Error';\n let detail = message;\n const match = message.match(/^(Validation Error \\([^)]+\\)):\\s*(.+)$/s);\n if (match) {\n title = match[1];\n detail = match[2];\n }\n\n const banner = document.createElement('div');\n banner.id = 'strapi-quota-validation-banner';\n banner.style.position = 'fixed';\n banner.style.top = '24px';\n banner.style.left = '50%';\n banner.style.transform = 'translateX(-50%)';\n banner.style.zIndex = '999999';\n banner.style.minWidth = '420px';\n banner.style.maxWidth = '720px';\n banner.style.backgroundColor = '#D02B20';\n banner.style.color = '#FFFFFF';\n banner.style.padding = '14px 20px';\n banner.style.borderRadius = '6px';\n banner.style.boxShadow = '0 10px 25px rgba(0,0,0,0.35)';\n banner.style.display = 'flex';\n banner.style.alignItems = 'flex-start';\n banner.style.gap = '12px';\n banner.style.fontFamily = '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif';\n banner.style.fontSize = '14px';\n banner.style.lineHeight = '1.45';\n banner.style.transition = 'opacity 0.2s ease-in-out';\n\n banner.innerHTML = `\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"flex-shrink: 0; margin-top: 1px;\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"></line>\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"></line>\n </svg>\n <div style=\"flex: 1;\">\n <div style=\"font-weight: 700; margin-bottom: 2px;\">${escapeHtml(title)}</div>\n <div style=\"opacity: 0.95;\">${escapeHtml(detail)}</div>\n </div>\n <button style=\"background: none; border: none; color: #FFF; cursor: pointer; padding: 0 4px; font-size: 18px; line-height: 1; opacity: 0.8;\" onclick=\"this.parentElement.remove()\">×</button>\n `;\n\n document.body.appendChild(banner);\n\n setTimeout(() => {\n if (banner.parentElement) {\n banner.style.opacity = '0';\n setTimeout(() => banner.remove(), 250);\n }\n }, 10000);\n}\n\nexport default {\n register(app: any) {\n app.addMenuLink({\n to: `plugins/${PLUGIN_ID}`,\n icon: Pencil,\n intlLabel: {\n id: `${PLUGIN_ID}.plugin.name`,\n defaultMessage: 'Bulk Attribute Updater',\n },\n Component: () => import('./pages/BulkUpdater'),\n permissions: [],\n position: 3,\n });\n\n app.addSettingsLink('global', {\n id: `${PLUGIN_ID}-settings`,\n intlLabel: {\n id: `${PLUGIN_ID}.settings.name`,\n defaultMessage: 'Bulk Updater Settings',\n },\n to: `${PLUGIN_ID}`,\n Component: () => import('./pages/SettingsPage'),\n permissions: [],\n });\n\n app.registerPlugin({\n id: PLUGIN_ID,\n name: PLUGIN_ID,\n });\n\n app.customFields.register({\n name: 'scoped-select',\n pluginId: PLUGIN_ID,\n type: 'string',\n intlLabel: {\n id: `${PLUGIN_ID}.scoped-select.label`,\n defaultMessage: 'Scoped Quota Select',\n },\n intlDescription: {\n id: `${PLUGIN_ID}.scoped-select.description`,\n defaultMessage: 'Dropdown with dynamic cross-component usage quota enforcement',\n },\n components: {\n Input: async () => import('./components/ScopedQuotaSelectInput'),\n },\n });\n },\n bootstrap(app: any) {\n // Install global response interceptor for quota validation rule error banners\n if (typeof window !== 'undefined' && !(window as any).__bulk_updater_interceptor_installed) {\n (window as any).__bulk_updater_interceptor_installed = true;\n\n if (window.fetch) {\n const origFetch = window.fetch;\n window.fetch = async function (...args) {\n const response = await origFetch.apply(this, args);\n if (response.status === 400 || response.status === 422 || response.status === 500) {\n try {\n const clone = response.clone();\n const data = await clone.json();\n const errMsg = data?.error?.message || data?.message;\n if (\n typeof errMsg === 'string' &&\n (errMsg.startsWith('Validation Error') || errMsg.includes('cannot appear more than'))\n ) {\n showQuotaErrorToast(errMsg);\n }\n } catch {}\n }\n return response;\n };\n }\n\n if ((window as any).XMLHttpRequest) {\n const origOpen = XMLHttpRequest.prototype.open;\n XMLHttpRequest.prototype.open = function (...args: any[]) {\n this.addEventListener('load', function () {\n if (this.status === 400 || this.status === 422 || this.status === 500) {\n try {\n const data = JSON.parse(this.responseText);\n const errMsg = data?.error?.message || data?.message;\n if (\n typeof errMsg === 'string' &&\n (errMsg.startsWith('Validation Error') || errMsg.includes('cannot appear more than'))\n ) {\n showQuotaErrorToast(errMsg);\n }\n } catch {}\n }\n });\n return origOpen.apply(this, args as any);\n };\n }\n }\n try {\n const contentManagerPlugin = app.getPlugin('content-manager');\n if (contentManagerPlugin?.apis?.addDocumentAction) {\n contentManagerPlugin.apis.addDocumentAction((actions: any[]) => {\n if (window.strapi?.features?.isEnabled?.('cms-content-history')) {\n return actions;\n }\n\n const hasHistory = actions.some((a: any) => a.type === 'history' || a.id === 'content-history');\n if (hasHistory) return actions;\n\n const customHistoryAction = ({ model, document }: any) => {\n if (window.strapi?.features?.isEnabled?.('cms-content-history')) {\n return null;\n }\n\n return {\n icon: React.createElement(ClockCounterClockwise, {}),\n label: 'Content History',\n onClick: () => {\n const currentPath = window.location.pathname.replace(/\\/$/, '');\n if (!currentPath.endsWith('/history')) {\n window.location.href = `${currentPath}/history${window.location.search}`;\n }\n },\n disabled: !document || !document.id || !model?.startsWith('api::'),\n position: 'header',\n };\n };\n customHistoryAction.type = 'history';\n customHistoryAction.position = 'header';\n\n const deleteIndex = actions.findIndex((a: any) => a.type === 'delete');\n if (deleteIndex !== -1) {\n actions.splice(deleteIndex, 0, customHistoryAction);\n } else {\n actions.push(customHistoryAction);\n }\n return actions;\n });\n }\n } catch {}\n },\n};\n"],"names":["Pencil","document","React","ClockCounterClockwise"],"mappings":"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"}
|
package/dist/admin/index.mjs
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
import React__default from "react";
|
|
2
2
|
import { ClockCounterClockwise, Pencil } from "@strapi/icons";
|
|
3
3
|
const PLUGIN_ID = "bulk-updater";
|
|
4
|
+
function escapeHtml(str) {
|
|
5
|
+
return (str || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6
|
+
}
|
|
7
|
+
function showQuotaErrorToast(message) {
|
|
8
|
+
if (typeof document === "undefined") return;
|
|
9
|
+
const existing = document.getElementById("strapi-quota-validation-banner");
|
|
10
|
+
if (existing) existing.remove();
|
|
11
|
+
let title = "Validation Error";
|
|
12
|
+
let detail = message;
|
|
13
|
+
const match = message.match(/^(Validation Error \([^)]+\)):\s*(.+)$/s);
|
|
14
|
+
if (match) {
|
|
15
|
+
title = match[1];
|
|
16
|
+
detail = match[2];
|
|
17
|
+
}
|
|
18
|
+
const banner = document.createElement("div");
|
|
19
|
+
banner.id = "strapi-quota-validation-banner";
|
|
20
|
+
banner.style.position = "fixed";
|
|
21
|
+
banner.style.top = "24px";
|
|
22
|
+
banner.style.left = "50%";
|
|
23
|
+
banner.style.transform = "translateX(-50%)";
|
|
24
|
+
banner.style.zIndex = "999999";
|
|
25
|
+
banner.style.minWidth = "420px";
|
|
26
|
+
banner.style.maxWidth = "720px";
|
|
27
|
+
banner.style.backgroundColor = "#D02B20";
|
|
28
|
+
banner.style.color = "#FFFFFF";
|
|
29
|
+
banner.style.padding = "14px 20px";
|
|
30
|
+
banner.style.borderRadius = "6px";
|
|
31
|
+
banner.style.boxShadow = "0 10px 25px rgba(0,0,0,0.35)";
|
|
32
|
+
banner.style.display = "flex";
|
|
33
|
+
banner.style.alignItems = "flex-start";
|
|
34
|
+
banner.style.gap = "12px";
|
|
35
|
+
banner.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
36
|
+
banner.style.fontSize = "14px";
|
|
37
|
+
banner.style.lineHeight = "1.45";
|
|
38
|
+
banner.style.transition = "opacity 0.2s ease-in-out";
|
|
39
|
+
banner.innerHTML = `
|
|
40
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink: 0; margin-top: 1px;">
|
|
41
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
42
|
+
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
43
|
+
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
44
|
+
</svg>
|
|
45
|
+
<div style="flex: 1;">
|
|
46
|
+
<div style="font-weight: 700; margin-bottom: 2px;">${escapeHtml(title)}</div>
|
|
47
|
+
<div style="opacity: 0.95;">${escapeHtml(detail)}</div>
|
|
48
|
+
</div>
|
|
49
|
+
<button style="background: none; border: none; color: #FFF; cursor: pointer; padding: 0 4px; font-size: 18px; line-height: 1; opacity: 0.8;" onclick="this.parentElement.remove()">×</button>
|
|
50
|
+
`;
|
|
51
|
+
document.body.appendChild(banner);
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
if (banner.parentElement) {
|
|
54
|
+
banner.style.opacity = "0";
|
|
55
|
+
setTimeout(() => banner.remove(), 250);
|
|
56
|
+
}
|
|
57
|
+
}, 1e4);
|
|
58
|
+
}
|
|
4
59
|
const index = {
|
|
5
60
|
register(app) {
|
|
6
61
|
app.addMenuLink({
|
|
@@ -46,6 +101,45 @@ const index = {
|
|
|
46
101
|
});
|
|
47
102
|
},
|
|
48
103
|
bootstrap(app) {
|
|
104
|
+
if (typeof window !== "undefined" && !window.__bulk_updater_interceptor_installed) {
|
|
105
|
+
window.__bulk_updater_interceptor_installed = true;
|
|
106
|
+
if (window.fetch) {
|
|
107
|
+
const origFetch = window.fetch;
|
|
108
|
+
window.fetch = async function(...args) {
|
|
109
|
+
const response = await origFetch.apply(this, args);
|
|
110
|
+
if (response.status === 400 || response.status === 422 || response.status === 500) {
|
|
111
|
+
try {
|
|
112
|
+
const clone = response.clone();
|
|
113
|
+
const data = await clone.json();
|
|
114
|
+
const errMsg = data?.error?.message || data?.message;
|
|
115
|
+
if (typeof errMsg === "string" && (errMsg.startsWith("Validation Error") || errMsg.includes("cannot appear more than"))) {
|
|
116
|
+
showQuotaErrorToast(errMsg);
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return response;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (window.XMLHttpRequest) {
|
|
125
|
+
const origOpen = XMLHttpRequest.prototype.open;
|
|
126
|
+
XMLHttpRequest.prototype.open = function(...args) {
|
|
127
|
+
this.addEventListener("load", function() {
|
|
128
|
+
if (this.status === 400 || this.status === 422 || this.status === 500) {
|
|
129
|
+
try {
|
|
130
|
+
const data = JSON.parse(this.responseText);
|
|
131
|
+
const errMsg = data?.error?.message || data?.message;
|
|
132
|
+
if (typeof errMsg === "string" && (errMsg.startsWith("Validation Error") || errMsg.includes("cannot appear more than"))) {
|
|
133
|
+
showQuotaErrorToast(errMsg);
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return origOpen.apply(this, args);
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
49
143
|
try {
|
|
50
144
|
const contentManagerPlugin = app.getPlugin("content-manager");
|
|
51
145
|
if (contentManagerPlugin?.apis?.addDocumentAction) {
|
|
@@ -55,7 +149,7 @@ const index = {
|
|
|
55
149
|
}
|
|
56
150
|
const hasHistory = actions.some((a) => a.type === "history" || a.id === "content-history");
|
|
57
151
|
if (hasHistory) return actions;
|
|
58
|
-
const customHistoryAction = ({ model, document }) => {
|
|
152
|
+
const customHistoryAction = ({ model, document: document2 }) => {
|
|
59
153
|
if (window.strapi?.features?.isEnabled?.("cms-content-history")) {
|
|
60
154
|
return null;
|
|
61
155
|
}
|
|
@@ -68,7 +162,7 @@ const index = {
|
|
|
68
162
|
window.location.href = `${currentPath}/history${window.location.search}`;
|
|
69
163
|
}
|
|
70
164
|
},
|
|
71
|
-
disabled: !
|
|
165
|
+
disabled: !document2 || !document2.id || !model?.startsWith("api::"),
|
|
72
166
|
position: "header"
|
|
73
167
|
};
|
|
74
168
|
};
|
package/dist/admin/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../admin/src/pluginId.ts","../../admin/src/index.ts"],"sourcesContent":["export const PLUGIN_ID = 'bulk-updater';\n","import React from 'react';\nimport { PLUGIN_ID } from './pluginId';\nimport { Pencil, ClockCounterClockwise } from '@strapi/icons';\n\nexport default {\n register(app: any) {\n app.addMenuLink({\n to: `plugins/${PLUGIN_ID}`,\n icon: Pencil,\n intlLabel: {\n id: `${PLUGIN_ID}.plugin.name`,\n defaultMessage: 'Bulk Attribute Updater',\n },\n Component: () => import('./pages/BulkUpdater'),\n permissions: [],\n position: 3,\n });\n\n app.addSettingsLink('global', {\n id: `${PLUGIN_ID}-settings`,\n intlLabel: {\n id: `${PLUGIN_ID}.settings.name`,\n defaultMessage: 'Bulk Updater Settings',\n },\n to: `${PLUGIN_ID}`,\n Component: () => import('./pages/SettingsPage'),\n permissions: [],\n });\n\n app.registerPlugin({\n id: PLUGIN_ID,\n name: PLUGIN_ID,\n });\n\n app.customFields.register({\n name: 'scoped-select',\n pluginId: PLUGIN_ID,\n type: 'string',\n intlLabel: {\n id: `${PLUGIN_ID}.scoped-select.label`,\n defaultMessage: 'Scoped Quota Select',\n },\n intlDescription: {\n id: `${PLUGIN_ID}.scoped-select.description`,\n defaultMessage: 'Dropdown with dynamic cross-component usage quota enforcement',\n },\n components: {\n Input: async () => import('./components/ScopedQuotaSelectInput'),\n },\n });\n },\n bootstrap(app: any) {\n try {\n const contentManagerPlugin = app.getPlugin('content-manager');\n if (contentManagerPlugin?.apis?.addDocumentAction) {\n contentManagerPlugin.apis.addDocumentAction((actions: any[]) => {\n if (window.strapi?.features?.isEnabled?.('cms-content-history')) {\n return actions;\n }\n\n const hasHistory = actions.some((a: any) => a.type === 'history' || a.id === 'content-history');\n if (hasHistory) return actions;\n\n const customHistoryAction = ({ model, document }: any) => {\n if (window.strapi?.features?.isEnabled?.('cms-content-history')) {\n return null;\n }\n\n return {\n icon: React.createElement(ClockCounterClockwise, {}),\n label: 'Content History',\n onClick: () => {\n const currentPath = window.location.pathname.replace(/\\/$/, '');\n if (!currentPath.endsWith('/history')) {\n window.location.href = `${currentPath}/history${window.location.search}`;\n }\n },\n disabled: !document || !document.id || !model?.startsWith('api::'),\n position: 'header',\n };\n };\n customHistoryAction.type = 'history';\n customHistoryAction.position = 'header';\n\n const deleteIndex = actions.findIndex((a: any) => a.type === 'delete');\n if (deleteIndex !== -1) {\n actions.splice(deleteIndex, 0, customHistoryAction);\n } else {\n actions.push(customHistoryAction);\n }\n return actions;\n });\n }\n } catch {}\n },\n};\n"],"names":["React"],"mappings":";;AAAO,MAAM,YAAY;ACIzB,MAAA,QAAe;AAAA,EACb,SAAS,KAAU;AACjB,QAAI,YAAY;AAAA,MACd,IAAI,WAAW,SAAS;AAAA,MACxB,MAAM;AAAA,MACN,WAAW;AAAA,QACT,IAAI,GAAG,SAAS;AAAA,QAChB,gBAAgB;AAAA,MAAA;AAAA,MAElB,WAAW,MAAM,OAAO,qCAAqB;AAAA,MAC7C,aAAa,CAAA;AAAA,MACb,UAAU;AAAA,IAAA,CACX;AAED,QAAI,gBAAgB,UAAU;AAAA,MAC5B,IAAI,GAAG,SAAS;AAAA,MAChB,WAAW;AAAA,QACT,IAAI,GAAG,SAAS;AAAA,QAChB,gBAAgB;AAAA,MAAA;AAAA,MAElB,IAAI,GAAG,SAAS;AAAA,MAChB,WAAW,MAAM,OAAO,sCAAsB;AAAA,MAC9C,aAAa,CAAA;AAAA,IAAC,CACf;AAED,QAAI,eAAe;AAAA,MACjB,IAAI;AAAA,MACJ,MAAM;AAAA,IAAA,CACP;AAED,QAAI,aAAa,SAAS;AAAA,MACxB,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM;AAAA,MACN,WAAW;AAAA,QACT,IAAI,GAAG,SAAS;AAAA,QAChB,gBAAgB;AAAA,MAAA;AAAA,MAElB,iBAAiB;AAAA,QACf,IAAI,GAAG,SAAS;AAAA,QAChB,gBAAgB;AAAA,MAAA;AAAA,MAElB,YAAY;AAAA,QACV,OAAO,YAAY,OAAO,gDAAqC;AAAA,MAAA;AAAA,IACjE,CACD;AAAA,EAAA;AAAA,EAEH,UAAU,KAAU;AAClB,QAAI;AACF,YAAM,uBAAuB,IAAI,UAAU,iBAAiB;AAC5D,UAAI,sBAAsB,MAAM,mBAAmB;AACjD,6BAAqB,KAAK,kBAAkB,CAAC,YAAmB;AAC9D,cAAI,OAAO,QAAQ,UAAU,YAAY,qBAAqB,GAAG;AAC/D,mBAAO;AAAA,UAAA;AAGT,gBAAM,aAAa,QAAQ,KAAK,CAAC,MAAW,EAAE,SAAS,aAAa,EAAE,OAAO,iBAAiB;AAC9F,cAAI,WAAY,QAAO;AAEvB,gBAAM,sBAAsB,CAAC,EAAE,OAAO,eAAoB;AACxD,gBAAI,OAAO,QAAQ,UAAU,YAAY,qBAAqB,GAAG;AAC/D,qBAAO;AAAA,YAAA;AAGT,mBAAO;AAAA,cACL,MAAMA,eAAM,cAAc,uBAAuB,CAAA,CAAE;AAAA,cACnD,OAAO;AAAA,cACP,SAAS,MAAM;AACb,sBAAM,cAAc,OAAO,SAAS,SAAS,QAAQ,OAAO,EAAE;AAC9D,oBAAI,CAAC,YAAY,SAAS,UAAU,GAAG;AACrC,yBAAO,SAAS,OAAO,GAAG,WAAW,WAAW,OAAO,SAAS,MAAM;AAAA,gBAAA;AAAA,cACxE;AAAA,cAEF,UAAU,CAAC,YAAY,CAAC,SAAS,MAAM,CAAC,OAAO,WAAW,OAAO;AAAA,cACjE,UAAU;AAAA,YAAA;AAAA,UACZ;AAEF,8BAAoB,OAAO;AAC3B,8BAAoB,WAAW;AAE/B,gBAAM,cAAc,QAAQ,UAAU,CAAC,MAAW,EAAE,SAAS,QAAQ;AACrE,cAAI,gBAAgB,IAAI;AACtB,oBAAQ,OAAO,aAAa,GAAG,mBAAmB;AAAA,UAAA,OAC7C;AACL,oBAAQ,KAAK,mBAAmB;AAAA,UAAA;AAElC,iBAAO;AAAA,QAAA,CACR;AAAA,MAAA;AAAA,IACH,QACM;AAAA,IAAA;AAAA,EAAC;AAEb;"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../admin/src/pluginId.ts","../../admin/src/index.ts"],"sourcesContent":["export const PLUGIN_ID = 'bulk-updater';\n","import React from 'react';\nimport { PLUGIN_ID } from './pluginId';\nimport { Pencil, ClockCounterClockwise } from '@strapi/icons';\n\nfunction escapeHtml(str: string): string {\n return (str || '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction showQuotaErrorToast(message: string) {\n if (typeof document === 'undefined') return;\n const existing = document.getElementById('strapi-quota-validation-banner');\n if (existing) existing.remove();\n\n let title = 'Validation Error';\n let detail = message;\n const match = message.match(/^(Validation Error \\([^)]+\\)):\\s*(.+)$/s);\n if (match) {\n title = match[1];\n detail = match[2];\n }\n\n const banner = document.createElement('div');\n banner.id = 'strapi-quota-validation-banner';\n banner.style.position = 'fixed';\n banner.style.top = '24px';\n banner.style.left = '50%';\n banner.style.transform = 'translateX(-50%)';\n banner.style.zIndex = '999999';\n banner.style.minWidth = '420px';\n banner.style.maxWidth = '720px';\n banner.style.backgroundColor = '#D02B20';\n banner.style.color = '#FFFFFF';\n banner.style.padding = '14px 20px';\n banner.style.borderRadius = '6px';\n banner.style.boxShadow = '0 10px 25px rgba(0,0,0,0.35)';\n banner.style.display = 'flex';\n banner.style.alignItems = 'flex-start';\n banner.style.gap = '12px';\n banner.style.fontFamily = '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif';\n banner.style.fontSize = '14px';\n banner.style.lineHeight = '1.45';\n banner.style.transition = 'opacity 0.2s ease-in-out';\n\n banner.innerHTML = `\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"flex-shrink: 0; margin-top: 1px;\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"></line>\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"></line>\n </svg>\n <div style=\"flex: 1;\">\n <div style=\"font-weight: 700; margin-bottom: 2px;\">${escapeHtml(title)}</div>\n <div style=\"opacity: 0.95;\">${escapeHtml(detail)}</div>\n </div>\n <button style=\"background: none; border: none; color: #FFF; cursor: pointer; padding: 0 4px; font-size: 18px; line-height: 1; opacity: 0.8;\" onclick=\"this.parentElement.remove()\">×</button>\n `;\n\n document.body.appendChild(banner);\n\n setTimeout(() => {\n if (banner.parentElement) {\n banner.style.opacity = '0';\n setTimeout(() => banner.remove(), 250);\n }\n }, 10000);\n}\n\nexport default {\n register(app: any) {\n app.addMenuLink({\n to: `plugins/${PLUGIN_ID}`,\n icon: Pencil,\n intlLabel: {\n id: `${PLUGIN_ID}.plugin.name`,\n defaultMessage: 'Bulk Attribute Updater',\n },\n Component: () => import('./pages/BulkUpdater'),\n permissions: [],\n position: 3,\n });\n\n app.addSettingsLink('global', {\n id: `${PLUGIN_ID}-settings`,\n intlLabel: {\n id: `${PLUGIN_ID}.settings.name`,\n defaultMessage: 'Bulk Updater Settings',\n },\n to: `${PLUGIN_ID}`,\n Component: () => import('./pages/SettingsPage'),\n permissions: [],\n });\n\n app.registerPlugin({\n id: PLUGIN_ID,\n name: PLUGIN_ID,\n });\n\n app.customFields.register({\n name: 'scoped-select',\n pluginId: PLUGIN_ID,\n type: 'string',\n intlLabel: {\n id: `${PLUGIN_ID}.scoped-select.label`,\n defaultMessage: 'Scoped Quota Select',\n },\n intlDescription: {\n id: `${PLUGIN_ID}.scoped-select.description`,\n defaultMessage: 'Dropdown with dynamic cross-component usage quota enforcement',\n },\n components: {\n Input: async () => import('./components/ScopedQuotaSelectInput'),\n },\n });\n },\n bootstrap(app: any) {\n // Install global response interceptor for quota validation rule error banners\n if (typeof window !== 'undefined' && !(window as any).__bulk_updater_interceptor_installed) {\n (window as any).__bulk_updater_interceptor_installed = true;\n\n if (window.fetch) {\n const origFetch = window.fetch;\n window.fetch = async function (...args) {\n const response = await origFetch.apply(this, args);\n if (response.status === 400 || response.status === 422 || response.status === 500) {\n try {\n const clone = response.clone();\n const data = await clone.json();\n const errMsg = data?.error?.message || data?.message;\n if (\n typeof errMsg === 'string' &&\n (errMsg.startsWith('Validation Error') || errMsg.includes('cannot appear more than'))\n ) {\n showQuotaErrorToast(errMsg);\n }\n } catch {}\n }\n return response;\n };\n }\n\n if ((window as any).XMLHttpRequest) {\n const origOpen = XMLHttpRequest.prototype.open;\n XMLHttpRequest.prototype.open = function (...args: any[]) {\n this.addEventListener('load', function () {\n if (this.status === 400 || this.status === 422 || this.status === 500) {\n try {\n const data = JSON.parse(this.responseText);\n const errMsg = data?.error?.message || data?.message;\n if (\n typeof errMsg === 'string' &&\n (errMsg.startsWith('Validation Error') || errMsg.includes('cannot appear more than'))\n ) {\n showQuotaErrorToast(errMsg);\n }\n } catch {}\n }\n });\n return origOpen.apply(this, args as any);\n };\n }\n }\n try {\n const contentManagerPlugin = app.getPlugin('content-manager');\n if (contentManagerPlugin?.apis?.addDocumentAction) {\n contentManagerPlugin.apis.addDocumentAction((actions: any[]) => {\n if (window.strapi?.features?.isEnabled?.('cms-content-history')) {\n return actions;\n }\n\n const hasHistory = actions.some((a: any) => a.type === 'history' || a.id === 'content-history');\n if (hasHistory) return actions;\n\n const customHistoryAction = ({ model, document }: any) => {\n if (window.strapi?.features?.isEnabled?.('cms-content-history')) {\n return null;\n }\n\n return {\n icon: React.createElement(ClockCounterClockwise, {}),\n label: 'Content History',\n onClick: () => {\n const currentPath = window.location.pathname.replace(/\\/$/, '');\n if (!currentPath.endsWith('/history')) {\n window.location.href = `${currentPath}/history${window.location.search}`;\n }\n },\n disabled: !document || !document.id || !model?.startsWith('api::'),\n position: 'header',\n };\n };\n customHistoryAction.type = 'history';\n customHistoryAction.position = 'header';\n\n const deleteIndex = actions.findIndex((a: any) => a.type === 'delete');\n if (deleteIndex !== -1) {\n actions.splice(deleteIndex, 0, customHistoryAction);\n } else {\n actions.push(customHistoryAction);\n }\n return actions;\n });\n }\n } catch {}\n },\n};\n"],"names":["document","React"],"mappings":"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"}
|