@purposeinplay/payload-ai-translate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +714 -0
- package/dist/alerts-collection.d.ts +21 -0
- package/dist/alerts-collection.js +159 -0
- package/dist/api.d.ts +4 -0
- package/dist/api.js +918 -0
- package/dist/bulk-translate-batches-collection.d.ts +29 -0
- package/dist/bulk-translate-batches-collection.js +404 -0
- package/dist/bulk-translate-units-collection.d.ts +35 -0
- package/dist/bulk-translate-units-collection.js +310 -0
- package/dist/client/estimated-cost-cell.d.ts +6 -0
- package/dist/client/estimated-cost-cell.js +12 -0
- package/dist/client/excluded-fields-field.d.ts +45 -0
- package/dist/client/excluded-fields-field.js +553 -0
- package/dist/client/field-translate-button.d.ts +6 -0
- package/dist/client/field-translate-button.js +199 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.js +6 -0
- package/dist/client/lib/use-global-kill-switches.d.ts +20 -0
- package/dist/client/lib/use-global-kill-switches.js +58 -0
- package/dist/client/translate-button.d.ts +2 -0
- package/dist/client/translate-button.js +228 -0
- package/dist/client/translate-modal.d.ts +16 -0
- package/dist/client/translate-modal.js +549 -0
- package/dist/client/translation-progress.d.ts +10 -0
- package/dist/client/translation-progress.js +297 -0
- package/dist/components/TranslationNavGroup.d.ts +45 -0
- package/dist/components/TranslationNavGroup.js +104 -0
- package/dist/defaults.d.ts +11 -0
- package/dist/defaults.js +16 -0
- package/dist/endpoints/client-config.d.ts +44 -0
- package/dist/endpoints/client-config.js +145 -0
- package/dist/endpoints/estimate.d.ts +5 -0
- package/dist/endpoints/estimate.js +237 -0
- package/dist/endpoints/progress.d.ts +2 -0
- package/dist/endpoints/progress.js +314 -0
- package/dist/endpoints/translate.d.ts +11 -0
- package/dist/endpoints/translate.js +376 -0
- package/dist/endpoints/translation-hub/_helpers.d.ts +140 -0
- package/dist/endpoints/translation-hub/_helpers.js +297 -0
- package/dist/endpoints/translation-hub/active.d.ts +21 -0
- package/dist/endpoints/translation-hub/active.js +220 -0
- package/dist/endpoints/translation-hub/cancel.d.ts +22 -0
- package/dist/endpoints/translation-hub/cancel.js +233 -0
- package/dist/endpoints/translation-hub/enqueue.d.ts +70 -0
- package/dist/endpoints/translation-hub/enqueue.js +529 -0
- package/dist/endpoints/translation-hub/failures.d.ts +12 -0
- package/dist/endpoints/translation-hub/failures.js +67 -0
- package/dist/endpoints/translation-hub/force-reset.d.ts +20 -0
- package/dist/endpoints/translation-hub/force-reset.js +144 -0
- package/dist/endpoints/translation-hub/index.d.ts +21 -0
- package/dist/endpoints/translation-hub/index.js +20 -0
- package/dist/endpoints/translation-hub/list.d.ts +40 -0
- package/dist/endpoints/translation-hub/list.js +182 -0
- package/dist/endpoints/translation-hub/preflight.d.ts +19 -0
- package/dist/endpoints/translation-hub/preflight.js +141 -0
- package/dist/endpoints/translation-hub/retry-failed.d.ts +38 -0
- package/dist/endpoints/translation-hub/retry-failed.js +235 -0
- package/dist/endpoints/translation-hub/revert.d.ts +88 -0
- package/dist/endpoints/translation-hub/revert.js +405 -0
- package/dist/endpoints/translation-hub/status.d.ts +45 -0
- package/dist/endpoints/translation-hub/status.js +391 -0
- package/dist/endpoints/translation-hub/usage-summary.d.ts +114 -0
- package/dist/endpoints/translation-hub/usage-summary.js +481 -0
- package/dist/exports/client.d.ts +6 -0
- package/dist/exports/client.js +6 -0
- package/dist/exports/components.d.ts +6 -0
- package/dist/exports/components.js +5 -0
- package/dist/exports/index.d.ts +8 -0
- package/dist/exports/index.js +7 -0
- package/dist/exports/providers.d.ts +9 -0
- package/dist/exports/providers.js +5 -0
- package/dist/exports/views-client.d.ts +23 -0
- package/dist/exports/views-client.js +22 -0
- package/dist/exports/views.d.ts +30 -0
- package/dist/exports/views.js +29 -0
- package/dist/hooks/after-change-global.d.ts +4 -0
- package/dist/hooks/after-change-global.js +109 -0
- package/dist/hooks/after-change.d.ts +16 -0
- package/dist/hooks/after-change.js +205 -0
- package/dist/hooks/after-delete.d.ts +30 -0
- package/dist/hooks/after-delete.js +95 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/jobs-collection.d.ts +17 -0
- package/dist/jobs-collection.js +139 -0
- package/dist/lexical/classifier.d.ts +3 -0
- package/dist/lexical/classifier.js +108 -0
- package/dist/lexical/deserializer.d.ts +4 -0
- package/dist/lexical/deserializer.js +263 -0
- package/dist/lexical/placeholder-integrity.d.ts +6 -0
- package/dist/lexical/placeholder-integrity.js +21 -0
- package/dist/lexical/placeholders.d.ts +21 -0
- package/dist/lexical/placeholders.js +117 -0
- package/dist/lexical/serializer.d.ts +21 -0
- package/dist/lexical/serializer.js +233 -0
- package/dist/lexical/types.d.ts +32 -0
- package/dist/lexical/types.js +1 -0
- package/dist/lib/auth-diagnostics.d.ts +14 -0
- package/dist/lib/auth-diagnostics.js +19 -0
- package/dist/lib/batch-counts.d.ts +58 -0
- package/dist/lib/batch-counts.js +105 -0
- package/dist/lib/bulk-translate-migrations.d.ts +92 -0
- package/dist/lib/bulk-translate-migrations.js +153 -0
- package/dist/lib/coalescing-queue.d.ts +38 -0
- package/dist/lib/coalescing-queue.js +69 -0
- package/dist/lib/content-extractor.d.ts +16 -0
- package/dist/lib/content-extractor.js +410 -0
- package/dist/lib/content-hash.d.ts +1 -0
- package/dist/lib/content-hash.js +19 -0
- package/dist/lib/content-patcher.d.ts +15 -0
- package/dist/lib/content-patcher.js +293 -0
- package/dist/lib/cost-guards.d.ts +2 -0
- package/dist/lib/cost-guards.js +18 -0
- package/dist/lib/daily-spend-cap.d.ts +58 -0
- package/dist/lib/daily-spend-cap.js +233 -0
- package/dist/lib/effective-locales.d.ts +181 -0
- package/dist/lib/effective-locales.js +302 -0
- package/dist/lib/error-messages.d.ts +245 -0
- package/dist/lib/error-messages.js +626 -0
- package/dist/lib/events.d.ts +39 -0
- package/dist/lib/events.js +146 -0
- package/dist/lib/exclude-fields.d.ts +3 -0
- package/dist/lib/exclude-fields.js +64 -0
- package/dist/lib/field-breadcrumb.d.ts +31 -0
- package/dist/lib/field-breadcrumb.js +227 -0
- package/dist/lib/field-diff.d.ts +1 -0
- package/dist/lib/field-diff.js +25 -0
- package/dist/lib/field-empty.d.ts +2 -0
- package/dist/lib/field-empty.js +68 -0
- package/dist/lib/field-resolver.d.ts +3 -0
- package/dist/lib/field-resolver.js +164 -0
- package/dist/lib/group-soft-skips.d.ts +39 -0
- package/dist/lib/group-soft-skips.js +45 -0
- package/dist/lib/locale-merge.d.ts +44 -0
- package/dist/lib/locale-merge.js +357 -0
- package/dist/lib/locale-row-check.d.ts +30 -0
- package/dist/lib/locale-row-check.js +64 -0
- package/dist/lib/logger.d.ts +74 -0
- package/dist/lib/logger.js +97 -0
- package/dist/lib/manual-edit-guard.d.ts +128 -0
- package/dist/lib/manual-edit-guard.js +393 -0
- package/dist/lib/output-validation.d.ts +48 -0
- package/dist/lib/output-validation.js +148 -0
- package/dist/lib/payload-read.d.ts +16 -0
- package/dist/lib/payload-read.js +51 -0
- package/dist/lib/per-doc-claim.d.ts +90 -0
- package/dist/lib/per-doc-claim.js +140 -0
- package/dist/lib/per-doc-lock.d.ts +94 -0
- package/dist/lib/per-doc-lock.js +119 -0
- package/dist/lib/persist-usage.d.ts +91 -0
- package/dist/lib/persist-usage.js +116 -0
- package/dist/lib/progress-store.d.ts +103 -0
- package/dist/lib/progress-store.js +314 -0
- package/dist/lib/rate-limiter.d.ts +3 -0
- package/dist/lib/rate-limiter.js +53 -0
- package/dist/lib/snapshot-select.d.ts +43 -0
- package/dist/lib/snapshot-select.js +108 -0
- package/dist/lib/translate-prompt.d.ts +31 -0
- package/dist/lib/translate-prompt.js +66 -0
- package/dist/lib/translation-token-bucket.d.ts +57 -0
- package/dist/lib/translation-token-bucket.js +365 -0
- package/dist/lib/truncate-source-value.d.ts +1 -0
- package/dist/lib/truncate-source-value.js +27 -0
- package/dist/manual-edit-collection.d.ts +22 -0
- package/dist/manual-edit-collection.js +124 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +934 -0
- package/dist/providers/ai-sdk-adapter.d.ts +35 -0
- package/dist/providers/ai-sdk-adapter.js +100 -0
- package/dist/providers/anthropic.d.ts +31 -0
- package/dist/providers/anthropic.js +66 -0
- package/dist/providers/custom.d.ts +36 -0
- package/dist/providers/custom.js +24 -0
- package/dist/providers/gemini.d.ts +20 -0
- package/dist/providers/gemini.js +48 -0
- package/dist/providers/mock.d.ts +2 -0
- package/dist/providers/mock.js +29 -0
- package/dist/providers/openai.d.ts +28 -0
- package/dist/providers/openai.js +69 -0
- package/dist/settings-global.d.ts +74 -0
- package/dist/settings-global.js +216 -0
- package/dist/tasks/bulk-translate-coordinator.d.ts +115 -0
- package/dist/tasks/bulk-translate-coordinator.js +708 -0
- package/dist/tasks/bulk-translate-doc-task.d.ts +142 -0
- package/dist/tasks/bulk-translate-doc-task.js +1000 -0
- package/dist/tasks/bulk-translate-janitor.d.ts +87 -0
- package/dist/tasks/bulk-translate-janitor.js +311 -0
- package/dist/tasks/translate-job-task.d.ts +51 -0
- package/dist/tasks/translate-job-task.js +154 -0
- package/dist/translate.d.ts +113 -0
- package/dist/translate.js +911 -0
- package/dist/translation-daily-spend-collection.d.ts +24 -0
- package/dist/translation-daily-spend-collection.js +133 -0
- package/dist/translation-rate-limits-collection.d.ts +30 -0
- package/dist/translation-rate-limits-collection.js +144 -0
- package/dist/types.d.ts +672 -0
- package/dist/types.js +1 -0
- package/dist/usage-collection.d.ts +14 -0
- package/dist/usage-collection.js +377 -0
- package/dist/views/BulkRunsHub/BatchRow.d.ts +32 -0
- package/dist/views/BulkRunsHub/BatchRow.js +1222 -0
- package/dist/views/BulkRunsHub/BucketRow.d.ts +62 -0
- package/dist/views/BulkRunsHub/BucketRow.js +982 -0
- package/dist/views/BulkRunsHub/BulkRunsHub.client.d.ts +18 -0
- package/dist/views/BulkRunsHub/BulkRunsHub.client.js +331 -0
- package/dist/views/BulkRunsHub/EmptyState.d.ts +6 -0
- package/dist/views/BulkRunsHub/EmptyState.js +64 -0
- package/dist/views/BulkRunsHub/FilterBar.d.ts +16 -0
- package/dist/views/BulkRunsHub/FilterBar.js +284 -0
- package/dist/views/BulkRunsHub/InFlightBanner.d.ts +14 -0
- package/dist/views/BulkRunsHub/InFlightBanner.js +59 -0
- package/dist/views/BulkRunsHub/StatusBadge.d.ts +64 -0
- package/dist/views/BulkRunsHub/StatusBadge.js +248 -0
- package/dist/views/BulkRunsHub/SummaryStrip.d.ts +22 -0
- package/dist/views/BulkRunsHub/SummaryStrip.js +249 -0
- package/dist/views/BulkRunsHub/bucket-grouping.d.ts +200 -0
- package/dist/views/BulkRunsHub/bucket-grouping.js +344 -0
- package/dist/views/BulkRunsHub/bucketFailureSummary.d.ts +9 -0
- package/dist/views/BulkRunsHub/bucketFailureSummary.js +36 -0
- package/dist/views/BulkRunsHub/dedupedStatusFetch.d.ts +5 -0
- package/dist/views/BulkRunsHub/dedupedStatusFetch.js +45 -0
- package/dist/views/BulkRunsHub/index.d.ts +17 -0
- package/dist/views/BulkRunsHub/index.js +80 -0
- package/dist/views/BulkRunsHub/urlFilters.d.ts +14 -0
- package/dist/views/BulkRunsHub/urlFilters.js +50 -0
- package/dist/views/BulkRunsHub/useBulkRunsList.d.ts +26 -0
- package/dist/views/BulkRunsHub/useBulkRunsList.js +204 -0
- package/dist/views/BulkRunsHub/useUrlFilters.d.ts +10 -0
- package/dist/views/BulkRunsHub/useUrlFilters.js +88 -0
- package/dist/views/TranslationHub/ActiveJobs.d.ts +6 -0
- package/dist/views/TranslationHub/ActiveJobs.js +320 -0
- package/dist/views/TranslationHub/AdvancedPanel.d.ts +17 -0
- package/dist/views/TranslationHub/AdvancedPanel.js +996 -0
- package/dist/views/TranslationHub/AlertBanner.d.ts +6 -0
- package/dist/views/TranslationHub/AlertBanner.js +568 -0
- package/dist/views/TranslationHub/AuditPanel.d.ts +6 -0
- package/dist/views/TranslationHub/AuditPanel.helpers.d.ts +44 -0
- package/dist/views/TranslationHub/AuditPanel.helpers.js +71 -0
- package/dist/views/TranslationHub/AuditPanel.js +1367 -0
- package/dist/views/TranslationHub/BulkTranslate.types.d.ts +242 -0
- package/dist/views/TranslationHub/BulkTranslate.types.js +36 -0
- package/dist/views/TranslationHub/BulkTranslateFailureDrawer.d.ts +19 -0
- package/dist/views/TranslationHub/BulkTranslateFailureDrawer.js +332 -0
- package/dist/views/TranslationHub/BulkTranslateMonitor.d.ts +28 -0
- package/dist/views/TranslationHub/BulkTranslateMonitor.js +305 -0
- package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.d.ts +3 -0
- package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.js +42 -0
- package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.d.ts +26 -0
- package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.js +95 -0
- package/dist/views/TranslationHub/BulkTranslatePreflightModal.d.ts +22 -0
- package/dist/views/TranslationHub/BulkTranslatePreflightModal.js +879 -0
- package/dist/views/TranslationHub/BulkTranslateTerminalCard.d.ts +29 -0
- package/dist/views/TranslationHub/BulkTranslateTerminalCard.js +445 -0
- package/dist/views/TranslationHub/BulkTranslateTrigger.d.ts +66 -0
- package/dist/views/TranslationHub/BulkTranslateTrigger.js +161 -0
- package/dist/views/TranslationHub/EditorRecentRunsPanel.d.ts +33 -0
- package/dist/views/TranslationHub/EditorRecentRunsPanel.js +290 -0
- package/dist/views/TranslationHub/Hub.client.d.ts +74 -0
- package/dist/views/TranslationHub/Hub.client.js +357 -0
- package/dist/views/TranslationHub/ModelCombobox.d.ts +14 -0
- package/dist/views/TranslationHub/ModelCombobox.js +415 -0
- package/dist/views/TranslationHub/PerCollectionConfig.d.ts +10 -0
- package/dist/views/TranslationHub/PerCollectionConfig.helpers.d.ts +16 -0
- package/dist/views/TranslationHub/PerCollectionConfig.helpers.js +19 -0
- package/dist/views/TranslationHub/PerCollectionConfig.js +759 -0
- package/dist/views/TranslationHub/SettingsRail.d.ts +11 -0
- package/dist/views/TranslationHub/SettingsRail.js +382 -0
- package/dist/views/TranslationHub/StatusStrip.d.ts +6 -0
- package/dist/views/TranslationHub/StatusStrip.js +451 -0
- package/dist/views/TranslationHub/UsageTable.d.ts +6 -0
- package/dist/views/TranslationHub/UsageTable.helpers.d.ts +69 -0
- package/dist/views/TranslationHub/UsageTable.helpers.js +49 -0
- package/dist/views/TranslationHub/UsageTable.js +1240 -0
- package/dist/views/TranslationHub/alertGrouping.d.ts +70 -0
- package/dist/views/TranslationHub/alertGrouping.js +99 -0
- package/dist/views/TranslationHub/index.d.ts +20 -0
- package/dist/views/TranslationHub/index.js +109 -0
- package/dist/views/TranslationHub/tabNavigation.d.ts +53 -0
- package/dist/views/TranslationHub/tabNavigation.js +74 -0
- package/dist/views/TranslationHub/terminalBannerVisibility.d.ts +33 -0
- package/dist/views/TranslationHub/terminalBannerVisibility.js +124 -0
- package/dist/views/TranslationHub/useBulkTranslateActive.d.ts +49 -0
- package/dist/views/TranslationHub/useBulkTranslateActive.js +251 -0
- package/dist/views/TranslationHub/useFocusTrap.d.ts +6 -0
- package/dist/views/TranslationHub/useFocusTrap.js +81 -0
- package/dist/views/TranslationHub/useTranslationHubUsageSummary.d.ts +77 -0
- package/dist/views/TranslationHub/useTranslationHubUsageSummary.js +267 -0
- package/dist/views/shared/EditorError.d.ts +97 -0
- package/dist/views/shared/EditorError.js +205 -0
- package/dist/views/shared/ModelCell.d.ts +18 -0
- package/dist/views/shared/ModelCell.js +31 -0
- package/dist/views/shared/docHref.d.ts +16 -0
- package/dist/views/shared/docHref.js +26 -0
- package/dist/views/shared/fetch-error-body.d.ts +25 -0
- package/dist/views/shared/fetch-error-body.js +42 -0
- package/dist/views/shared/filterPillStyle.d.ts +35 -0
- package/dist/views/shared/filterPillStyle.js +40 -0
- package/dist/views/shared/format.d.ts +75 -0
- package/dist/views/shared/format.js +131 -0
- package/package.json +141 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { ALERTS_CONTEXT_FLAG, REENTRY_FLAG } from '../defaults.js';
|
|
2
|
+
import { createScopedLogger } from './logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Module-scoped payload reference for alert persistence. Set via
|
|
5
|
+
* `setAlertsContext` from the plugin's `onInit` (post-config-build,
|
|
6
|
+
* when `payload` is live). When null, `emitAlert` skips DB persistence
|
|
7
|
+
* but still fires the consumer's `onAlert` callback.
|
|
8
|
+
*
|
|
9
|
+
* `payload` carries an optional `logger` (pino) so we can emit
|
|
10
|
+
* structured "alerts about alerts failing" — without it, persistence
|
|
11
|
+
* failures swallow silently (Grafana blindness).
|
|
12
|
+
*/ let alertsContext = null;
|
|
13
|
+
export function setAlertsContext(ctx) {
|
|
14
|
+
alertsContext = ctx;
|
|
15
|
+
}
|
|
16
|
+
export function emitEvent(config, event) {
|
|
17
|
+
if (!config.onEvent) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const result = config.onEvent(event);
|
|
22
|
+
if (result && typeof result.catch === 'function') {
|
|
23
|
+
result.catch((err)=>{
|
|
24
|
+
logEventsFailure('events.emit.failed', err, {
|
|
25
|
+
eventType: event.type,
|
|
26
|
+
phase: 'onEvent.async'
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
} catch (err) {
|
|
31
|
+
// Swallow — event emission never breaks the main flow.
|
|
32
|
+
// But surface to pino so Grafana sees consumer callbacks failing.
|
|
33
|
+
logEventsFailure('events.emit.failed', err, {
|
|
34
|
+
eventType: event.type,
|
|
35
|
+
phase: 'onEvent.sync'
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Best-effort structured log for events/alerts-pipeline failures. The
|
|
41
|
+
* pipeline cannot break the main translation flow; the trade-off is
|
|
42
|
+
* that swallowed failures used to be invisible. Routes through the
|
|
43
|
+
* module-scoped alerts context's payload when set; otherwise falls
|
|
44
|
+
* back to `console.warn` so dev environments without `onInit` still
|
|
45
|
+
* see the signal.
|
|
46
|
+
*/ function logEventsFailure(eventName, err, fields) {
|
|
47
|
+
if (alertsContext?.payload?.logger) {
|
|
48
|
+
const log = createScopedLogger(alertsContext.payload, {
|
|
49
|
+
component: 'events'
|
|
50
|
+
});
|
|
51
|
+
log.event('warn', eventName, {
|
|
52
|
+
err,
|
|
53
|
+
...fields
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// No payload logger available (pre-onInit, or consumer mock). Last-
|
|
58
|
+
// resort visibility — better than the previous silent swallow.
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.warn(`[ai-translate] ${eventName}`, {
|
|
61
|
+
err,
|
|
62
|
+
...fields
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Best-effort alert persistence + consumer callback. Two outputs:
|
|
67
|
+
*
|
|
68
|
+
* 1. Persists a row to `translation-alerts` collection when the
|
|
69
|
+
* plugin's alertsContext is set (auto-registered when
|
|
70
|
+
* `usageTracking.enabled`). Lets the Hub surface alerts in-app.
|
|
71
|
+
* 2. Calls the consumer's `onAlert` callback if set (back-compat —
|
|
72
|
+
* pre-1.1.15 consumers wired this to Slack/email).
|
|
73
|
+
*
|
|
74
|
+
* Neither path can break the main translation flow — both are wrapped
|
|
75
|
+
* in best-effort try/catch with no propagation.
|
|
76
|
+
*/ export function emitAlert(config, alert) {
|
|
77
|
+
// Persistence path. Doesn't depend on consumer config.
|
|
78
|
+
if (alertsContext) {
|
|
79
|
+
const ctx = alertsContext;
|
|
80
|
+
void (async ()=>{
|
|
81
|
+
try {
|
|
82
|
+
// Merge `code` + `context` (1.2.5+) into the metadata JSON
|
|
83
|
+
// column. AlertBanner reads them back via `metadata.code` /
|
|
84
|
+
// `metadata.context` and renders via `editorMessageFor()`.
|
|
85
|
+
// Pre-1.2.5 alerts without these fields fall back to the
|
|
86
|
+
// raw `message` string.
|
|
87
|
+
const baseMetadata = alert.metadata ?? null;
|
|
88
|
+
const mergedMetadata = alert.code || alert.context ? {
|
|
89
|
+
...baseMetadata ?? {},
|
|
90
|
+
...alert.code ? {
|
|
91
|
+
code: alert.code
|
|
92
|
+
} : {},
|
|
93
|
+
...alert.context ? {
|
|
94
|
+
context: alert.context
|
|
95
|
+
} : {}
|
|
96
|
+
} : baseMetadata;
|
|
97
|
+
await ctx.payload.create({
|
|
98
|
+
collection: ctx.collectionSlug,
|
|
99
|
+
data: {
|
|
100
|
+
alertType: alert.type.replace(/^translation\./, ''),
|
|
101
|
+
message: alert.message,
|
|
102
|
+
collection: alert.collection ?? null,
|
|
103
|
+
documentId: alert.documentId != null ? String(alert.documentId) : null,
|
|
104
|
+
metadata: mergedMetadata,
|
|
105
|
+
dismissed: false
|
|
106
|
+
},
|
|
107
|
+
context: {
|
|
108
|
+
[ALERTS_CONTEXT_FLAG]: true,
|
|
109
|
+
[REENTRY_FLAG]: true
|
|
110
|
+
},
|
|
111
|
+
overrideAccess: true
|
|
112
|
+
});
|
|
113
|
+
} catch (err) {
|
|
114
|
+
// Best-effort. Persistence failure cannot break the consumer's
|
|
115
|
+
// `onAlert` callback (which may be the primary alerting channel,
|
|
116
|
+
// e.g. wired to PagerDuty). But surface as a structured pino
|
|
117
|
+
// event so Grafana sees the "alerts about alerts failing".
|
|
118
|
+
logEventsFailure('events.emit.failed', err, {
|
|
119
|
+
alertType: alert.type,
|
|
120
|
+
phase: 'persistence'
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
})();
|
|
124
|
+
}
|
|
125
|
+
// Consumer callback path. Unchanged from pre-1.1.15.
|
|
126
|
+
if (!config.onAlert) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const result = config.onAlert(alert);
|
|
131
|
+
if (result && typeof result.catch === 'function') {
|
|
132
|
+
result.catch((err)=>{
|
|
133
|
+
logEventsFailure('events.emit.failed', err, {
|
|
134
|
+
alertType: alert.type,
|
|
135
|
+
phase: 'onAlert.async'
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
} catch (err) {
|
|
140
|
+
// Swallow — alert emission never breaks the main flow.
|
|
141
|
+
logEventsFailure('events.emit.failed', err, {
|
|
142
|
+
alertType: alert.type,
|
|
143
|
+
phase: 'onAlert.sync'
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { AITranslatePluginConfig } from '../types.js';
|
|
2
|
+
export declare function getEffectiveExcludePatterns(config: Pick<AITranslatePluginConfig, 'excludeFields' | 'excludeUrlFields'>): string[];
|
|
3
|
+
export declare function isExcluded(fieldPath: string, patterns: string[]): boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effective exclude patterns: user-supplied `excludeFields` plus defaults:
|
|
3
|
+
* - `**.url` — URL fields trip the verbatim-echo validator and waste tokens;
|
|
4
|
+
* disable via `excludeUrlFields: false` for rare locale-specific deep links.
|
|
5
|
+
* - JSON-LD `@`-prefixed keys (`**.@type`, `**.@context`, `**.@id`, `**.@graph`)
|
|
6
|
+
* — schema.org constants that consumers parse as identifiers; translating
|
|
7
|
+
* `"Article"` to `"Artikel"` breaks structured-data interpretation. These
|
|
8
|
+
* are unconditionally excluded because translating them is never correct.
|
|
9
|
+
* Pattern matching uses literal `@` so `**.@type` matches paths ending in
|
|
10
|
+
* `.@type` only.
|
|
11
|
+
*/ const JSON_LD_RESERVED_PATTERNS = [
|
|
12
|
+
'**.@type',
|
|
13
|
+
'**.@context',
|
|
14
|
+
'**.@id',
|
|
15
|
+
'**.@graph',
|
|
16
|
+
'**.@vocab'
|
|
17
|
+
];
|
|
18
|
+
export function getEffectiveExcludePatterns(config) {
|
|
19
|
+
const user = config.excludeFields ?? [];
|
|
20
|
+
const patterns = [
|
|
21
|
+
...user
|
|
22
|
+
];
|
|
23
|
+
if (config.excludeUrlFields !== false && !user.includes('**.url')) {
|
|
24
|
+
// `**.url` matches any path ending in `.url` — link.url, hero.cta.url, etc.
|
|
25
|
+
patterns.push('**.url');
|
|
26
|
+
}
|
|
27
|
+
for (const reserved of JSON_LD_RESERVED_PATTERNS){
|
|
28
|
+
if (!patterns.includes(reserved)) patterns.push(reserved);
|
|
29
|
+
}
|
|
30
|
+
return patterns;
|
|
31
|
+
}
|
|
32
|
+
function matchesPattern(fieldPath, pattern) {
|
|
33
|
+
if (pattern === fieldPath) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
// Contains wildcard: '**.visibility.**' matches any path containing '.visibility.'.
|
|
37
|
+
// Must run before the prefix-only and suffix-only branches because those
|
|
38
|
+
// would otherwise claim a pattern like `**.x.**` (which starts with `**.`)
|
|
39
|
+
// before the contains-wildcard check ever runs.
|
|
40
|
+
if (pattern.startsWith('**.') && pattern.endsWith('.**')) {
|
|
41
|
+
const middle = pattern.slice(3, -3);
|
|
42
|
+
if (middle.length === 0) return true; // `**.**` matches anything
|
|
43
|
+
return fieldPath === middle || fieldPath.startsWith(middle + '.') || fieldPath.endsWith('.' + middle) || fieldPath.includes('.' + middle + '.');
|
|
44
|
+
}
|
|
45
|
+
// Suffix wildcard: 'visibility.**' matches 'visibility.auth', 'visibility.x.y'
|
|
46
|
+
if (pattern.endsWith('.**')) {
|
|
47
|
+
const prefix = pattern.slice(0, -3);
|
|
48
|
+
return fieldPath === prefix || fieldPath.startsWith(prefix + '.');
|
|
49
|
+
}
|
|
50
|
+
// Prefix wildcard: '**.visibility' matches 'content.0.visibility', 'x.y.visibility'
|
|
51
|
+
if (pattern.startsWith('**.')) {
|
|
52
|
+
const suffix = pattern.slice(3);
|
|
53
|
+
return fieldPath === suffix || fieldPath.endsWith('.' + suffix);
|
|
54
|
+
}
|
|
55
|
+
const patternParts = pattern.split('.');
|
|
56
|
+
const fieldParts = fieldPath.split('.');
|
|
57
|
+
if (patternParts.length !== fieldParts.length) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return patternParts.every((part, i)=>part === '*' || part === fieldParts[i]);
|
|
61
|
+
}
|
|
62
|
+
export function isExcluded(fieldPath, patterns) {
|
|
63
|
+
return patterns.some((pattern)=>matchesPattern(fieldPath, pattern));
|
|
64
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a dotted Payload field path into a human-readable breadcrumb.
|
|
3
|
+
*
|
|
4
|
+
* Example:
|
|
5
|
+
* path = "sidebar.featured_sidebar_items.3.label"
|
|
6
|
+
* field = SiteNavigation tabs config
|
|
7
|
+
* → ["Sidebar Navigation", "Featured Sidebar Items", "#4", "Label"]
|
|
8
|
+
*
|
|
9
|
+
* The Translation Hub renders soft-skipped field paths to editors who
|
|
10
|
+
* don't think in dotted-paths — they think in "Sidebar Navigation ›
|
|
11
|
+
* Featured Items › Item #4 › Label." This helper walks the field config
|
|
12
|
+
* matching each path segment to either a `name`, a numeric array index,
|
|
13
|
+
* or a tab name; falls back to titleizing the raw segment when the
|
|
14
|
+
* config doesn't match (older row paths, deleted fields, etc).
|
|
15
|
+
*
|
|
16
|
+
* Pure function, no side effects, no I/O. Tested in isolation against
|
|
17
|
+
* synthetic Payload configs that cover tabs/groups/arrays/rows/blocks.
|
|
18
|
+
*/
|
|
19
|
+
export type FieldBreadcrumbInput = {
|
|
20
|
+
/** Dotted field path, e.g. "sidebar.featured_sidebar_items.3.label" */
|
|
21
|
+
path: string;
|
|
22
|
+
/** Top-level field config from the collection or global */
|
|
23
|
+
fields: ReadonlyArray<unknown>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Returns the resolved breadcrumb segments. Falls back to titleized
|
|
27
|
+
* raw segments for any part of the path that doesn't resolve, so the
|
|
28
|
+
* renderer can always show something. Numeric segments resolve to
|
|
29
|
+
* `#N` (1-based for editor familiarity).
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveFieldBreadcrumb(input: FieldBreadcrumbInput): string[];
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a dotted Payload field path into a human-readable breadcrumb.
|
|
3
|
+
*
|
|
4
|
+
* Example:
|
|
5
|
+
* path = "sidebar.featured_sidebar_items.3.label"
|
|
6
|
+
* field = SiteNavigation tabs config
|
|
7
|
+
* → ["Sidebar Navigation", "Featured Sidebar Items", "#4", "Label"]
|
|
8
|
+
*
|
|
9
|
+
* The Translation Hub renders soft-skipped field paths to editors who
|
|
10
|
+
* don't think in dotted-paths — they think in "Sidebar Navigation ›
|
|
11
|
+
* Featured Items › Item #4 › Label." This helper walks the field config
|
|
12
|
+
* matching each path segment to either a `name`, a numeric array index,
|
|
13
|
+
* or a tab name; falls back to titleizing the raw segment when the
|
|
14
|
+
* config doesn't match (older row paths, deleted fields, etc).
|
|
15
|
+
*
|
|
16
|
+
* Pure function, no side effects, no I/O. Tested in isolation against
|
|
17
|
+
* synthetic Payload configs that cover tabs/groups/arrays/rows/blocks.
|
|
18
|
+
*/ // ---------------------------------------------------------------------------
|
|
19
|
+
// Minimal local field-config shape
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* Returns the resolved breadcrumb segments. Falls back to titleized
|
|
23
|
+
* raw segments for any part of the path that doesn't resolve, so the
|
|
24
|
+
* renderer can always show something. Numeric segments resolve to
|
|
25
|
+
* `#N` (1-based for editor familiarity).
|
|
26
|
+
*/ export function resolveFieldBreadcrumb(input) {
|
|
27
|
+
const { path, fields } = input;
|
|
28
|
+
if (!path) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const parts = path.split('.');
|
|
32
|
+
const fieldArray = fields;
|
|
33
|
+
const result = walk(parts, 0, fieldArray, []);
|
|
34
|
+
return result.length > 0 ? result : [
|
|
35
|
+
titleize(path)
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Walker
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
function walk(parts, index, fields, acc) {
|
|
42
|
+
if (index >= parts.length) {
|
|
43
|
+
return [
|
|
44
|
+
...acc
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
const part = parts[index];
|
|
48
|
+
// Numeric segment with no resolver context → render as `#N`. The
|
|
49
|
+
// array case below normally consumes the index after matching the
|
|
50
|
+
// array field, so reaching this branch means the path advanced into
|
|
51
|
+
// a block-typed array (which is unresolvable statically) or stale
|
|
52
|
+
// data — render the index without trying to descend further.
|
|
53
|
+
if (/^\d+$/.test(part)) {
|
|
54
|
+
const indexNum = Number(part);
|
|
55
|
+
return walk(parts, index + 1, fields, [
|
|
56
|
+
...acc,
|
|
57
|
+
`#${indexNum + 1}`
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
if (!fields || fields.length === 0) {
|
|
61
|
+
const remaining = parts.slice(index).map(humanSegment);
|
|
62
|
+
return [
|
|
63
|
+
...acc,
|
|
64
|
+
...remaining
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
const match = findInScope(fields, part);
|
|
68
|
+
if (!match) {
|
|
69
|
+
const remaining = parts.slice(index).map(humanSegment);
|
|
70
|
+
return [
|
|
71
|
+
...acc,
|
|
72
|
+
...remaining
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
// Prepend any tab labels we crossed to reach the match
|
|
76
|
+
const acc2 = [
|
|
77
|
+
...acc,
|
|
78
|
+
...match.tabLabels.map((t)=>labelOf(t.label, t.name))
|
|
79
|
+
];
|
|
80
|
+
const field = match.field;
|
|
81
|
+
const fieldLabel = labelOf(field.label, field.name);
|
|
82
|
+
switch(field.type){
|
|
83
|
+
case 'array':
|
|
84
|
+
{
|
|
85
|
+
const next = [
|
|
86
|
+
...acc2,
|
|
87
|
+
fieldLabel
|
|
88
|
+
];
|
|
89
|
+
const idxPart = parts[index + 1];
|
|
90
|
+
if (idxPart !== undefined && /^\d+$/.test(idxPart)) {
|
|
91
|
+
const idxNum = Number(idxPart);
|
|
92
|
+
return walk(parts, index + 2, field.fields, [
|
|
93
|
+
...next,
|
|
94
|
+
`#${idxNum + 1}`
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
return walk(parts, index + 1, field.fields, next);
|
|
98
|
+
}
|
|
99
|
+
case 'group':
|
|
100
|
+
{
|
|
101
|
+
const next = [
|
|
102
|
+
...acc2,
|
|
103
|
+
fieldLabel
|
|
104
|
+
];
|
|
105
|
+
return walk(parts, index + 1, field.fields, next);
|
|
106
|
+
}
|
|
107
|
+
case 'blocks':
|
|
108
|
+
{
|
|
109
|
+
const next = [
|
|
110
|
+
...acc2,
|
|
111
|
+
fieldLabel
|
|
112
|
+
];
|
|
113
|
+
const idxPart = parts[index + 1];
|
|
114
|
+
if (idxPart !== undefined && /^\d+$/.test(idxPart)) {
|
|
115
|
+
const idxNum = Number(idxPart);
|
|
116
|
+
const tail = parts.slice(index + 2).map(humanSegment);
|
|
117
|
+
return [
|
|
118
|
+
...next,
|
|
119
|
+
`#${idxNum + 1}`,
|
|
120
|
+
...tail
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
return walk(parts, index + 1, undefined, next);
|
|
124
|
+
}
|
|
125
|
+
default:
|
|
126
|
+
{
|
|
127
|
+
const next = [
|
|
128
|
+
...acc2,
|
|
129
|
+
fieldLabel
|
|
130
|
+
];
|
|
131
|
+
const remaining = parts.slice(index + 1).map(humanSegment);
|
|
132
|
+
return [
|
|
133
|
+
...next,
|
|
134
|
+
...remaining
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Find a named field within `fields`, transparently descending into
|
|
141
|
+
* `tabs`, `row`, and `collapsible` containers (which don't add a path
|
|
142
|
+
* segment). Returns the tab(s) crossed so the renderer can show
|
|
143
|
+
* "<TabLabel> › <Field>".
|
|
144
|
+
*/ function findInScope(fields, name) {
|
|
145
|
+
for (const f of fields){
|
|
146
|
+
if (isNamedField(f) && f.name === name) {
|
|
147
|
+
return {
|
|
148
|
+
field: f,
|
|
149
|
+
tabLabels: []
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const f of fields){
|
|
154
|
+
if (f.type === 'tabs' && f.tabs) {
|
|
155
|
+
for (const tab of f.tabs){
|
|
156
|
+
// Named tabs (`{ name: 'sidebar', fields: [...] }`) namespace
|
|
157
|
+
// their children under the tab name — so a path segment matching
|
|
158
|
+
// a tab name is the segment itself, not a child of an unnamed
|
|
159
|
+
// container. Treat the tab like a group: return a synthetic
|
|
160
|
+
// group field that the walker descends into.
|
|
161
|
+
if (tab.name === name) {
|
|
162
|
+
return {
|
|
163
|
+
field: {
|
|
164
|
+
type: 'group',
|
|
165
|
+
name: tab.name,
|
|
166
|
+
label: tab.label,
|
|
167
|
+
fields: tab.fields
|
|
168
|
+
},
|
|
169
|
+
tabLabels: []
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const inside = tab.fields ?? [];
|
|
173
|
+
const inner = findInScope(inside, name);
|
|
174
|
+
if (inner) {
|
|
175
|
+
// Unnamed tab (`{ label: 'Foo', fields: [...] }`) — its
|
|
176
|
+
// children appear at the parent scope with no namespace, so
|
|
177
|
+
// the tab's label is prepended to the breadcrumb.
|
|
178
|
+
return {
|
|
179
|
+
field: inner.field,
|
|
180
|
+
tabLabels: tab.name ? inner.tabLabels : [
|
|
181
|
+
{
|
|
182
|
+
name: tab.name,
|
|
183
|
+
label: tab.label
|
|
184
|
+
},
|
|
185
|
+
...inner.tabLabels
|
|
186
|
+
]
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if ((f.type === 'row' || f.type === 'collapsible') && f.fields) {
|
|
192
|
+
const inner = findInScope(f.fields, name);
|
|
193
|
+
if (inner) {
|
|
194
|
+
return inner;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
function isNamedField(f) {
|
|
201
|
+
return typeof f.name === 'string';
|
|
202
|
+
}
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Label resolution
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
function labelOf(label, name) {
|
|
207
|
+
if (typeof label === 'string') {
|
|
208
|
+
return label;
|
|
209
|
+
}
|
|
210
|
+
if (label && typeof label === 'object') {
|
|
211
|
+
const first = Object.values(label).find((v)=>typeof v === 'string');
|
|
212
|
+
if (first) return first;
|
|
213
|
+
}
|
|
214
|
+
if (name) {
|
|
215
|
+
return titleize(name);
|
|
216
|
+
}
|
|
217
|
+
return '';
|
|
218
|
+
}
|
|
219
|
+
function humanSegment(s) {
|
|
220
|
+
if (/^\d+$/.test(s)) {
|
|
221
|
+
return `#${Number(s) + 1}`;
|
|
222
|
+
}
|
|
223
|
+
return titleize(s);
|
|
224
|
+
}
|
|
225
|
+
function titleize(s) {
|
|
226
|
+
return s.split(/[_-]/).map((w)=>w.length === 0 ? '' : w[0].toUpperCase() + w.slice(1)).join(' ');
|
|
227
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function diffFields(previousDoc: Record<string, unknown>, currentDoc: Record<string, unknown>, fieldPaths: string[]): string[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { hashFieldContent } from './content-hash.js';
|
|
2
|
+
function resolvePathValue(obj, path) {
|
|
3
|
+
const segments = path.split('.');
|
|
4
|
+
let current = obj;
|
|
5
|
+
for (const segment of segments){
|
|
6
|
+
if (current == null || typeof current !== 'object') {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
current = current[segment];
|
|
10
|
+
}
|
|
11
|
+
return current;
|
|
12
|
+
}
|
|
13
|
+
export function diffFields(previousDoc, currentDoc, fieldPaths) {
|
|
14
|
+
const changed = [];
|
|
15
|
+
for (const path of fieldPaths){
|
|
16
|
+
const prevValue = resolvePathValue(previousDoc, path);
|
|
17
|
+
const currValue = resolvePathValue(currentDoc, path);
|
|
18
|
+
const prevHash = hashFieldContent(prevValue);
|
|
19
|
+
const currHash = hashFieldContent(currValue);
|
|
20
|
+
if (prevHash !== currHash) {
|
|
21
|
+
changed.push(path);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return changed;
|
|
25
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
function isTextEmpty(value) {
|
|
2
|
+
return value == null || typeof value === 'string' && value.trim() === '';
|
|
3
|
+
}
|
|
4
|
+
function isRichTextEmpty(value) {
|
|
5
|
+
if (value == null || typeof value !== 'object') {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
const doc = value;
|
|
9
|
+
if (!doc.root || typeof doc.root !== 'object') {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
const root = doc.root;
|
|
13
|
+
const children = root.children;
|
|
14
|
+
if (!Array.isArray(children) || children.length === 0) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return children.every((child)=>{
|
|
18
|
+
if (child == null || typeof child !== 'object') {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
const node = child;
|
|
22
|
+
const nodeChildren = node.children;
|
|
23
|
+
if (!Array.isArray(nodeChildren) || nodeChildren.length === 0) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return nodeChildren.every((textNode)=>{
|
|
27
|
+
if (textNode == null || typeof textNode !== 'object') {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
const tn = textNode;
|
|
31
|
+
return typeof tn.text === 'string' && tn.text.trim() === '';
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function isArrayOrBlocksEmpty(value) {
|
|
36
|
+
if (value == null) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (!Array.isArray(value)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return value.length === 0;
|
|
43
|
+
}
|
|
44
|
+
function isGroupEmpty(value) {
|
|
45
|
+
if (value == null) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (typeof value !== 'object') {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return Object.values(value).every((v)=>v == null);
|
|
52
|
+
}
|
|
53
|
+
export function isFieldEmpty(value, fieldType) {
|
|
54
|
+
switch(fieldType){
|
|
55
|
+
case 'text':
|
|
56
|
+
case 'textarea':
|
|
57
|
+
return isTextEmpty(value);
|
|
58
|
+
case 'richText':
|
|
59
|
+
return isRichTextEmpty(value);
|
|
60
|
+
case 'array':
|
|
61
|
+
case 'blocks':
|
|
62
|
+
return isArrayOrBlocksEmpty(value);
|
|
63
|
+
case 'group':
|
|
64
|
+
return isGroupEmpty(value);
|
|
65
|
+
case 'json':
|
|
66
|
+
return isGroupEmpty(value);
|
|
67
|
+
}
|
|
68
|
+
}
|