strapi-plugin-bulk-updater 1.3.6 → 1.3.8
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/_chunks/{BulkUpdater-CXDs0Dws.js → BulkUpdater-BosdSbGF.js} +2 -2
- package/dist/_chunks/{BulkUpdater-CXDs0Dws.js.map → BulkUpdater-BosdSbGF.js.map} +1 -1
- package/dist/_chunks/{BulkUpdater-D812YfOE.mjs → BulkUpdater-DUrmamj-.mjs} +2 -2
- package/dist/_chunks/{BulkUpdater-D812YfOE.mjs.map → BulkUpdater-DUrmamj-.mjs.map} +1 -1
- package/dist/_chunks/{ScopedQuotaSelectInput-ChWrQIBV.mjs → ScopedQuotaSelectInput-Cy3eEj9-.mjs} +50 -23
- package/dist/_chunks/ScopedQuotaSelectInput-Cy3eEj9-.mjs.map +1 -0
- package/dist/_chunks/{ScopedQuotaSelectInput-s4oUMLgK.js → ScopedQuotaSelectInput-CyGlYa9x.js} +50 -23
- package/dist/_chunks/ScopedQuotaSelectInput-CyGlYa9x.js.map +1 -0
- package/dist/_chunks/{SettingsPage-BAvuOb7V.js → SettingsPage-B6Ia7SVS.js} +2 -2
- package/dist/_chunks/{SettingsPage-BAvuOb7V.js.map → SettingsPage-B6Ia7SVS.js.map} +1 -1
- package/dist/_chunks/{SettingsPage-BWo4l6Gd.mjs → SettingsPage-Bdse5MP4.mjs} +2 -2
- package/dist/_chunks/{SettingsPage-BWo4l6Gd.mjs.map → SettingsPage-Bdse5MP4.mjs.map} +1 -1
- package/dist/_chunks/{ValidationRulesTab-BLZWPGrZ.js → ValidationRulesTab-BqGz1VLo.js} +83 -16
- package/dist/_chunks/ValidationRulesTab-BqGz1VLo.js.map +1 -0
- package/dist/_chunks/{ValidationRulesTab-Dt53h-Ke.mjs → ValidationRulesTab-Dqkson67.mjs} +84 -17
- package/dist/_chunks/ValidationRulesTab-Dqkson67.mjs.map +1 -0
- package/dist/admin/index.js +3 -3
- package/dist/admin/index.mjs +3 -3
- package/dist/server/index.js +202 -66
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +202 -66
- package/dist/server/index.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/_chunks/ScopedQuotaSelectInput-ChWrQIBV.mjs.map +0 -1
- package/dist/_chunks/ScopedQuotaSelectInput-s4oUMLgK.js.map +0 -1
- package/dist/_chunks/ValidationRulesTab-BLZWPGrZ.js.map +0 -1
- package/dist/_chunks/ValidationRulesTab-Dt53h-Ke.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ValidationRulesTab-Dqkson67.mjs","sources":["../../admin/src/pages/ValidationRulesTab.tsx"],"sourcesContent":["import React, { useState, useEffect } from 'react';\nimport { useFetchClient, useNotification } from '@strapi/strapi/admin';\nimport {\n Box,\n Flex,\n Typography,\n Button,\n TextInput,\n Textarea,\n SingleSelect,\n SingleSelectOption,\n Badge,\n Table,\n Thead,\n Tbody,\n Tr,\n Th,\n Td,\n Alert,\n Checkbox,\n} from '@strapi/design-system';\nimport { Plus, Trash, Check, ArrowClockwise, Search, Download } from '@strapi/icons';\n\nexport interface ValidationRule {\n id: string;\n name: string;\n description?: string;\n collectionUid?: string;\n targetField: string;\n quotas: Record<string, number>;\n exceededAction: 'disable' | 'hide';\n enabled: boolean;\n scope: 'entry' | 'document';\n includeRichTextHtml?: boolean;\n richTextFields?: string[];\n createdAt?: string;\n updatedAt?: string;\n}\n\nexport default function ValidationRulesTab() {\n const { get, post, del } = useFetchClient();\n const { toggleNotification } = useNotification();\n\n const [rules, setRules] = useState<ValidationRule[]>([]);\n const [loading, setLoading] = useState<boolean>(true);\n const [modalOpen, setModalOpen] = useState<boolean>(false);\n const [editingRule, setEditingRule] = useState<ValidationRule | null>(null);\n\n // Available collections from backend models\n const [availableCollections, setAvailableCollections] = useState<Array<{ uid: string; displayName: string }>>([]);\n\n // Form state for creating/editing a rule\n const [formName, setFormName] = useState<string>('');\n const [formDesc, setFormDesc] = useState<string>('');\n const [formCollectionUid, setFormCollectionUid] = useState<string>('*');\n const [formField, setFormField] = useState<string>('');\n const [formAction, setFormAction] = useState<'disable' | 'hide'>('disable');\n const [formIncludeRichText, setFormIncludeRichText] = useState<boolean>(true);\n const [formQuotas, setFormQuotas] = useState<Array<{ key: string; maxAllowed: number }>>([\n { key: 'h1', maxAllowed: 1 },\n { key: 'h2', maxAllowed: 3 },\n ]);\n const [saving, setSaving] = useState<boolean>(false);\n\n // Scan modal state\n const [scanModalOpen, setScanModalOpen] = useState<boolean>(false);\n const [scanningRuleId, setScanningRuleId] = useState<string | null>(null);\n const [scanReport, setScanReport] = useState<any>(null);\n\n const fetchCollections = async () => {\n try {\n const res = await get('/bulk-updater/models');\n if (res.data?.targetUids && res.data?.metaByUid) {\n const list = res.data.targetUids.map((uid: string) => ({\n uid,\n displayName: res.data.metaByUid[uid]?.displayName || uid,\n }));\n setAvailableCollections(list);\n }\n } catch {\n // Fallback silently if models not loaded\n }\n };\n\n const handleScanRule = async (rule: ValidationRule) => {\n try {\n setScanningRuleId(rule.id);\n const res = await get(`/bulk-updater/validation-rules/${rule.id}/scan`);\n if (res.data && res.data.success) {\n setScanReport(res.data);\n setScanModalOpen(true);\n }\n } catch (err: any) {\n toggleNotification({\n type: 'danger',\n message: `Scan failed: ${err.message}`,\n });\n } finally {\n setScanningRuleId(null);\n }\n };\n\n const fetchRules = async () => {\n try {\n setLoading(true);\n const res = await get('/bulk-updater/validation-rules');\n if (res.data && res.data.success) {\n setRules(res.data.rules || []);\n }\n } catch (err: any) {\n toggleNotification({\n type: 'danger',\n message: `Failed to load rules: ${err.message}`,\n });\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n fetchRules();\n fetchCollections();\n }, []);\n\n const openCreateModal = () => {\n setEditingRule(null);\n setFormName('');\n setFormDesc('');\n setFormCollectionUid('*');\n setFormField('headingLevel');\n setFormAction('disable');\n setFormIncludeRichText(true);\n setFormQuotas([\n { key: 'h1', maxAllowed: 1 },\n { key: 'h2', maxAllowed: 3 },\n ]);\n setModalOpen(true);\n };\n\n const openEditModal = (rule: ValidationRule) => {\n setEditingRule(rule);\n setFormName(rule.name);\n setFormDesc(rule.description || '');\n setFormCollectionUid(rule.collectionUid || '*');\n setFormField(rule.targetField);\n setFormAction(rule.exceededAction || 'disable');\n setFormIncludeRichText(rule.includeRichTextHtml ?? true);\n const quotaPairs = Object.entries(rule.quotas || {}).map(([k, v]) => ({\n key: k,\n maxAllowed: v,\n }));\n setFormQuotas(quotaPairs.length > 0 ? quotaPairs : [{ key: 'h1', maxAllowed: 1 }]);\n setModalOpen(true);\n };\n\n const handleAddQuotaRow = () => {\n setFormQuotas((prev) => [...prev, { key: '', maxAllowed: 1 }]);\n };\n\n const handleRemoveQuotaRow = (idx: number) => {\n setFormQuotas((prev) => prev.filter((_, i) => i !== idx));\n };\n\n const handleQuotaChange = (idx: number, field: 'key' | 'maxAllowed', value: any) => {\n setFormQuotas((prev) =>\n prev.map((row, i) => {\n if (i === idx) {\n return {\n ...row,\n [field]: field === 'maxAllowed' ? Math.max(1, parseInt(value, 10) || 1) : String(value).trim(),\n };\n }\n return row;\n })\n );\n };\n\n const handleSave = async () => {\n if (!formName.trim() || !formField.trim()) {\n toggleNotification({\n type: 'warning',\n message: 'Rule Name and Target Field are required.',\n });\n return;\n }\n\n const quotasRecord: Record<string, number> = {};\n for (const q of formQuotas) {\n if (q.key.trim()) {\n quotasRecord[q.key.trim().toLowerCase()] = q.maxAllowed;\n }\n }\n\n if (Object.keys(quotasRecord).length === 0) {\n toggleNotification({\n type: 'warning',\n message: 'At least one quota option with a limit is required.',\n });\n return;\n }\n\n try {\n setSaving(true);\n const payload: Partial<ValidationRule> = {\n id: editingRule ? editingRule.id : undefined,\n name: formName.trim(),\n description: formDesc.trim(),\n collectionUid: formCollectionUid,\n targetField: formField.trim(),\n quotas: quotasRecord,\n exceededAction: formAction,\n enabled: editingRule ? editingRule.enabled : true,\n scope: 'entry',\n includeRichTextHtml: formIncludeRichText,\n };\n\n const res = await post('/bulk-updater/validation-rules', { rule: payload });\n if (res.data && res.data.success) {\n toggleNotification({\n type: 'success',\n message: `Rule \"${payload.name}\" saved successfully!`,\n });\n setModalOpen(false);\n fetchRules();\n }\n } catch (err: any) {\n toggleNotification({\n type: 'danger',\n message: `Failed to save rule: ${err.message}`,\n });\n } finally {\n setSaving(false);\n }\n };\n\n const handleToggle = async (rule: ValidationRule) => {\n try {\n const res = await post(`/bulk-updater/validation-rules/${rule.id}/toggle`, {\n enabled: !rule.enabled,\n });\n if (res.data && res.data.success) {\n setRules((prev) =>\n prev.map((r) => (r.id === rule.id ? { ...r, enabled: !rule.enabled } : r))\n );\n toggleNotification({\n type: 'info',\n message: `Rule \"${rule.name}\" is now ${!rule.enabled ? 'ACTIVE' : 'DISABLED'}.`,\n });\n }\n } catch (err: any) {\n toggleNotification({\n type: 'danger',\n message: `Failed to update rule status: ${err.message}`,\n });\n }\n };\n\n const handleDelete = async (ruleId: string, ruleName: string) => {\n if (!window.confirm(`Are you sure you want to delete the rule \"${ruleName}\"?`)) return;\n\n try {\n const res = await del(`/bulk-updater/validation-rules/${ruleId}`);\n if (res.data && res.data.success) {\n setRules((prev) => prev.filter((r) => r.id !== ruleId));\n toggleNotification({\n type: 'success',\n message: `Rule \"${ruleName}\" deleted.`,\n });\n }\n } catch (err: any) {\n toggleNotification({\n type: 'danger',\n message: `Failed to delete rule: ${err.message}`,\n });\n }\n };\n\n return (\n <Box>\n {/* Header Panel */}\n <Box padding={6} background=\"neutral0\" shadow=\"filterShadow\" hasRadius marginBottom={6}>\n <Flex justifyContent=\"space-between\" alignItems=\"center\" wrap=\"wrap\" gap={3}>\n <Box>\n <Typography variant=\"delta\" tag=\"h2\" fontWeight=\"bold\">\n Scoped & Cross-Component Validation Rules\n </Typography>\n <Typography variant=\"pi\" textColor=\"neutral600\" style={{ marginTop: 4 }}>\n Define collection-scoped rules and combined heading limits across structured components and free-form Rich Text / CKEditor HTML / Markdown content.\n </Typography>\n </Box>\n <Flex gap={2}>\n <Button\n variant=\"tertiary\"\n startIcon={<ArrowClockwise />}\n onClick={fetchRules}\n loading={loading}\n >\n Refresh\n </Button>\n <Button\n variant=\"default\"\n startIcon={<Plus />}\n onClick={openCreateModal}\n >\n Create New Rule\n </Button>\n </Flex>\n </Flex>\n </Box>\n\n {/* Rules Table */}\n <Box padding={6} background=\"neutral0\" shadow=\"filterShadow\" hasRadius>\n <Typography variant=\"sigma\" textColor=\"neutral600\" marginBottom={3} style={{ display: 'block' }}>\n CONFIGURED RULES ({rules.length})\n </Typography>\n\n {loading ? (\n <Typography variant=\"omega\" textColor=\"neutral500\">Loading validation rules...</Typography>\n ) : rules.length === 0 ? (\n <Alert variant=\"info\" title=\"No Rules Defined\">\n Click \"Create New Rule\" above to add your first cross-component quota rule.\n </Alert>\n ) : (\n <Table colCount={7} rowCount={rules.length + 1}>\n <Thead>\n <Tr>\n <Th><Typography variant=\"sigma\">RULE NAME & TARGET FIELD</Typography></Th>\n <Th><Typography variant=\"sigma\">COLLECTION SCOPE</Typography></Th>\n <Th><Typography variant=\"sigma\">QUOTA CONSTRAINTS</Typography></Th>\n <Th><Typography variant=\"sigma\">RICH TEXT / HTML</Typography></Th>\n <Th><Typography variant=\"sigma\">EXCEEDED ACTION</Typography></Th>\n <Th><Typography variant=\"sigma\">STATUS</Typography></Th>\n <Th style={{ textAlign: 'right' }}><Typography variant=\"sigma\">ACTIONS</Typography></Th>\n </Tr>\n </Thead>\n <Tbody>\n {rules.map((rule) => {\n const quotaKeys = Object.entries(rule.quotas || {});\n const isGlobal = !rule.collectionUid || rule.collectionUid === '*';\n const collectionName = !isGlobal\n ? (availableCollections.find((c) => c.uid === rule.collectionUid)?.displayName || rule.collectionUid)\n : 'All Collections (Global)';\n\n return (\n <Tr key={rule.id}>\n <Td style={{ minWidth: 200 }}>\n <Typography variant=\"omega\" fontWeight=\"bold\" textColor=\"neutral800\" display=\"block\">\n {rule.name}\n </Typography>\n <Typography variant=\"pi\" textColor=\"neutral500\">\n Target: <code>{rule.targetField}</code>\n </Typography>\n </Td>\n <Td style={{ minWidth: 160 }}>\n <Badge\n backgroundColor={isGlobal ? 'neutral150' : 'primary100'}\n textColor={isGlobal ? 'neutral700' : 'primary700'}\n >\n {collectionName}\n </Badge>\n </Td>\n <Td style={{ minWidth: 180 }}>\n <Flex gap={1} wrap=\"wrap\">\n {quotaKeys.map(([optVal, maxVal]) => (\n <Badge key={optVal} backgroundColor=\"neutral100\" textColor=\"primary700\">\n <strong>{optVal.toUpperCase()}</strong>: max {maxVal}\n </Badge>\n ))}\n </Flex>\n </Td>\n <Td style={{ minWidth: 140 }}>\n {rule.includeRichTextHtml ? (\n <Badge backgroundColor=\"success100\" textColor=\"success700\">\n HTML & Rich Text Active\n </Badge>\n ) : (\n <Badge backgroundColor=\"neutral150\" textColor=\"neutral600\">\n Structured Only\n </Badge>\n )}\n </Td>\n <Td style={{ minWidth: 110 }}>\n <Badge\n backgroundColor={rule.exceededAction === 'hide' ? 'secondary100' : 'warning100'}\n textColor={rule.exceededAction === 'hide' ? 'secondary700' : 'warning700'}\n >\n {rule.exceededAction.toUpperCase()}\n </Badge>\n </Td>\n <Td style={{ minWidth: 100 }}>\n <Button\n variant={rule.enabled ? 'success-light' : 'tertiary'}\n size=\"S\"\n onClick={() => handleToggle(rule)}\n >\n {rule.enabled ? '● ACTIVE' : '○ DISABLED'}\n </Button>\n </Td>\n <Td style={{ textAlign: 'right', minWidth: 260 }}>\n <Flex gap={2} justifyContent=\"flex-end\">\n <Button\n variant=\"secondary\"\n size=\"S\"\n startIcon={<Search />}\n onClick={() => handleScanRule(rule)}\n loading={scanningRuleId === rule.id}\n >\n Scan Violations\n </Button>\n <Button\n variant=\"tertiary\"\n size=\"S\"\n onClick={() => openEditModal(rule)}\n >\n Edit\n </Button>\n <Button\n variant=\"danger-light\"\n size=\"S\"\n startIcon={<Trash />}\n onClick={() => handleDelete(rule.id, rule.name)}\n >\n Delete\n </Button>\n </Flex>\n </Td>\n </Tr>\n );\n })}\n </Tbody>\n </Table>\n )}\n </Box>\n\n {/* Create / Edit Rule Modal */}\n {modalOpen && (\n <Box\n style={{\n position: 'fixed',\n inset: 0,\n backgroundColor: 'rgba(33, 33, 52, 0.65)',\n zIndex: 9999,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Box\n background=\"neutral0\"\n hasRadius\n shadow=\"popupShadow\"\n padding={6}\n style={{ width: '90%', maxWidth: 640, maxHeight: '90vh', overflowY: 'auto' }}\n >\n <Typography variant=\"delta\" tag=\"h2\" fontWeight=\"bold\" marginBottom={1}>\n {editingRule ? 'Edit Validation Rule' : 'Create New Validation Rule'}\n </Typography>\n <Typography variant=\"pi\" textColor=\"neutral600\" marginBottom={4} style={{ display: 'block' }}>\n Configure collection-scoped quotas. Rules apply consistently to target properties and optionally scan rich text HTML content.\n </Typography>\n\n <Flex direction=\"column\" gap={4} alignItems=\"stretch\">\n <TextInput\n label=\"Rule Name\"\n placeholder=\"e.g. SEO Single H1 Page Rule\"\n value={formName}\n onChange={(e: any) => setFormName(e.target.value)}\n required\n />\n\n <SingleSelect\n label=\"Target Collection Type\"\n value={formCollectionUid}\n onChange={(val: any) => setFormCollectionUid(val)}\n hint=\"Scope this rule to a specific collection type, or select All Collections.\"\n >\n <SingleSelectOption value=\"*\">* (All Collections / Global)</SingleSelectOption>\n {availableCollections.map((c) => (\n <SingleSelectOption key={c.uid} value={c.uid}>\n {c.displayName} ({c.uid})\n </SingleSelectOption>\n ))}\n </SingleSelect>\n\n <TextInput\n label=\"Target Field / Component Property Path\"\n placeholder=\"e.g. headingLevel, titleStyle, or st-featured.titleHeadingStyle.titleStyle\"\n value={formField}\n onChange={(e: any) => setFormField(e.target.value)}\n hint=\"Attribute name or dot-notated hierarchy (e.g. 'titleStyle' or 'st-featured.titleHeadingStyle.titleStyle').\"\n required\n />\n\n <Box padding={3} background=\"neutral100\" hasRadius style={{ border: '1px solid #e0e0e0' }}>\n <Checkbox\n checked={formIncludeRichText}\n onCheckedChange={(checked: boolean) => setFormIncludeRichText(checked)}\n >\n <Box marginLeft={2}>\n <Typography variant=\"omega\" fontWeight=\"bold\">\n Scan & Combine Rich Text / CKEditor HTML Headings\n </Typography>\n <Typography variant=\"pi\" textColor=\"neutral600\" display=\"block\">\n When enabled, any <h1>..<h6> tags, Markdown headings (#), or Strapi Blocks inside rich text fields are counted together with structured component choices towards the quota.\n </Typography>\n </Box>\n </Checkbox>\n </Box>\n\n <Textarea\n label=\"Rule Description\"\n placeholder=\"Briefly explain what this constraint accomplishes...\"\n value={formDesc}\n onChange={(e: any) => setFormDesc(e.target.value)}\n />\n\n <SingleSelect\n label=\"When Quota Is Exceeded in Another Component\"\n value={formAction}\n onChange={(val: any) => setFormAction(val as 'disable' | 'hide')}\n >\n <SingleSelectOption value=\"disable\">\n Disable Option with Usage Badge [(1/1 used)]\n </SingleSelectOption>\n <SingleSelectOption value=\"hide\">\n Hide Option completely from dropdown menu\n </SingleSelectOption>\n </SingleSelect>\n\n {/* Quotas Editor */}\n <Box\n padding={4}\n background=\"neutral100\"\n hasRadius\n style={{ border: '1px solid #e0e0e0' }}\n >\n <Flex justifyContent=\"space-between\" alignItems=\"center\" marginBottom={3}>\n <Box>\n <Typography variant=\"pi\" fontWeight=\"bold\" textColor=\"neutral800\">\n Option Quotas (Max Allowed per Entry)\n </Typography>\n <Typography variant=\"pi\" textColor=\"neutral600\" style={{ display: 'block' }}>\n Options left unlisted will have unlimited usage.\n </Typography>\n </Box>\n <Button\n variant=\"secondary\"\n size=\"S\"\n startIcon={<Plus />}\n onClick={handleAddQuotaRow}\n >\n Add Option\n </Button>\n </Flex>\n\n <Flex direction=\"column\" gap={2}>\n {formQuotas.map((q, idx) => (\n <Flex key={idx} gap={2} alignItems=\"center\">\n <Box style={{ flex: 2 }}>\n <TextInput\n placeholder=\"Option key (e.g. h1)\"\n value={q.key}\n onChange={(e: any) => handleQuotaChange(idx, 'key', e.target.value)}\n />\n </Box>\n <Box style={{ flex: 1 }}>\n <TextInput\n type=\"number\"\n placeholder=\"Max\"\n value={String(q.maxAllowed)}\n onChange={(e: any) => handleQuotaChange(idx, 'maxAllowed', e.target.value)}\n />\n </Box>\n <Button\n variant=\"ghost\"\n size=\"S\"\n onClick={() => handleRemoveQuotaRow(idx)}\n disabled={formQuotas.length <= 1}\n >\n ✕\n </Button>\n </Flex>\n ))}\n </Flex>\n </Box>\n </Flex>\n\n {/* Modal Actions */}\n <Flex justifyContent=\"flex-end\" gap={2} marginTop={6}>\n <Button\n variant=\"tertiary\"\n onClick={() => setModalOpen(false)}\n disabled={saving}\n >\n Cancel\n </Button>\n <Button\n variant=\"default\"\n startIcon={<Check />}\n onClick={handleSave}\n loading={saving}\n >\n Save Rule\n </Button>\n </Flex>\n </Box>\n </Box>\n )}\n\n {/* Scan Results Modal */}\n {scanModalOpen && scanReport && (\n <Box\n style={{\n position: 'fixed',\n inset: 0,\n backgroundColor: 'rgba(33, 33, 52, 0.65)',\n zIndex: 9999,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Box\n background=\"neutral0\"\n hasRadius\n shadow=\"popupShadow\"\n padding={6}\n style={{ width: '90%', maxWidth: 780, maxHeight: '85vh', overflowY: 'auto' }}\n >\n <Flex justifyContent=\"space-between\" alignItems=\"center\" marginBottom={4}>\n <Box>\n <Typography variant=\"beta\" tag=\"h2\" fontWeight=\"bold\">\n Rule Violation Audit: {scanReport.rule?.name}\n </Typography>\n <Typography variant=\"pi\" textColor=\"neutral600\">\n Target Field: <code>{scanReport.rule?.targetField}</code> • Total Entries Scanned: {scanReport.totalScanned}\n </Typography>\n </Box>\n <Badge\n backgroundColor={scanReport.totalViolations > 0 ? 'danger100' : 'success100'}\n textColor={scanReport.totalViolations > 0 ? 'danger700' : 'success700'}\n size=\"M\"\n >\n {scanReport.totalViolations > 0\n ? `${scanReport.totalViolations} VIOLATION(S) FOUND`\n : '100% COMPLIANT'}\n </Badge>\n </Flex>\n\n {scanReport.totalViolations === 0 ? (\n <Alert variant=\"success\" title=\"No Pre-Existing Violations Detected\" marginBottom={4}>\n All existing documents across your collections adhere strictly to the configured quota limits.\n </Alert>\n ) : (\n <Box>\n <Alert variant=\"danger\" title=\"Legacy Records Found Exceeding Quota\" marginBottom={4}>\n The following {scanReport.totalViolations} record(s) contain more instances of restricted options than currently permitted by this rule.\n </Alert>\n\n <Table colCount={4} rowCount={scanReport.violations.length + 1}>\n <Thead>\n <Tr>\n <Th><Typography variant=\"sigma\">CONTENT TYPE & ENTRY</Typography></Th>\n <Th><Typography variant=\"sigma\">RESTRICTED OPTION</Typography></Th>\n <Th><Typography variant=\"sigma\">COUNT VS LIMIT</Typography></Th>\n <Th style={{ textAlign: 'right' }}><Typography variant=\"sigma\">ACTION</Typography></Th>\n </Tr>\n </Thead>\n <Tbody>\n {scanReport.violations.map((v: any, idx: number) => (\n <Tr key={idx}>\n <Td>\n <Typography variant=\"omega\" fontWeight=\"bold\">\n {v.entryLabel}\n </Typography>\n <Typography variant=\"pi\" textColor=\"neutral500\" display=\"block\">\n Collection: {v.collectionDisplayName}\n </Typography>\n {v.detail && (\n <Typography variant=\"pi\" textColor=\"neutral600\" display=\"block\" style={{ fontSize: 11 }}>\n Breakdown: {v.detail}\n </Typography>\n )}\n </Td>\n <Td>\n <Badge backgroundColor=\"warning100\" textColor=\"warning700\">\n {v.violatingOption}\n </Badge>\n </Td>\n <Td>\n <Typography variant=\"omega\" textColor=\"danger600\" fontWeight=\"bold\">\n {v.foundCount} used\n </Typography>\n <Typography variant=\"pi\" textColor=\"neutral500\">\n {' '}(Allowed max: {v.maxAllowed})\n </Typography>\n </Td>\n <Td style={{ textAlign: 'right' }}>\n <Button\n variant=\"secondary\"\n size=\"S\"\n onClick={() => window.open(v.editUrl, '_blank')}\n >\n Open in Editor ↗\n </Button>\n </Td>\n </Tr>\n ))}\n </Tbody>\n </Table>\n </Box>\n )}\n\n <Flex justifyContent=\"space-between\" alignItems=\"center\" marginTop={6}>\n {scanReport.totalViolations > 0 ? (\n <Button\n variant=\"secondary\"\n startIcon={<Download />}\n onClick={async () => {\n try {\n const res = await get(`/bulk-updater/csv/export-violations/${scanReport.rule.id}`);\n const csvStr = res.data?.csvContent || (typeof res.data === 'string' ? res.data : '');\n if (!csvStr) {\n toggleNotification({\n type: 'warning',\n message: 'No violation data to export',\n });\n return;\n }\n const safeContent = csvStr.startsWith('\\uFEFF') ? csvStr : '\\uFEFF' + csvStr;\n const blob = new Blob([safeContent], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.setAttribute('download', `violations_${scanReport.rule.id}.csv`);\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n toggleNotification({\n type: 'success',\n message: 'Violations exported to CSV successfully',\n });\n } catch (e: any) {\n toggleNotification({\n type: 'danger',\n message: e.message || 'Failed exporting violations CSV',\n });\n }\n }}\n >\n Export Violations CSV\n </Button>\n ) : (\n <Box />\n )}\n <Button variant=\"default\" onClick={() => setScanModalOpen(false)}>\n Close Report\n </Button>\n </Flex>\n </Box>\n </Box>\n )}\n </Box>\n );\n}\n"],"names":[],"mappings":"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"}
|
package/dist/admin/index.js
CHANGED
|
@@ -13,7 +13,7 @@ const index = {
|
|
|
13
13
|
id: `${PLUGIN_ID}.plugin.name`,
|
|
14
14
|
defaultMessage: "Bulk Attribute Updater"
|
|
15
15
|
},
|
|
16
|
-
Component: () => Promise.resolve().then(() => require("../_chunks/BulkUpdater-
|
|
16
|
+
Component: () => Promise.resolve().then(() => require("../_chunks/BulkUpdater-BosdSbGF.js")),
|
|
17
17
|
permissions: [],
|
|
18
18
|
position: 3
|
|
19
19
|
});
|
|
@@ -24,7 +24,7 @@ const index = {
|
|
|
24
24
|
defaultMessage: "Bulk Updater Settings"
|
|
25
25
|
},
|
|
26
26
|
to: `${PLUGIN_ID}`,
|
|
27
|
-
Component: () => Promise.resolve().then(() => require("../_chunks/SettingsPage-
|
|
27
|
+
Component: () => Promise.resolve().then(() => require("../_chunks/SettingsPage-B6Ia7SVS.js")),
|
|
28
28
|
permissions: []
|
|
29
29
|
});
|
|
30
30
|
app.registerPlugin({
|
|
@@ -44,7 +44,7 @@ const index = {
|
|
|
44
44
|
defaultMessage: "Dropdown with dynamic cross-component usage quota enforcement"
|
|
45
45
|
},
|
|
46
46
|
components: {
|
|
47
|
-
Input: async () => Promise.resolve().then(() => require("../_chunks/ScopedQuotaSelectInput-
|
|
47
|
+
Input: async () => Promise.resolve().then(() => require("../_chunks/ScopedQuotaSelectInput-CyGlYa9x.js"))
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
},
|
package/dist/admin/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ const index = {
|
|
|
10
10
|
id: `${PLUGIN_ID}.plugin.name`,
|
|
11
11
|
defaultMessage: "Bulk Attribute Updater"
|
|
12
12
|
},
|
|
13
|
-
Component: () => import("../_chunks/BulkUpdater-
|
|
13
|
+
Component: () => import("../_chunks/BulkUpdater-DUrmamj-.mjs"),
|
|
14
14
|
permissions: [],
|
|
15
15
|
position: 3
|
|
16
16
|
});
|
|
@@ -21,7 +21,7 @@ const index = {
|
|
|
21
21
|
defaultMessage: "Bulk Updater Settings"
|
|
22
22
|
},
|
|
23
23
|
to: `${PLUGIN_ID}`,
|
|
24
|
-
Component: () => import("../_chunks/SettingsPage-
|
|
24
|
+
Component: () => import("../_chunks/SettingsPage-Bdse5MP4.mjs"),
|
|
25
25
|
permissions: []
|
|
26
26
|
});
|
|
27
27
|
app.registerPlugin({
|
|
@@ -41,7 +41,7 @@ const index = {
|
|
|
41
41
|
defaultMessage: "Dropdown with dynamic cross-component usage quota enforcement"
|
|
42
42
|
},
|
|
43
43
|
components: {
|
|
44
|
-
Input: async () => import("../_chunks/ScopedQuotaSelectInput-
|
|
44
|
+
Input: async () => import("../_chunks/ScopedQuotaSelectInput-Cy3eEj9-.mjs")
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
47
|
},
|
package/dist/server/index.js
CHANGED
|
@@ -3580,7 +3580,8 @@ const DEFAULT_RULES = [
|
|
|
3580
3580
|
{
|
|
3581
3581
|
id: "rule_seo_heading_h1",
|
|
3582
3582
|
name: "SEO Single H1 Page Rule",
|
|
3583
|
-
description: "Enforces that only one H1 heading tag can be selected across all dynamic zone heading blocks within an entry.",
|
|
3583
|
+
description: "Enforces that only one H1 heading tag can be selected across all dynamic zone heading blocks within an entry, combined with rich text HTML.",
|
|
3584
|
+
collectionUid: "*",
|
|
3584
3585
|
targetField: "headingLevel",
|
|
3585
3586
|
quotas: {
|
|
3586
3587
|
h1: 1,
|
|
@@ -3589,10 +3590,56 @@ const DEFAULT_RULES = [
|
|
|
3589
3590
|
exceededAction: "disable",
|
|
3590
3591
|
enabled: true,
|
|
3591
3592
|
scope: "entry",
|
|
3593
|
+
includeRichTextHtml: true,
|
|
3594
|
+
richTextFields: [],
|
|
3592
3595
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3593
3596
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3594
3597
|
}
|
|
3595
3598
|
];
|
|
3599
|
+
function extractHeadingsFromRichText(content) {
|
|
3600
|
+
const counts = { h1: 0, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0 };
|
|
3601
|
+
if (!content) return counts;
|
|
3602
|
+
if (Array.isArray(content)) {
|
|
3603
|
+
for (const node of content) {
|
|
3604
|
+
if (node && typeof node === "object") {
|
|
3605
|
+
if (node.type === "heading" && typeof node.level === "number") {
|
|
3606
|
+
const tag = `h${node.level}`;
|
|
3607
|
+
if (counts[tag] !== void 0) counts[tag]++;
|
|
3608
|
+
}
|
|
3609
|
+
if (Array.isArray(node.children)) {
|
|
3610
|
+
const sub = extractHeadingsFromRichText(node.children);
|
|
3611
|
+
for (const k of Object.keys(counts)) counts[k] += sub[k];
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
return counts;
|
|
3616
|
+
}
|
|
3617
|
+
if (typeof content === "string") {
|
|
3618
|
+
for (let level = 1; level <= 6; level++) {
|
|
3619
|
+
const tag = `h${level}`;
|
|
3620
|
+
const htmlMatches = content.match(new RegExp(`<${tag}\\b[^>]*>`, "gi"));
|
|
3621
|
+
if (htmlMatches) {
|
|
3622
|
+
counts[tag] += htmlMatches.length;
|
|
3623
|
+
}
|
|
3624
|
+
const mdRegex = new RegExp(`(?:^|\\n)${"#".repeat(level)}\\s+[^\\n]+`, "g");
|
|
3625
|
+
const mdMatches = content.match(mdRegex);
|
|
3626
|
+
if (mdMatches) {
|
|
3627
|
+
counts[tag] += mdMatches.length;
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
return counts;
|
|
3632
|
+
}
|
|
3633
|
+
function matchesPath(normalizedPath, leafKey, targetField) {
|
|
3634
|
+
if (!targetField) return false;
|
|
3635
|
+
const pLower = (normalizedPath || "").toLowerCase().trim();
|
|
3636
|
+
const kLower = (leafKey || "").toLowerCase().trim();
|
|
3637
|
+
const tLower = targetField.toLowerCase().trim();
|
|
3638
|
+
if (!tLower.includes(".")) {
|
|
3639
|
+
return kLower === tLower || pLower === tLower || pLower.endsWith(`.${tLower}`);
|
|
3640
|
+
}
|
|
3641
|
+
return pLower === tLower || pLower.endsWith(`.${tLower}`) || pLower.endsWith(tLower);
|
|
3642
|
+
}
|
|
3596
3643
|
const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
3597
3644
|
async getStore() {
|
|
3598
3645
|
return strapi2.store({
|
|
@@ -3621,6 +3668,9 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
3621
3668
|
savedRule = {
|
|
3622
3669
|
...rules[index2],
|
|
3623
3670
|
...ruleData,
|
|
3671
|
+
collectionUid: ruleData.collectionUid !== void 0 ? ruleData.collectionUid : rules[index2].collectionUid || "*",
|
|
3672
|
+
includeRichTextHtml: ruleData.includeRichTextHtml !== void 0 ? Boolean(ruleData.includeRichTextHtml) : Boolean(rules[index2].includeRichTextHtml),
|
|
3673
|
+
richTextFields: Array.isArray(ruleData.richTextFields) ? ruleData.richTextFields : rules[index2].richTextFields || [],
|
|
3624
3674
|
updatedAt: now
|
|
3625
3675
|
};
|
|
3626
3676
|
rules[index2] = savedRule;
|
|
@@ -3629,11 +3679,14 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
3629
3679
|
id: ruleData.id,
|
|
3630
3680
|
name: ruleData.name || "New Rule",
|
|
3631
3681
|
description: ruleData.description || "",
|
|
3682
|
+
collectionUid: ruleData.collectionUid || "*",
|
|
3632
3683
|
targetField: ruleData.targetField || "headingLevel",
|
|
3633
3684
|
quotas: ruleData.quotas || { h1: 1 },
|
|
3634
3685
|
exceededAction: ruleData.exceededAction || "disable",
|
|
3635
3686
|
enabled: ruleData.enabled ?? true,
|
|
3636
3687
|
scope: ruleData.scope || "entry",
|
|
3688
|
+
includeRichTextHtml: Boolean(ruleData.includeRichTextHtml),
|
|
3689
|
+
richTextFields: Array.isArray(ruleData.richTextFields) ? ruleData.richTextFields : [],
|
|
3637
3690
|
createdAt: now,
|
|
3638
3691
|
updatedAt: now
|
|
3639
3692
|
};
|
|
@@ -3645,11 +3698,14 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
3645
3698
|
id,
|
|
3646
3699
|
name: ruleData.name || "Custom Quota Rule",
|
|
3647
3700
|
description: ruleData.description || "",
|
|
3701
|
+
collectionUid: ruleData.collectionUid || "*",
|
|
3648
3702
|
targetField: ruleData.targetField || "headingLevel",
|
|
3649
3703
|
quotas: ruleData.quotas || { h1: 1 },
|
|
3650
3704
|
exceededAction: ruleData.exceededAction || "disable",
|
|
3651
3705
|
enabled: ruleData.enabled ?? true,
|
|
3652
3706
|
scope: ruleData.scope || "entry",
|
|
3707
|
+
includeRichTextHtml: Boolean(ruleData.includeRichTextHtml),
|
|
3708
|
+
richTextFields: Array.isArray(ruleData.richTextFields) ? ruleData.richTextFields : [],
|
|
3653
3709
|
createdAt: now,
|
|
3654
3710
|
updatedAt: now
|
|
3655
3711
|
};
|
|
@@ -3693,21 +3749,34 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
3693
3749
|
let totalScanned = 0;
|
|
3694
3750
|
const allContentTypes = strapi2.contentTypes || {};
|
|
3695
3751
|
const allComponents = strapi2.components || {};
|
|
3752
|
+
function componentHasField(compUid, targetField2) {
|
|
3753
|
+
const comp = allComponents[compUid];
|
|
3754
|
+
if (!comp?.attributes) return false;
|
|
3755
|
+
for (const [attrName, attrDef] of Object.entries(comp.attributes)) {
|
|
3756
|
+
if (matchesPath(attrName, attrName, targetField2)) return true;
|
|
3757
|
+
if (attrDef.type === "component" && attrDef.component) {
|
|
3758
|
+
if (componentHasField(attrDef.component, targetField2)) return true;
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
return false;
|
|
3762
|
+
}
|
|
3696
3763
|
function schemaHasField(ctSchema) {
|
|
3697
3764
|
if (!ctSchema || !ctSchema.attributes) return { hasField: false, populateAttrs: {} };
|
|
3698
3765
|
const rawAttrs = ctSchema.attributes;
|
|
3699
3766
|
const populateAttrs = {};
|
|
3700
|
-
let hasField =
|
|
3767
|
+
let hasField = false;
|
|
3701
3768
|
for (const [attrKey, attrDef] of Object.entries(rawAttrs)) {
|
|
3769
|
+
if (matchesPath(attrKey, attrKey, targetField)) {
|
|
3770
|
+
hasField = true;
|
|
3771
|
+
}
|
|
3702
3772
|
if (attrDef.type === "component" && attrDef.component) {
|
|
3703
|
-
|
|
3704
|
-
if (comp?.attributes?.[targetField]) {
|
|
3773
|
+
if (componentHasField(attrDef.component, targetField)) {
|
|
3705
3774
|
hasField = true;
|
|
3706
3775
|
populateAttrs[attrKey] = true;
|
|
3707
3776
|
}
|
|
3708
3777
|
} else if (attrDef.type === "dynamiczone" && Array.isArray(attrDef.components)) {
|
|
3709
3778
|
const matchingComp = attrDef.components.some(
|
|
3710
|
-
(compUid) =>
|
|
3779
|
+
(compUid) => componentHasField(compUid, targetField)
|
|
3711
3780
|
);
|
|
3712
3781
|
if (matchingComp) {
|
|
3713
3782
|
hasField = true;
|
|
@@ -3717,19 +3786,23 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
3717
3786
|
}
|
|
3718
3787
|
return { hasField, populateAttrs };
|
|
3719
3788
|
}
|
|
3720
|
-
const candidateUids = Object.keys(allContentTypes).filter(
|
|
3721
|
-
(uid
|
|
3722
|
-
|
|
3789
|
+
const candidateUids = Object.keys(allContentTypes).filter((uid) => {
|
|
3790
|
+
if (!uid.startsWith("api::") || uid.includes("bulk-updater")) return false;
|
|
3791
|
+
if (rule.collectionUid && rule.collectionUid !== "*" && rule.collectionUid !== uid) {
|
|
3792
|
+
return false;
|
|
3793
|
+
}
|
|
3794
|
+
return true;
|
|
3795
|
+
});
|
|
3723
3796
|
const relevantCollections = [];
|
|
3724
3797
|
for (const uid of candidateUids) {
|
|
3725
3798
|
const ct = allContentTypes[uid];
|
|
3726
3799
|
if (!ct) continue;
|
|
3727
3800
|
const { hasField, populateAttrs } = schemaHasField(ct);
|
|
3728
|
-
if (hasField) {
|
|
3801
|
+
if (hasField || rule.collectionUid && rule.collectionUid === uid) {
|
|
3729
3802
|
relevantCollections.push({
|
|
3730
3803
|
uid,
|
|
3731
3804
|
displayName: ct.info?.displayName || uid,
|
|
3732
|
-
populateOption: Object.keys(populateAttrs).length > 0 ? populateAttrs :
|
|
3805
|
+
populateOption: Object.keys(populateAttrs).length > 0 ? populateAttrs : "*"
|
|
3733
3806
|
});
|
|
3734
3807
|
}
|
|
3735
3808
|
}
|
|
@@ -3746,35 +3819,69 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
3746
3819
|
});
|
|
3747
3820
|
if (!entries || entries.length === 0) break;
|
|
3748
3821
|
for (const entry of entries) {
|
|
3749
|
-
let traverse = function(obj) {
|
|
3822
|
+
let traverse = function(obj, currentPath = "") {
|
|
3750
3823
|
if (!obj || typeof obj !== "object") return;
|
|
3751
3824
|
for (const key of Object.keys(obj)) {
|
|
3752
3825
|
const val = obj[key];
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3826
|
+
const newPath = currentPath ? `${currentPath}.${key}` : key;
|
|
3827
|
+
const normalizedPath = newPath.replace(/\.\d+/g, "");
|
|
3828
|
+
if (matchesPath(normalizedPath, key, targetField) && typeof val === "string" && val.trim()) {
|
|
3829
|
+
const lower = val.toLowerCase().trim();
|
|
3830
|
+
structuredCounts[lower] = (structuredCounts[lower] || 0) + 1;
|
|
3831
|
+
}
|
|
3832
|
+
if (rule.includeRichTextHtml) {
|
|
3833
|
+
if (typeof val === "string" && (val.includes("<h") || val.includes("#"))) {
|
|
3834
|
+
const headings = extractHeadingsFromRichText(val);
|
|
3835
|
+
for (const [tag, count] of Object.entries(headings)) {
|
|
3836
|
+
if (count > 0) {
|
|
3837
|
+
richTextCounts[tag] = (richTextCounts[tag] || 0) + count;
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
} else if (Array.isArray(val) && val.length > 0 && typeof val[0] === "object" && val[0]?.type) {
|
|
3841
|
+
const headings = extractHeadingsFromRichText(val);
|
|
3842
|
+
for (const [tag, count] of Object.entries(headings)) {
|
|
3843
|
+
if (count > 0) {
|
|
3844
|
+
richTextCounts[tag] = (richTextCounts[tag] || 0) + count;
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
continue;
|
|
3848
|
+
}
|
|
3756
3849
|
}
|
|
3757
3850
|
if (typeof val === "object" && val !== null) {
|
|
3758
|
-
traverse(val);
|
|
3851
|
+
traverse(val, newPath);
|
|
3759
3852
|
}
|
|
3760
3853
|
}
|
|
3761
3854
|
};
|
|
3762
3855
|
totalScanned++;
|
|
3763
|
-
const
|
|
3856
|
+
const structuredCounts = {};
|
|
3857
|
+
const richTextCounts = { h1: 0, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0 };
|
|
3764
3858
|
traverse(entry);
|
|
3765
3859
|
for (const [optKey, max] of Object.entries(quotas)) {
|
|
3766
|
-
const lowerOpt = optKey.toLowerCase();
|
|
3767
|
-
const
|
|
3768
|
-
|
|
3860
|
+
const lowerOpt = optKey.toLowerCase().trim();
|
|
3861
|
+
const structCount = structuredCounts[lowerOpt] || 0;
|
|
3862
|
+
const rtCount = rule.includeRichTextHtml ? richTextCounts[lowerOpt] || 0 : 0;
|
|
3863
|
+
const combinedCount = structCount + rtCount;
|
|
3864
|
+
if (combinedCount > max) {
|
|
3769
3865
|
const entryLabel = entry.title || entry.name || entry.email || `Entry #${entry.documentId}`;
|
|
3866
|
+
let detail = "";
|
|
3867
|
+
if (rtCount > 0 && structCount > 0) {
|
|
3868
|
+
detail = `${structCount} structured + ${rtCount} in rich text HTML`;
|
|
3869
|
+
} else if (rtCount > 0) {
|
|
3870
|
+
detail = `${rtCount} in rich text HTML / Markdown`;
|
|
3871
|
+
} else {
|
|
3872
|
+
detail = `${structCount} in structured component`;
|
|
3873
|
+
}
|
|
3770
3874
|
violations.push({
|
|
3771
3875
|
uid,
|
|
3772
3876
|
collectionDisplayName: displayName,
|
|
3773
3877
|
documentId: entry.documentId,
|
|
3774
3878
|
entryLabel,
|
|
3775
3879
|
violatingOption: optKey.toUpperCase(),
|
|
3776
|
-
foundCount:
|
|
3880
|
+
foundCount: combinedCount,
|
|
3881
|
+
structuredCount: structCount,
|
|
3882
|
+
richTextCount: rtCount,
|
|
3777
3883
|
maxAllowed: max,
|
|
3884
|
+
detail,
|
|
3778
3885
|
editUrl: `/admin/content-manager/collection-types/${uid}/${entry.documentId}`
|
|
3779
3886
|
});
|
|
3780
3887
|
}
|
|
@@ -34049,17 +34156,37 @@ const index = {
|
|
|
34049
34156
|
if (["create", "update"].includes(context.action)) {
|
|
34050
34157
|
const data = params?.data;
|
|
34051
34158
|
if (data && typeof data === "object") {
|
|
34052
|
-
let scanDataTree = function(obj) {
|
|
34159
|
+
let scanDataTree = function(obj, currentPath = "") {
|
|
34053
34160
|
if (!obj || typeof obj !== "object") return;
|
|
34054
34161
|
for (const key of Object.keys(obj)) {
|
|
34055
34162
|
const val = obj[key];
|
|
34056
|
-
|
|
34057
|
-
|
|
34058
|
-
|
|
34059
|
-
|
|
34163
|
+
const newPath = currentPath ? `${currentPath}.${key}` : key;
|
|
34164
|
+
const normalizedPath = newPath.replace(/\.\d+/g, "");
|
|
34165
|
+
for (const rule of effectiveRules) {
|
|
34166
|
+
if (matchesPath(normalizedPath, key, rule.targetField) && typeof val === "string" && val.trim()) {
|
|
34167
|
+
const lowerVal = val.toLowerCase().trim();
|
|
34168
|
+
structuredCounts[rule.id] = structuredCounts[rule.id] || {};
|
|
34169
|
+
structuredCounts[rule.id][lowerVal] = (structuredCounts[rule.id][lowerVal] || 0) + 1;
|
|
34170
|
+
}
|
|
34171
|
+
}
|
|
34172
|
+
if (typeof val === "string" && (val.includes("<h") || val.includes("#"))) {
|
|
34173
|
+
const headings = extractHeadingsFromRichText(val);
|
|
34174
|
+
for (const [tag, count] of Object.entries(headings)) {
|
|
34175
|
+
if (count > 0) {
|
|
34176
|
+
richTextCounts[tag] = (richTextCounts[tag] || 0) + count;
|
|
34177
|
+
}
|
|
34178
|
+
}
|
|
34179
|
+
} else if (Array.isArray(val) && val.length > 0 && typeof val[0] === "object" && val[0]?.type) {
|
|
34180
|
+
const headings = extractHeadingsFromRichText(val);
|
|
34181
|
+
for (const [tag, count] of Object.entries(headings)) {
|
|
34182
|
+
if (count > 0) {
|
|
34183
|
+
richTextCounts[tag] = (richTextCounts[tag] || 0) + count;
|
|
34184
|
+
}
|
|
34185
|
+
}
|
|
34186
|
+
continue;
|
|
34060
34187
|
}
|
|
34061
34188
|
if (typeof val === "object" && val !== null) {
|
|
34062
|
-
scanDataTree(val);
|
|
34189
|
+
scanDataTree(val, newPath);
|
|
34063
34190
|
}
|
|
34064
34191
|
}
|
|
34065
34192
|
};
|
|
@@ -34073,20 +34200,22 @@ const index = {
|
|
|
34073
34200
|
} catch (err) {
|
|
34074
34201
|
strapi2.log.warn("[bulk-updater] Failed to fetch active UI validation rules:", err);
|
|
34075
34202
|
}
|
|
34076
|
-
const
|
|
34077
|
-
|
|
34078
|
-
|
|
34079
|
-
|
|
34080
|
-
|
|
34081
|
-
|
|
34082
|
-
}
|
|
34083
|
-
|
|
34084
|
-
|
|
34203
|
+
const effectiveRules = [
|
|
34204
|
+
...activeUiRules.filter((r) => !r.collectionUid || r.collectionUid === "*" || r.collectionUid === context.uid).map((r) => ({
|
|
34205
|
+
id: r.id || r.targetField,
|
|
34206
|
+
name: r.name || r.targetField,
|
|
34207
|
+
collectionUid: r.collectionUid || "*",
|
|
34208
|
+
targetField: r.targetField,
|
|
34209
|
+
quotas: r.quotas || {},
|
|
34210
|
+
includeRichTextHtml: Boolean(r.includeRichTextHtml),
|
|
34211
|
+
richTextFields: r.richTextFields || []
|
|
34212
|
+
}))
|
|
34213
|
+
];
|
|
34085
34214
|
const allComponents = strapi2.components || {};
|
|
34086
34215
|
for (const comp of Object.values(allComponents)) {
|
|
34087
34216
|
const attrs = comp.attributes || {};
|
|
34088
34217
|
for (const [attrName, attrDef] of Object.entries(attrs)) {
|
|
34089
|
-
if (attrDef.type === "customField" && (attrDef.customField === "plugin::bulk-updater.scoped-select" || attrDef.customField === "global::scoped-select") && !
|
|
34218
|
+
if (attrDef.type === "customField" && (attrDef.customField === "plugin::bulk-updater.scoped-select" || attrDef.customField === "global::scoped-select") && !effectiveRules.some((r) => matchesPath(attrName, attrName, r.targetField))) {
|
|
34090
34219
|
const quotas = attrDef.options?.quotas || {};
|
|
34091
34220
|
const items = attrDef.options?.items || [];
|
|
34092
34221
|
const itemQuotas = {};
|
|
@@ -34095,35 +34224,33 @@ const index = {
|
|
|
34095
34224
|
itemQuotas[item.value] = item.maxAllowed;
|
|
34096
34225
|
}
|
|
34097
34226
|
}
|
|
34098
|
-
|
|
34099
|
-
|
|
34100
|
-
|
|
34101
|
-
|
|
34102
|
-
|
|
34103
|
-
|
|
34104
|
-
|
|
34105
|
-
|
|
34106
|
-
|
|
34107
|
-
|
|
34227
|
+
effectiveRules.push({
|
|
34228
|
+
id: `schema_${attrName}`,
|
|
34229
|
+
name: `${attrName} quota rule`,
|
|
34230
|
+
collectionUid: "*",
|
|
34231
|
+
targetField: attrName,
|
|
34232
|
+
quotas: {
|
|
34233
|
+
h1: 1,
|
|
34234
|
+
h2: 3,
|
|
34235
|
+
...itemQuotas,
|
|
34236
|
+
...quotas
|
|
34237
|
+
},
|
|
34238
|
+
includeRichTextHtml: true,
|
|
34239
|
+
richTextFields: []
|
|
34240
|
+
});
|
|
34108
34241
|
}
|
|
34109
34242
|
}
|
|
34110
34243
|
}
|
|
34111
|
-
|
|
34112
|
-
|
|
34113
|
-
quotas: { h1: 1, h2: 3 },
|
|
34114
|
-
ruleName: "Default SEO Heading Rule"
|
|
34115
|
-
};
|
|
34116
|
-
}
|
|
34117
|
-
const counts = {};
|
|
34244
|
+
const structuredCounts = {};
|
|
34245
|
+
const richTextCounts = { h1: 0, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0 };
|
|
34118
34246
|
let dataToScan = data;
|
|
34119
34247
|
if (context.action === "update" && (params?.documentId || params?.where?.documentId)) {
|
|
34120
34248
|
const docId = params?.documentId || params?.where?.documentId;
|
|
34121
|
-
|
|
34122
|
-
if (hasQuotaFieldInPayload && context.uid) {
|
|
34249
|
+
if (context.uid && docId) {
|
|
34123
34250
|
try {
|
|
34124
|
-
const existing = await strapi2.
|
|
34125
|
-
documentId: docId,
|
|
34126
|
-
populate:
|
|
34251
|
+
const existing = await strapi2.db?.query?.(context.uid)?.findOne?.({
|
|
34252
|
+
where: { documentId: docId },
|
|
34253
|
+
populate: true
|
|
34127
34254
|
});
|
|
34128
34255
|
if (existing) {
|
|
34129
34256
|
dataToScan = { ...existing, ...data };
|
|
@@ -34133,14 +34260,23 @@ const index = {
|
|
|
34133
34260
|
}
|
|
34134
34261
|
}
|
|
34135
34262
|
scanDataTree(dataToScan);
|
|
34136
|
-
for (const
|
|
34137
|
-
|
|
34138
|
-
|
|
34139
|
-
const
|
|
34140
|
-
|
|
34141
|
-
|
|
34142
|
-
|
|
34143
|
-
|
|
34263
|
+
for (const rule of effectiveRules) {
|
|
34264
|
+
const ruleCounts = structuredCounts[rule.id] || {};
|
|
34265
|
+
for (const [quotaKey, maxAllowed] of Object.entries(rule.quotas || {})) {
|
|
34266
|
+
const lowerVal = quotaKey.toLowerCase().trim();
|
|
34267
|
+
const structCount = ruleCounts[lowerVal] || 0;
|
|
34268
|
+
const rtCount = rule.includeRichTextHtml ? richTextCounts[lowerVal] || 0 : 0;
|
|
34269
|
+
const totalFound = structCount + rtCount;
|
|
34270
|
+
if (totalFound > maxAllowed) {
|
|
34271
|
+
let detailMsg = `Option "${quotaKey.toUpperCase()}" cannot appear more than ${maxAllowed} time(s) across this entry.`;
|
|
34272
|
+
if (rtCount > 0 && structCount > 0) {
|
|
34273
|
+
detailMsg += ` Found ${totalFound} occurrences combined: ${structCount} in structured component ('${rule.targetField}') and ${rtCount} in rich text HTML / Markdown.`;
|
|
34274
|
+
} else if (rtCount > 0) {
|
|
34275
|
+
detailMsg += ` Found ${totalFound} in rich text HTML / Markdown content.`;
|
|
34276
|
+
} else {
|
|
34277
|
+
detailMsg += ` Found ${totalFound} in structured field '${rule.targetField}'.`;
|
|
34278
|
+
}
|
|
34279
|
+
throw new ApplicationError(`Validation Error (${rule.name}): ${detailMsg}`);
|
|
34144
34280
|
}
|
|
34145
34281
|
}
|
|
34146
34282
|
}
|