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.
@@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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: !document || !document.id || !model?.startsWith("api::"),
168
+ disabled: !document2 || !document2.id || !model?.startsWith("api::"),
75
169
  position: "header"
76
170
  };
77
171
  };
@@ -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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\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":";;;;;AAAO,MAAM,YAAY;ACIzB,SAAS,WAAW,KAAqB;AACvC,UAAQ,OAAO,IACZ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,OAAO;AAC1B;AAEA,SAAS,oBAAoB,SAAiB;AAC5C,MAAI,OAAO,aAAa,YAAa;AACrC,QAAM,WAAW,SAAS,eAAe,gCAAgC;AACzE,MAAI,mBAAmB,OAAA;AAEvB,MAAI,QAAQ;AACZ,MAAI,SAAS;AACb,QAAM,QAAQ,QAAQ,MAAM,yCAAyC;AACrE,MAAI,OAAO;AACT,YAAQ,MAAM,CAAC;AACf,aAAS,MAAM,CAAC;AAAA,EAAA;AAGlB,QAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,SAAO,KAAK;AACZ,SAAO,MAAM,WAAW;AACxB,SAAO,MAAM,MAAM;AACnB,SAAO,MAAM,OAAO;AACpB,SAAO,MAAM,YAAY;AACzB,SAAO,MAAM,SAAS;AACtB,SAAO,MAAM,WAAW;AACxB,SAAO,MAAM,WAAW;AACxB,SAAO,MAAM,kBAAkB;AAC/B,SAAO,MAAM,QAAQ;AACrB,SAAO,MAAM,UAAU;AACvB,SAAO,MAAM,eAAe;AAC5B,SAAO,MAAM,YAAY;AACzB,SAAO,MAAM,UAAU;AACvB,SAAO,MAAM,aAAa;AAC1B,SAAO,MAAM,MAAM;AACnB,SAAO,MAAM,aAAa;AAC1B,SAAO,MAAM,WAAW;AACxB,SAAO,MAAM,aAAa;AAC1B,SAAO,MAAM,aAAa;AAE1B,SAAO,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2DAOsC,WAAW,KAAK,CAAC;AAAA,oCACxC,WAAW,MAAM,CAAC;AAAA;AAAA;AAAA;AAKpD,WAAS,KAAK,YAAY,MAAM;AAEhC,aAAW,MAAM;AACf,QAAI,OAAO,eAAe;AACxB,aAAO,MAAM,UAAU;AACvB,iBAAW,MAAM,OAAO,OAAA,GAAU,GAAG;AAAA,IAAA;AAAA,EACvC,GACC,GAAK;AACV;AAEA,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;AAElB,QAAI,OAAO,WAAW,eAAe,CAAE,OAAe,sCAAsC;AACzF,aAAe,uCAAuC;AAEvD,UAAI,OAAO,OAAO;AAChB,cAAM,YAAY,OAAO;AACzB,eAAO,QAAQ,kBAAmB,MAAM;AACtC,gBAAM,WAAW,MAAM,UAAU,MAAM,MAAM,IAAI;AACjD,cAAI,SAAS,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;AACjF,gBAAI;AACF,oBAAM,QAAQ,SAAS,MAAA;AACvB,oBAAM,OAAO,MAAM,MAAM,KAAA;AACzB,oBAAM,SAAS,MAAM,OAAO,WAAW,MAAM;AAC7C,kBACE,OAAO,WAAW,aACjB,OAAO,WAAW,kBAAkB,KAAK,OAAO,SAAS,yBAAyB,IACnF;AACA,oCAAoB,MAAM;AAAA,cAAA;AAAA,YAC5B,QACM;AAAA,YAAA;AAAA,UAAC;AAEX,iBAAO;AAAA,QAAA;AAAA,MACT;AAGF,UAAK,OAAe,gBAAgB;AAClC,cAAM,WAAW,eAAe,UAAU;AAC1C,uBAAe,UAAU,OAAO,YAAa,MAAa;AACxD,eAAK,iBAAiB,QAAQ,WAAY;AACxC,gBAAI,KAAK,WAAW,OAAO,KAAK,WAAW,OAAO,KAAK,WAAW,KAAK;AACrE,kBAAI;AACF,sBAAM,OAAO,KAAK,MAAM,KAAK,YAAY;AACzC,sBAAM,SAAS,MAAM,OAAO,WAAW,MAAM;AAC7C,oBACE,OAAO,WAAW,aACjB,OAAO,WAAW,kBAAkB,KAAK,OAAO,SAAS,yBAAyB,IACnF;AACA,sCAAoB,MAAM;AAAA,gBAAA;AAAA,cAC5B,QACM;AAAA,cAAA;AAAA,YAAC;AAAA,UACX,CACD;AACD,iBAAO,SAAS,MAAM,MAAM,IAAW;AAAA,QAAA;AAAA,MACzC;AAAA,IACF;AAEF,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,UAAAC,gBAAoB;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,CAACF,aAAY,CAACA,UAAS,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,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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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: !document || !document.id || !model?.startsWith("api::"),
165
+ disabled: !document2 || !document2.id || !model?.startsWith("api::"),
72
166
  position: "header"
73
167
  };
74
168
  };
@@ -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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\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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"}