@delmaredigital/payload-experiments 0.3.0-rc.1 → 0.4.0-rc.1
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/README.md +172 -21
- package/dist/admin/MetricSourceSelect.js +3 -3
- package/dist/admin/PlacementStatus.d.ts +3 -0
- package/dist/admin/PlacementStatus.js +54 -0
- package/dist/admin/PreviewLinks.d.ts +2 -1
- package/dist/admin/PreviewLinks.js +47 -26
- package/dist/admin/TestsNavLink.d.ts +3 -1
- package/dist/admin/TestsNavLink.js +2 -2
- package/dist/admin/actions/ConfirmDialog.d.ts +2 -1
- package/dist/admin/actions/ConfirmDialog.js +2 -2
- package/dist/admin/actions/DecideDialog.d.ts +2 -1
- package/dist/admin/actions/DecideDialog.js +2 -2
- package/dist/admin/api.d.ts +11 -2
- package/dist/admin/api.js +20 -2
- package/dist/admin/armSelect/ArmSelectField.js +14 -22
- package/dist/admin/arms/arms.model.d.ts +1 -21
- package/dist/admin/arms/arms.model.js +1 -153
- package/dist/admin/blocks/BlockPanel.d.ts +4 -0
- package/dist/admin/blocks/BlockPanel.js +153 -0
- package/dist/admin/blocks/TestBlockField.d.ts +5 -0
- package/dist/admin/blocks/TestBlockField.js +163 -0
- package/dist/admin/blocks/formApi.d.ts +43 -0
- package/dist/admin/blocks/formApi.js +153 -0
- package/dist/admin/blocks/inject.d.ts +7 -0
- package/dist/admin/blocks/inject.js +41 -0
- package/dist/admin/blocks/model.d.ts +17 -0
- package/dist/admin/blocks/model.js +45 -0
- package/dist/admin/blocks/statusCache.d.ts +7 -0
- package/dist/admin/blocks/statusCache.js +83 -0
- package/dist/admin/blocks/useBlockForm.d.ts +17 -0
- package/dist/admin/blocks/useBlockForm.js +46 -0
- package/dist/admin/copy/actions.d.ts +2 -0
- package/dist/admin/copy/actions.js +9 -0
- package/dist/admin/copy/arms.d.ts +1 -6
- package/dist/admin/copy/arms.js +1 -16
- package/dist/admin/copy/blocks.d.ts +27 -0
- package/dist/admin/copy/blocks.js +38 -0
- package/dist/admin/copy/goals.d.ts +2 -0
- package/dist/admin/copy/goals.js +4 -1
- package/dist/admin/copy/newTest.d.ts +10 -0
- package/dist/admin/copy/newTest.js +10 -0
- package/dist/admin/copy/pages.d.ts +26 -0
- package/dist/admin/copy/pages.js +26 -0
- package/dist/admin/copy/readiness.d.ts +25 -3
- package/dist/admin/copy/readiness.js +25 -3
- package/dist/admin/copy/results.d.ts +3 -0
- package/dist/admin/copy/results.js +4 -1
- package/dist/admin/copy/review.d.ts +1 -1
- package/dist/admin/copy/review.js +1 -1
- package/dist/admin/copy/surface.d.ts +0 -1
- package/dist/admin/copy/surface.js +0 -1
- package/dist/admin/copy/testPage.d.ts +4 -0
- package/dist/admin/copy/testPage.js +4 -0
- package/dist/admin/goals/XpGoalField.d.ts +2 -0
- package/dist/admin/goals/XpGoalField.js +152 -0
- package/dist/admin/metricSource.model.d.ts +2 -0
- package/dist/admin/metricSource.model.js +4 -0
- package/dist/admin/pageCollections.d.ts +10 -0
- package/dist/admin/pageCollections.js +20 -0
- package/dist/admin/paths.d.ts +8 -1
- package/dist/admin/paths.js +8 -1
- package/dist/admin/placementStatus.model.d.ts +2 -0
- package/dist/admin/placementStatus.model.js +19 -0
- package/dist/admin/placements/placementLabels.d.ts +11 -0
- package/dist/admin/placements/placementLabels.js +21 -0
- package/dist/admin/placements/placementLabels.model.d.ts +13 -0
- package/dist/admin/placements/placementLabels.model.js +12 -0
- package/dist/admin/placements/usePlacementLabels.d.ts +12 -0
- package/dist/admin/placements/usePlacementLabels.js +102 -0
- package/dist/admin/previewLinks.model.d.ts +2 -0
- package/dist/admin/previewLinks.model.js +3 -0
- package/dist/admin/results/DataQualityPanel.js +4 -1
- package/dist/admin/results/legacy.d.ts +16 -0
- package/dist/admin/results/legacy.js +27 -0
- package/dist/admin/results/results.model.d.ts +9 -6
- package/dist/admin/results/results.model.js +22 -9
- package/dist/admin/results/snapshot.d.ts +14 -0
- package/dist/admin/results/snapshot.js +3 -0
- package/dist/admin/surface/TestsHome.js +43 -15
- package/dist/admin/surface/TestsSurface.js +5 -1
- package/dist/admin/surface/contracts.d.ts +6 -5
- package/dist/admin/surface/goals/GoalForm.js +67 -34
- package/dist/admin/surface/goals/GoalsList.js +3 -3
- package/dist/admin/surface/goals/data.js +19 -16
- package/dist/admin/surface/goals/goals.model.d.ts +1 -2
- package/dist/admin/surface/goals/goals.model.js +1 -3
- package/dist/admin/surface/home.model.d.ts +2 -1
- package/dist/admin/surface/home.model.js +8 -4
- package/dist/admin/surface/newTest/NewTest.js +58 -17
- package/dist/admin/surface/newTest/PageWizard.d.ts +7 -0
- package/dist/admin/surface/newTest/PageWizard.js +209 -0
- package/dist/admin/surface/pagePicker/CopyRoute.d.ts +6 -0
- package/dist/admin/surface/pagePicker/CopyRoute.js +53 -0
- package/dist/admin/surface/pagePicker/PagePicker.d.ts +10 -0
- package/dist/admin/surface/pagePicker/PagePicker.js +172 -0
- package/dist/admin/surface/pagePicker/pagePicker.model.d.ts +25 -0
- package/dist/admin/surface/pagePicker/pagePicker.model.js +25 -0
- package/dist/admin/surface/readiness/ActionDialogs.js +103 -69
- package/dist/admin/surface/readiness/Banner.js +30 -6
- package/dist/admin/surface/readiness/readiness.model.d.ts +8 -1
- package/dist/admin/surface/readiness/readiness.model.js +95 -6
- package/dist/admin/surface/results/ReleasePages.d.ts +3 -1
- package/dist/admin/surface/results/ReleasePages.js +3 -7
- package/dist/admin/surface/results/ResultsBody.js +23 -3
- package/dist/admin/surface/results/ResultsHeader.js +2 -5
- package/dist/admin/surface/results/recommendation.model.d.ts +3 -2
- package/dist/admin/surface/results/recommendation.model.js +10 -3
- package/dist/admin/surface/results/useResults.d.ts +10 -1
- package/dist/admin/surface/surface.css +0 -1
- package/dist/admin/surface/test/AdvancedSection.js +111 -104
- package/dist/admin/surface/test/GoalSection.js +97 -22
- package/dist/admin/surface/test/LengthSection.js +1 -1
- package/dist/admin/surface/test/PageHoldout.d.ts +3 -0
- package/dist/admin/surface/test/PageHoldout.js +67 -0
- package/dist/admin/surface/test/TestPage.js +2 -1
- package/dist/admin/surface/test/VersionsSection.js +12 -5
- package/dist/admin/surface/test/fields.d.ts +13 -4
- package/dist/admin/surface/test/fields.js +46 -14
- package/dist/admin/surface/test/jumpToSection.d.ts +1 -1
- package/dist/admin/surface/test/jumpToSection.js +4 -3
- package/dist/admin/surface/test/pageParts.d.ts +4 -1
- package/dist/admin/surface/test/pageParts.js +79 -2
- package/dist/admin/surface/test/testPage.css +8 -1
- package/dist/admin/surface/test/testPage.model.d.ts +2 -3
- package/dist/admin/surface/test/testPage.model.js +1 -21
- package/dist/admin/surface/testDocument.model.d.ts +1 -0
- package/dist/admin/surface/testDocument.model.js +2 -1
- package/dist/admin/surface/useTestDocument.d.ts +11 -2
- package/dist/admin/surface/useTestDocument.js +55 -16
- package/dist/admin/useApi.d.ts +11 -2
- package/dist/admin/useApi.js +3 -5
- package/dist/admin/useLayerUsage.js +14 -23
- package/dist/admin/views/ExperimentsRootView.js +58 -16
- package/dist/admin/views/TestsNavLinkServer.d.ts +2 -0
- package/dist/admin/views/TestsNavLinkServer.js +43 -0
- package/dist/arms/copy.d.ts +6 -0
- package/dist/arms/copy.js +16 -0
- package/dist/arms/model.d.ts +23 -0
- package/dist/arms/model.js +173 -0
- package/dist/blocks/bindings.d.ts +3 -0
- package/dist/blocks/bindings.js +34 -0
- package/dist/blocks/createExperimentBlock.d.ts +6 -0
- package/dist/blocks/createExperimentBlock.js +77 -0
- package/dist/blocks/formState.d.ts +31 -0
- package/dist/blocks/formState.js +146 -0
- package/dist/blocks/model.d.ts +41 -0
- package/dist/blocks/model.js +34 -0
- package/dist/client/ExperimentGroup.d.ts +6 -0
- package/dist/client/ExperimentGroup.js +9 -0
- package/dist/client/ExperimentsProvider.js +20 -4
- package/dist/client/clickGoals.d.ts +4 -0
- package/dist/client/clickGoals.js +43 -0
- package/dist/collections/experiments/config.js +12 -2
- package/dist/collections/experiments/deleteGuard.d.ts +2 -1
- package/dist/collections/experiments/deleteGuard.js +12 -1
- package/dist/collections/experiments/editability.js +4 -2
- package/dist/collections/experiments/pickSlug.d.ts +2 -0
- package/dist/collections/experiments/pickSlug.js +8 -0
- package/dist/collections/labels.d.ts +3 -0
- package/dist/collections/labels.js +20 -0
- package/dist/collections/metrics.js +12 -2
- package/dist/collections/phases.js +1 -0
- package/dist/collections/serialize.js +7 -0
- package/dist/db/appends.d.ts +6 -2
- package/dist/db/appends.js +10 -4
- package/dist/db/clickGoals.d.ts +9 -0
- package/dist/db/clickGoals.js +22 -0
- package/dist/db/delete.d.ts +1 -0
- package/dist/db/delete.js +1 -0
- package/dist/db/locks.d.ts +14 -0
- package/dist/db/locks.js +36 -1
- package/dist/db/names.d.ts +4 -0
- package/dist/db/names.js +4 -0
- package/dist/db/pageBindings.d.ts +4 -0
- package/dist/db/pageBindings.js +17 -0
- package/dist/db/pageBusy.d.ts +2 -0
- package/dist/db/pageBusy.js +10 -0
- package/dist/db/pageContent.d.ts +10 -0
- package/dist/db/pageContent.js +79 -0
- package/dist/db/pageOccupancy.d.ts +12 -0
- package/dist/db/pageOccupancy.js +45 -0
- package/dist/db/placements.d.ts +33 -0
- package/dist/db/placements.js +54 -0
- package/dist/db/slugs.d.ts +8 -0
- package/dist/db/slugs.js +40 -0
- package/dist/db/snapshots.d.ts +6 -0
- package/dist/db/snapshots.js +9 -5
- package/dist/endpoints/clickKeys.d.ts +5 -0
- package/dist/endpoints/clickKeys.js +17 -0
- package/dist/endpoints/events.js +4 -1
- package/dist/endpoints/index.js +28 -0
- package/dist/endpoints/pages.d.ts +5 -0
- package/dist/endpoints/pages.js +120 -0
- package/dist/endpoints/placementStatus.d.ts +6 -0
- package/dist/endpoints/placementStatus.js +238 -0
- package/dist/endpoints/readBody.d.ts +2 -0
- package/dist/endpoints/readBody.js +24 -0
- package/dist/endpoints/results.js +5 -1
- package/dist/endpoints/summary.js +11 -4
- package/dist/endpoints/wrapBlock.d.ts +6 -0
- package/dist/endpoints/wrapBlock.js +76 -0
- package/dist/errors.d.ts +3 -3
- package/dist/errors.js +4 -4
- package/dist/events/rows.d.ts +1 -1
- package/dist/events/rows.js +5 -3
- package/dist/exports/adminClient.d.ts +5 -0
- package/dist/exports/adminClient.js +5 -0
- package/dist/exports/adminRsc.d.ts +1 -0
- package/dist/exports/adminRsc.js +1 -0
- package/dist/exports/blocks.d.ts +2 -0
- package/dist/exports/blocks.js +1 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.js +1 -0
- package/dist/exports/index.d.ts +6 -1
- package/dist/exports/index.js +3 -0
- package/dist/exports/puck.d.ts +5 -2
- package/dist/exports/puck.js +3 -1
- package/dist/exports/puckEditor.d.ts +6 -1
- package/dist/exports/puckEditor.js +4 -1
- package/dist/exports/rsc.d.ts +4 -0
- package/dist/exports/rsc.js +2 -0
- package/dist/fields/xpGoal.d.ts +3 -0
- package/dist/fields/xpGoal.js +28 -0
- package/dist/globals/state.js +10 -0
- package/dist/goals/cache.d.ts +3 -0
- package/dist/goals/cache.js +35 -0
- package/dist/goals/fairness.d.ts +40 -0
- package/dist/goals/fairness.js +200 -0
- package/dist/goals/key.d.ts +4 -0
- package/dist/goals/key.js +3 -0
- package/dist/goals/picker/clickGoalPicker.model.d.ts +29 -0
- package/dist/goals/picker/clickGoalPicker.model.js +45 -0
- package/dist/goals/picker/copy.d.ts +16 -0
- package/dist/goals/picker/copy.js +15 -0
- package/dist/goals/picker/useClickGoalPicker.d.ts +30 -0
- package/dist/goals/picker/useClickGoalPicker.js +220 -0
- package/dist/hooks/pageGuard.d.ts +5 -1
- package/dist/hooks/pageGuard.js +160 -80
- package/dist/lib/fetchAllDocs.d.ts +16 -0
- package/dist/lib/fetchAllDocs.js +56 -0
- package/dist/onInit.js +29 -0
- package/dist/options.d.ts +2 -0
- package/dist/options.js +43 -10
- package/dist/plugin.js +45 -28
- package/dist/puck/ExperimentPicker.js +47 -16
- package/dist/puck/ExperimentSlot.d.ts +51 -1
- package/dist/puck/ExperimentSlot.js +23 -12
- package/dist/puck/copy.d.ts +6 -4
- package/dist/puck/copy.js +6 -4
- package/dist/puck/editor/CreateExperimentDrawer.js +46 -56
- package/dist/puck/editor/ExperimentSlotEditor.d.ts +52 -1
- package/dist/puck/editor/ExperimentSlotEditor.js +292 -137
- package/dist/puck/editor/ResultsReadout.d.ts +6 -0
- package/dist/puck/editor/ResultsReadout.js +99 -0
- package/dist/puck/editor/VersionBuilder.d.ts +34 -0
- package/dist/puck/editor/VersionBuilder.js +317 -0
- package/dist/puck/editor/armWrites.d.ts +7 -0
- package/dist/puck/editor/armWrites.js +71 -0
- package/dist/puck/editor/armsCache.d.ts +14 -0
- package/dist/puck/editor/armsCache.js +78 -0
- package/dist/puck/editor/copy.d.ts +33 -1
- package/dist/puck/editor/copy.js +45 -8
- package/dist/puck/editor/createTest.d.ts +1 -1
- package/dist/puck/editor/createTest.js +25 -14
- package/dist/puck/editor/editor.chrome.css +48 -161
- package/dist/puck/editor/editor.css +48 -161
- package/dist/puck/editor/editor.model.d.ts +8 -3
- package/dist/puck/editor/editor.model.js +16 -10
- package/dist/puck/editor/experimentOutlinePlugin.d.ts +3 -0
- package/dist/puck/editor/experimentOutlinePlugin.js +89 -0
- package/dist/puck/editor/integration.d.ts +10 -0
- package/dist/puck/editor/integration.js +30 -0
- package/dist/puck/editor/xpGoalPuckEditorField.d.ts +7 -0
- package/dist/puck/editor/xpGoalPuckEditorField.js +134 -0
- package/dist/puck/experimentSlotField.d.ts +2 -0
- package/dist/puck/experimentSlotField.js +11 -3
- package/dist/puck/hostContext.d.ts +10 -0
- package/dist/puck/hostContext.js +12 -0
- package/dist/puck/integration.d.ts +6 -0
- package/dist/puck/integration.js +21 -0
- package/dist/puck/routes.d.ts +12 -0
- package/dist/puck/routes.js +11 -0
- package/dist/puck/slotConfig.d.ts +1 -1
- package/dist/puck/slotConfig.js +2 -2
- package/dist/puck/xpGoalField.d.ts +3 -0
- package/dist/puck/xpGoalField.js +6 -0
- package/dist/results/health.d.ts +4 -0
- package/dist/results/health.js +27 -0
- package/dist/results/snapshot.js +15 -2
- package/dist/sanitizeReadinessDetail.js +51 -1
- package/dist/server/metadata.js +9 -2
- package/dist/server/normaliseContentRelationships.d.ts +7 -0
- package/dist/server/normaliseContentRelationships.js +62 -0
- package/dist/server/pageKindSeoContract.d.ts +27 -0
- package/dist/server/pageKindSeoContract.js +80 -0
- package/dist/server/recordContentChange.d.ts +1 -1
- package/dist/server/recordContentChange.js +72 -13
- package/dist/server/resolveExperimentBlocks.d.ts +5 -0
- package/dist/server/resolveExperimentBlocks.js +54 -0
- package/dist/server/runResults.js +92 -19
- package/dist/server/toClientAssignments.js +2 -1
- package/dist/transitions/addMetric.d.ts +2 -1
- package/dist/transitions/addMetric.js +28 -1
- package/dist/transitions/checkStart.d.ts +17 -1
- package/dist/transitions/checkStart.js +99 -7
- package/dist/transitions/decide.js +40 -1
- package/dist/transitions/deleteExperiment.d.ts +3 -1
- package/dist/transitions/deleteExperiment.js +27 -1
- package/dist/transitions/kernel.js +4 -0
- package/dist/transitions/makeContent.d.ts +5 -0
- package/dist/transitions/makeContent.js +39 -11
- package/dist/transitions/pageCollection.d.ts +10 -0
- package/dist/transitions/pageCollection.js +20 -0
- package/dist/transitions/pageVersions.d.ts +10 -0
- package/dist/transitions/pageVersions.js +44 -0
- package/dist/transitions/phaseRecord.d.ts +1 -0
- package/dist/transitions/placementInteractions.d.ts +40 -0
- package/dist/transitions/placementInteractions.js +94 -0
- package/dist/transitions/placements.d.ts +10 -6
- package/dist/transitions/placements.js +156 -2
- package/dist/transitions/preflightData.d.ts +8 -0
- package/dist/transitions/preflightData.js +248 -36
- package/dist/transitions/resolveCaps.d.ts +1 -0
- package/dist/transitions/resolveCaps.js +6 -2
- package/dist/transitions/start.js +77 -3
- package/dist/types/admin.d.ts +1 -1
- package/dist/types/assignments.d.ts +1 -0
- package/dist/types/contracts.d.ts +31 -10
- package/dist/types/endpoints.d.ts +92 -5
- package/dist/types/index.d.ts +3 -0
- package/dist/types/puck.d.ts +7 -1
- package/dist/types/snapshot.d.ts +17 -4
- package/dist/types/surface.d.ts +13 -0
- package/dist/ui/Tabs.d.ts +4 -2
- package/dist/ui/Tabs.js +33 -26
- package/dist/ui/accent.d.ts +22 -3
- package/dist/ui/accent.js +74 -16
- package/package.json +12 -2
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { EXPERIMENT_BLOCK_SLUG, hostBlocks } from '../../blocks/model.js';
|
|
2
|
+
import { BLOCK_COPY } from '../copy/blocks.js';
|
|
3
|
+
/** The only form-state failure with block-specific editor copy. */ export class BlockChangedError extends Error {
|
|
4
|
+
constructor(){
|
|
5
|
+
super(BLOCK_COPY.blockChanged);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export const wrapMode = 'form-api';
|
|
9
|
+
/** Client twin: unnamed layout containers do not contribute to a data path. */ export function findClientBlocksField(fields, name) {
|
|
10
|
+
for (const field of fields){
|
|
11
|
+
if (field.type === 'blocks' && field.name === name) return field;
|
|
12
|
+
if (field.type === 'row' || field.type === 'collapsible') {
|
|
13
|
+
const found = findClientBlocksField(field.fields, name);
|
|
14
|
+
if (found) return found;
|
|
15
|
+
}
|
|
16
|
+
if (field.type === 'tabs') for (const tab of field.tabs){
|
|
17
|
+
if ('name' in tab && tab.name) continue;
|
|
18
|
+
const found = findClientBlocksField(tab.fields, name);
|
|
19
|
+
if (found) return found;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Only schema structure is needed by the pure helpers, never server validators.
|
|
24
|
+
* Resolve references at every depth, including inside the Experiment slots field.
|
|
25
|
+
*/ export function adaptClientBlocksField(field, config) {
|
|
26
|
+
function block(block) {
|
|
27
|
+
return {
|
|
28
|
+
...block,
|
|
29
|
+
fields: block.fields.map(adapt)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function adapt(field) {
|
|
33
|
+
if (field.type === 'blocks') return {
|
|
34
|
+
...field,
|
|
35
|
+
blocks: [
|
|
36
|
+
...field.blocks.map(block),
|
|
37
|
+
...(field.blockReferences ?? []).flatMap((reference)=>{
|
|
38
|
+
const resolved = typeof reference === 'string' ? config.blocks?.find((item)=>item.slug === reference) : reference;
|
|
39
|
+
return resolved ? [
|
|
40
|
+
block(resolved)
|
|
41
|
+
] : [];
|
|
42
|
+
})
|
|
43
|
+
],
|
|
44
|
+
blockReferences: []
|
|
45
|
+
};
|
|
46
|
+
if ('fields' in field) return {
|
|
47
|
+
...field,
|
|
48
|
+
fields: field.fields.map(adapt)
|
|
49
|
+
};
|
|
50
|
+
if (field.type === 'tabs') return {
|
|
51
|
+
...field,
|
|
52
|
+
tabs: field.tabs.map((tab)=>({
|
|
53
|
+
...tab,
|
|
54
|
+
fields: tab.fields.map(adapt)
|
|
55
|
+
}))
|
|
56
|
+
};
|
|
57
|
+
return field;
|
|
58
|
+
}
|
|
59
|
+
return adapt(field);
|
|
60
|
+
}
|
|
61
|
+
export function experimentBlockSlug(field) {
|
|
62
|
+
return field.blocks.find((block)=>block.fields.some((field)=>'name' in field && field.name === 'xpBlockStatus'))?.slug ?? EXPERIMENT_BLOCK_SLUG;
|
|
63
|
+
}
|
|
64
|
+
export function rowLocation(path) {
|
|
65
|
+
const match = /^([^.]+)\.(\d+)(?:\.|$)/.exec(path);
|
|
66
|
+
return match ? {
|
|
67
|
+
field: match[1],
|
|
68
|
+
index: Number(match[2])
|
|
69
|
+
} : undefined;
|
|
70
|
+
}
|
|
71
|
+
export function dataAt(data, path) {
|
|
72
|
+
return path.split('.').reduce((value, key)=>value && typeof value === 'object' ? value[key] : undefined, data);
|
|
73
|
+
}
|
|
74
|
+
/** Payload 3.90.2 and 4 beta.6 use the same row APIs. Sync replaces the whole
|
|
75
|
+
* wrapper so add/drop/reorder and the server-rendered component slots stay together.
|
|
76
|
+
* N=0 needs UPDATE: removeFieldRow alone serializes the empty field as number 0.
|
|
77
|
+
*/ export async function replaceBlockRow(args) {
|
|
78
|
+
const { form, getFormState, collectionSlug, id, field, index, rows } = args;
|
|
79
|
+
const data = form.getData(), before = hostBlocks(data[field]);
|
|
80
|
+
if (args.expectedBlock && JSON.stringify(before[index]) !== JSON.stringify(args.expectedBlock)) throw new BlockChangedError();
|
|
81
|
+
const original = JSON.stringify(data[field]);
|
|
82
|
+
const next = [
|
|
83
|
+
...before.slice(0, index),
|
|
84
|
+
...rows,
|
|
85
|
+
...before.slice(index + 1)
|
|
86
|
+
];
|
|
87
|
+
const result = await getFormState({
|
|
88
|
+
collectionSlug,
|
|
89
|
+
id,
|
|
90
|
+
schemaPath: collectionSlug,
|
|
91
|
+
data: {
|
|
92
|
+
...data,
|
|
93
|
+
[field]: next
|
|
94
|
+
},
|
|
95
|
+
docPermissions: args.docPermissions,
|
|
96
|
+
docPreferences: args.docPreferences ?? {
|
|
97
|
+
fields: {}
|
|
98
|
+
},
|
|
99
|
+
operation: id == null ? 'create' : 'update',
|
|
100
|
+
renderAllFields: true,
|
|
101
|
+
skipValidation: true
|
|
102
|
+
});
|
|
103
|
+
if (JSON.stringify(form.getDataByPath(field)) !== original) throw new BlockChangedError();
|
|
104
|
+
if (!result.state) throw new Error('Missing server form state');
|
|
105
|
+
const state = result.state;
|
|
106
|
+
function subFieldState(rowIndex) {
|
|
107
|
+
const prefix = `${field}.${rowIndex}.`;
|
|
108
|
+
return Object.fromEntries(Object.entries(state).filter(([path])=>path.startsWith(prefix)).map(([path, value])=>[
|
|
109
|
+
path.slice(prefix.length),
|
|
110
|
+
value
|
|
111
|
+
]));
|
|
112
|
+
}
|
|
113
|
+
if (!rows.length) {
|
|
114
|
+
form.removeFieldRow({
|
|
115
|
+
path: field,
|
|
116
|
+
rowIndex: index
|
|
117
|
+
});
|
|
118
|
+
if (next.length === 0) form.dispatchFields({
|
|
119
|
+
type: 'UPDATE',
|
|
120
|
+
path: field,
|
|
121
|
+
value: [],
|
|
122
|
+
disableFormData: false
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
form.replaceFieldRow({
|
|
127
|
+
path: field,
|
|
128
|
+
rowIndex: index,
|
|
129
|
+
schemaPath: `${collectionSlug}.${field}`,
|
|
130
|
+
blockType: rows[0].blockType,
|
|
131
|
+
subFieldState: subFieldState(index)
|
|
132
|
+
});
|
|
133
|
+
for(let offset = 1; offset < rows.length; offset++)form.addFieldRow({
|
|
134
|
+
path: field,
|
|
135
|
+
rowIndex: index + offset,
|
|
136
|
+
schemaPath: `${collectionSlug}.${field}`,
|
|
137
|
+
blockType: rows[offset].blockType,
|
|
138
|
+
subFieldState: subFieldState(index + offset)
|
|
139
|
+
});
|
|
140
|
+
// REPLACE_ROW invents a metadata id even when the data id is unchanged.
|
|
141
|
+
// Keep server row metadata (including custom labels) and the wrapper React key:
|
|
142
|
+
// otherwise a sync remounts the status field and loses its save notice.
|
|
143
|
+
const oldRows = form.getFields()[field]?.rows ?? [];
|
|
144
|
+
const serverRows = state[field]?.rows;
|
|
145
|
+
if (serverRows) form.dispatchFields({
|
|
146
|
+
type: 'UPDATE',
|
|
147
|
+
path: field,
|
|
148
|
+
rows: serverRows.map((row)=>({
|
|
149
|
+
...oldRows.find((old)=>old.id === row.id),
|
|
150
|
+
...row
|
|
151
|
+
}))
|
|
152
|
+
});
|
|
153
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Field } from 'payload';
|
|
2
|
+
/** Copy the coordinates supplied by CORE-L2, including every containing array. */
|
|
3
|
+
export declare function injectTestBlock(fields: Field[], registration: {
|
|
4
|
+
field: string;
|
|
5
|
+
blockSlug?: string;
|
|
6
|
+
appliesTo?: (doc: Record<string, unknown>) => boolean;
|
|
7
|
+
}): Field[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { findRegisteredBlocksField } from '../../blocks/formState.js';
|
|
2
|
+
import { XP_TEST_BLOCK } from '../paths.js';
|
|
3
|
+
/** Copy the coordinates supplied by CORE-L2, including every containing array. */ export function injectTestBlock(fields, registration) {
|
|
4
|
+
const found = findRegisteredBlocksField(fields, registration.field);
|
|
5
|
+
if (!found) return fields;
|
|
6
|
+
const ui = {
|
|
7
|
+
name: 'xpTestBlock',
|
|
8
|
+
type: 'ui',
|
|
9
|
+
admin: {
|
|
10
|
+
condition: (data)=>{
|
|
11
|
+
try {
|
|
12
|
+
return registration.appliesTo ? registration.appliesTo(data) === true : true;
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
components: {
|
|
18
|
+
Field: {
|
|
19
|
+
path: XP_TEST_BLOCK,
|
|
20
|
+
clientProps: {
|
|
21
|
+
blocksField: registration.field,
|
|
22
|
+
blockSlug: registration.blockSlug
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function insert(value, path) {
|
|
29
|
+
const [key, ...rest] = path;
|
|
30
|
+
if (key === undefined) return value;
|
|
31
|
+
const copy = Array.isArray(value) ? [
|
|
32
|
+
...value
|
|
33
|
+
] : {
|
|
34
|
+
...value
|
|
35
|
+
};
|
|
36
|
+
if (!rest.length) copy.splice(Number(key) + 1, 0, ui);
|
|
37
|
+
else copy[key] = insert(value[key], rest);
|
|
38
|
+
return copy;
|
|
39
|
+
}
|
|
40
|
+
return insert(fields, found.path);
|
|
41
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PlacementStatusResult } from '../../types/endpoints.js';
|
|
2
|
+
import type { RowsField } from './formApi.js';
|
|
3
|
+
export type BlockStatus = PlacementStatusResult['slugs'][string];
|
|
4
|
+
export type LiveBlockStatus = Extract<BlockStatus, {
|
|
5
|
+
status: string;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function liveStatus(status: BlockStatus | null): status is LiveBlockStatus;
|
|
8
|
+
export declare function slotLabel(slot: string, status: BlockStatus | null): string;
|
|
9
|
+
export declare function statusSentence(status: BlockStatus): string;
|
|
10
|
+
export declare function isOrphan(status: BlockStatus): boolean;
|
|
11
|
+
export declare function labelText(value: unknown): string | undefined;
|
|
12
|
+
export declare function blockChoices(value: unknown, field: RowsField, blockSlug: string): {
|
|
13
|
+
block: import("../../blocks/model.js").HostBlock;
|
|
14
|
+
index: number;
|
|
15
|
+
label: string;
|
|
16
|
+
option: string;
|
|
17
|
+
}[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { hostBlocks } from '../../blocks/model.js';
|
|
2
|
+
import { BLOCK_COPY, blockDecidedStatus, blockDraftStatus, blockFinishedStatus, blockPosition } from '../copy/blocks.js';
|
|
3
|
+
export function liveStatus(status) {
|
|
4
|
+
return !!status && 'status' in status;
|
|
5
|
+
}
|
|
6
|
+
export function slotLabel(slot, status) {
|
|
7
|
+
return (liveStatus(status) ? status.arms.find((arm)=>arm.key !== 'holdout' && (arm.slot ?? arm.key) === slot)?.label : undefined) || BLOCK_COPY.slotLabels[slot] || slot;
|
|
8
|
+
}
|
|
9
|
+
export function statusSentence(status) {
|
|
10
|
+
if (!liveStatus(status)) return BLOCK_COPY.deleted;
|
|
11
|
+
const winner = status.arms.find((arm)=>arm.key === status.winnerArm);
|
|
12
|
+
const winnerLabel = winner?.label || BLOCK_COPY.slotLabels[status.winnerArm ?? 'control'] || status.winnerArm || BLOCK_COPY.slotLabels.control;
|
|
13
|
+
switch(status.status){
|
|
14
|
+
case 'draft':
|
|
15
|
+
return blockDraftStatus(status.name);
|
|
16
|
+
case 'running':
|
|
17
|
+
return BLOCK_COPY.running;
|
|
18
|
+
case 'stopped':
|
|
19
|
+
return BLOCK_COPY.stopped;
|
|
20
|
+
case 'decided':
|
|
21
|
+
return blockDecidedStatus(winnerLabel, status.rollout);
|
|
22
|
+
case 'archived':
|
|
23
|
+
return status.winnerArm ? blockFinishedStatus(winnerLabel) : BLOCK_COPY.discarded;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function isOrphan(status) {
|
|
27
|
+
return !liveStatus(status) || status.status === 'archived' && !status.winnerArm;
|
|
28
|
+
}
|
|
29
|
+
export function labelText(value) {
|
|
30
|
+
return typeof value === 'string' ? value : value && typeof value === 'object' ? Object.values(value).find((item)=>typeof item === 'string') : undefined;
|
|
31
|
+
}
|
|
32
|
+
export function blockChoices(value, field, blockSlug) {
|
|
33
|
+
return hostBlocks(value).flatMap((block, index)=>{
|
|
34
|
+
if (block.blockType === blockSlug) return [];
|
|
35
|
+
const label = labelText(field.blocks.find((config)=>config.slug === block.blockType)?.labels?.singular) || block.blockType;
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
block,
|
|
39
|
+
index,
|
|
40
|
+
label,
|
|
41
|
+
option: blockPosition(label, index + 1)
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BlockStatus } from './model.js';
|
|
2
|
+
export declare const validBlockSlug: (slug: unknown) => slug is string;
|
|
3
|
+
/** Only status fields own reads. StrictMode replays the same mount token; a
|
|
4
|
+
* different owner after the last cleanup starts fresh, even in the same commit.
|
|
5
|
+
* Deferred eviction removes every entry with no live owner after that commit.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useBlockStatus(api: string, slug: unknown, owner?: boolean): BlockStatus | null;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react';
|
|
2
|
+
import { createApi } from '../api.js';
|
|
3
|
+
const cache = new Map();
|
|
4
|
+
// Subscribers do not create status entries: a label alone owns no cached data.
|
|
5
|
+
const listeners = new Map();
|
|
6
|
+
const keyOf = (api, slug)=>JSON.stringify([
|
|
7
|
+
api,
|
|
8
|
+
slug
|
|
9
|
+
]);
|
|
10
|
+
const publish = (key)=>listeners.get(key)?.callbacks.forEach((listener)=>listener());
|
|
11
|
+
export const validBlockSlug = (slug)=>typeof slug === 'string' && slug.length > 0 && slug.length <= 64;
|
|
12
|
+
/** Only status fields own reads. StrictMode replays the same mount token; a
|
|
13
|
+
* different owner after the last cleanup starts fresh, even in the same commit.
|
|
14
|
+
* Deferred eviction removes every entry with no live owner after that commit.
|
|
15
|
+
*/ export function useBlockStatus(api, slug, owner = false) {
|
|
16
|
+
const key = validBlockSlug(slug) ? keyOf(api, slug) : undefined;
|
|
17
|
+
const token = useRef({});
|
|
18
|
+
const subscribe = useCallback((listener)=>{
|
|
19
|
+
if (!key) return ()=>{};
|
|
20
|
+
let current = listeners.get(key);
|
|
21
|
+
if (!current) {
|
|
22
|
+
current = {
|
|
23
|
+
callbacks: new Set()
|
|
24
|
+
};
|
|
25
|
+
listeners.set(key, current);
|
|
26
|
+
}
|
|
27
|
+
current.callbacks.add(listener);
|
|
28
|
+
return ()=>{
|
|
29
|
+
current.callbacks = new Set([
|
|
30
|
+
...current.callbacks
|
|
31
|
+
].filter((item)=>item !== listener));
|
|
32
|
+
if (!current.callbacks.size) listeners.delete(key);
|
|
33
|
+
};
|
|
34
|
+
}, [
|
|
35
|
+
key
|
|
36
|
+
]);
|
|
37
|
+
const status = useSyncExternalStore(subscribe, ()=>{
|
|
38
|
+
const current = key ? cache.get(key) : undefined;
|
|
39
|
+
// A new owner must not sync with the previous mount's snapshot before its
|
|
40
|
+
// effect registers ownership (or starts the fresh read).
|
|
41
|
+
return current && (!owner || current.mounts.has(token.current)) ? current.value : null;
|
|
42
|
+
}, ()=>null);
|
|
43
|
+
useEffect(()=>{
|
|
44
|
+
if (!key || !owner || !validBlockSlug(slug)) return;
|
|
45
|
+
let current = cache.get(key);
|
|
46
|
+
if (!current || !current.owners && !current.mounts.has(token.current)) {
|
|
47
|
+
current = {
|
|
48
|
+
value: null,
|
|
49
|
+
owners: 0,
|
|
50
|
+
mounts: new WeakSet()
|
|
51
|
+
};
|
|
52
|
+
cache.set(key, current);
|
|
53
|
+
}
|
|
54
|
+
const owned = current;
|
|
55
|
+
owned.owners++;
|
|
56
|
+
owned.mounts.add(token.current);
|
|
57
|
+
publish(key);
|
|
58
|
+
if (!owned.pending) owned.pending = createApi(api).placementStatus({
|
|
59
|
+
slugs: [
|
|
60
|
+
slug
|
|
61
|
+
]
|
|
62
|
+
}).then((result)=>{
|
|
63
|
+
if (cache.get(key) !== owned) return;
|
|
64
|
+
owned.value = result.ok ? result.data?.slugs[slug] ?? null : null;
|
|
65
|
+
publish(key);
|
|
66
|
+
});
|
|
67
|
+
return ()=>{
|
|
68
|
+
owned.owners--;
|
|
69
|
+
queueMicrotask(()=>{
|
|
70
|
+
if (!owned.owners && cache.get(key) === owned) {
|
|
71
|
+
cache.delete(key);
|
|
72
|
+
publish(key);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
}, [
|
|
77
|
+
api,
|
|
78
|
+
key,
|
|
79
|
+
slug,
|
|
80
|
+
owner
|
|
81
|
+
]);
|
|
82
|
+
return status;
|
|
83
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useConfig, useDocumentInfo, useForm } from '@payloadcms/ui';
|
|
2
|
+
import { adaptClientBlocksField, rowLocation } from './formApi.js';
|
|
3
|
+
import type { ClientCollectionConfig } from 'payload';
|
|
4
|
+
import type { ExperimentBlock, HostBlock } from '../../blocks/model.js';
|
|
5
|
+
/** Declared so the d.ts build can name Payload UI's form types through public exports. */
|
|
6
|
+
export type BlockFormApi = {
|
|
7
|
+
form: ReturnType<typeof useForm>;
|
|
8
|
+
document: ReturnType<typeof useDocumentInfo>;
|
|
9
|
+
config: ReturnType<typeof useConfig>['config'];
|
|
10
|
+
collection: ClientCollectionConfig | undefined;
|
|
11
|
+
field: ReturnType<typeof adaptClientBlocksField> | undefined;
|
|
12
|
+
block: ExperimentBlock | undefined;
|
|
13
|
+
manager: boolean;
|
|
14
|
+
replace: (rows: HostBlock[]) => Promise<void>;
|
|
15
|
+
location: ReturnType<typeof rowLocation>;
|
|
16
|
+
};
|
|
17
|
+
export declare function useBlockForm(path: string): BlockFormApi;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useAuth, useConfig, useDocumentInfo, useForm, useFormFields, useServerFunctions } from '@payloadcms/ui';
|
|
3
|
+
import { adaptClientBlocksField, findClientBlocksField, replaceBlockRow, rowLocation } from './formApi.js';
|
|
4
|
+
export function useBlockForm(path) {
|
|
5
|
+
const form = useForm(), document = useDocumentInfo(), { config, getEntityConfig } = useConfig();
|
|
6
|
+
const { getFormState } = useServerFunctions(), { permissions } = useAuth();
|
|
7
|
+
// useForm's getters are stable; subscribe so changed slug/row paths are read again.
|
|
8
|
+
useFormFields(([fields])=>fields);
|
|
9
|
+
const location = rowLocation(path);
|
|
10
|
+
const collection = document.collectionSlug ? getEntityConfig({
|
|
11
|
+
collectionSlug: document.collectionSlug
|
|
12
|
+
}) : undefined;
|
|
13
|
+
const clientField = location && collection ? findClientBlocksField(collection.fields, location.field) : undefined;
|
|
14
|
+
const field = useMemo(()=>clientField ? adaptClientBlocksField(clientField, config) : undefined, [
|
|
15
|
+
clientField,
|
|
16
|
+
config
|
|
17
|
+
]);
|
|
18
|
+
const block = location ? form.getDataByPath(`${location.field}.${location.index}`) : undefined;
|
|
19
|
+
const testPermissions = permissions?.collections?.['xp-experiments'];
|
|
20
|
+
const manager = testPermissions?.update === true;
|
|
21
|
+
async function replace(rows) {
|
|
22
|
+
if (!location || !document.collectionSlug) return;
|
|
23
|
+
await replaceBlockRow({
|
|
24
|
+
form,
|
|
25
|
+
getFormState,
|
|
26
|
+
collectionSlug: document.collectionSlug,
|
|
27
|
+
id: document.id,
|
|
28
|
+
...location,
|
|
29
|
+
rows,
|
|
30
|
+
expectedBlock: block,
|
|
31
|
+
docPermissions: document.docPermissions,
|
|
32
|
+
docPreferences: await document.getDocPreferences?.()
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
form,
|
|
37
|
+
document,
|
|
38
|
+
config,
|
|
39
|
+
collection,
|
|
40
|
+
field,
|
|
41
|
+
block,
|
|
42
|
+
manager,
|
|
43
|
+
replace,
|
|
44
|
+
location
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AddMetricWarning } from '../../types/endpoints.js';
|
|
1
2
|
export declare const ACTIONS_COPY: {
|
|
2
3
|
readonly start: "Start";
|
|
3
4
|
readonly restart: "Start a new round";
|
|
@@ -70,3 +71,4 @@ export declare const CONTENT_ENTRY_REFUSALS: {
|
|
|
70
71
|
};
|
|
71
72
|
readonly fallback: "This page can't be changed right now. Reload to see its current state.";
|
|
72
73
|
};
|
|
74
|
+
export declare function addMetricWarningCopy(warning: AddMetricWarning): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { asymmetricGoalsCopy, placementUncheckedCopy } from './readiness.js';
|
|
1
2
|
export const ACTIONS_COPY = {
|
|
2
3
|
start: 'Start',
|
|
3
4
|
restart: 'Start a new round',
|
|
@@ -70,3 +71,11 @@ export const CONTENT_ENTRY_REFUSALS = {
|
|
|
70
71
|
},
|
|
71
72
|
fallback: ACTIONS_COPY.contentEntryFallback
|
|
72
73
|
};
|
|
74
|
+
export function addMetricWarningCopy(warning) {
|
|
75
|
+
if (warning.code === 'goal-added-mid-test') return "Clicks before this goal was added aren't counted (the same for every version).";
|
|
76
|
+
if (warning.code === 'placement-unchecked') {
|
|
77
|
+
const path = warning.detail?.path;
|
|
78
|
+
return placementUncheckedCopy(typeof path === 'string' ? path : 'This page');
|
|
79
|
+
}
|
|
80
|
+
return asymmetricGoalsCopy(warning.detail);
|
|
81
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { ArmIssueCode } from '../../types/endpoints.js';
|
|
2
1
|
export { ARM_LABELS as GROUP_LABELS } from '../../labels/arms.js';
|
|
3
|
-
export
|
|
2
|
+
export { ISSUE_COPY, issueMessage } from '../../arms/copy.js';
|
|
4
3
|
export declare const ARMS_COPY: {
|
|
5
4
|
readonly holdout: "The holdout sees the Original and is measured but not compared during the test. After you decide, it keeps seeing the Original so the winner can be compared with it.";
|
|
6
5
|
readonly pageRequired: "Enter a page path for this group.";
|
|
@@ -12,7 +11,3 @@ export declare const ARMS_COPY: {
|
|
|
12
11
|
readonly paramOption: "Choose an available option for {label}.";
|
|
13
12
|
readonly paramCheckbox: "Choose yes or no for {label}.";
|
|
14
13
|
};
|
|
15
|
-
export declare function issueMessage(issue: {
|
|
16
|
-
code: string;
|
|
17
|
-
message: string;
|
|
18
|
-
}, total: number): string;
|
package/dist/admin/copy/arms.js
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
export { ARM_LABELS as GROUP_LABELS } from '../../labels/arms.js';
|
|
2
|
-
export
|
|
3
|
-
'weights-total': 'Weights must add up to 100%. They add up to {total}% now.',
|
|
4
|
-
'control-count': 'Include exactly one Original.',
|
|
5
|
-
'control-first': 'Put the Original first.',
|
|
6
|
-
'holdout-last': 'Put the holdout last.',
|
|
7
|
-
'holdout-count': 'Include only one holdout.',
|
|
8
|
-
'variant-count': 'Include up to three variants.',
|
|
9
|
-
'duplicate-key': 'Each group must have a different standard name.',
|
|
10
|
-
'duplicate-slot': 'Each group must have a different content slot.',
|
|
11
|
-
'slot-required': 'Choose the matching content slot for this group.',
|
|
12
|
-
'invalid-key': 'Use the standard group names: Original, Variant A–C, Holdout.'
|
|
13
|
-
};
|
|
2
|
+
export { ISSUE_COPY, issueMessage } from '../../arms/copy.js';
|
|
14
3
|
export const ARMS_COPY = {
|
|
15
4
|
holdout: 'The holdout sees the Original and is measured but not compared during the test. After you decide, it keeps seeing the Original so the winner can be compared with it.',
|
|
16
5
|
pageRequired: 'Enter a page path for this group.',
|
|
@@ -22,7 +11,3 @@ export const ARMS_COPY = {
|
|
|
22
11
|
paramOption: 'Choose an available option for {label}.',
|
|
23
12
|
paramCheckbox: 'Choose yes or no for {label}.'
|
|
24
13
|
};
|
|
25
|
-
export function issueMessage(issue, total) {
|
|
26
|
-
const copy = Object.hasOwn(ISSUE_COPY, issue.code) ? ISSUE_COPY[issue.code] : issue.message;
|
|
27
|
-
return copy.replace('{total}', String(total));
|
|
28
|
-
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const BLOCK_COPY: {
|
|
2
|
+
testBlock: string;
|
|
3
|
+
pick: string;
|
|
4
|
+
empty: string;
|
|
5
|
+
blockChanged: string;
|
|
6
|
+
replace: string;
|
|
7
|
+
cancel: string;
|
|
8
|
+
retry: string;
|
|
9
|
+
saveFirst: string;
|
|
10
|
+
setup: string;
|
|
11
|
+
copy: string;
|
|
12
|
+
confirmCopy: string;
|
|
13
|
+
unwrap: string;
|
|
14
|
+
deleted: string;
|
|
15
|
+
discarded: string;
|
|
16
|
+
running: string;
|
|
17
|
+
stopped: string;
|
|
18
|
+
slotLabels: Record<string, string>;
|
|
19
|
+
wrapRefusals: Record<string, string>;
|
|
20
|
+
};
|
|
21
|
+
export declare const blockPosition: (label: string, position: number) => string;
|
|
22
|
+
export declare const blockTestName: (title: string, label: string) => string;
|
|
23
|
+
export declare const blockDraftStatus: (name: string) => string;
|
|
24
|
+
export declare const blockDecidedStatus: (winner: string, rollout: boolean) => string;
|
|
25
|
+
export declare const blockFinishedStatus: (winner: string) => string;
|
|
26
|
+
export declare const blockSaveNotice: (drafts: boolean) => "Save a draft of this page to add the test." | "Save this page to add the test.";
|
|
27
|
+
export declare const blockSyncNotice: (drafts: boolean) => "Versions were updated to match the test. Save draft to keep them." | "Versions were updated to match the test. Save to keep them.";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const BLOCK_COPY = {
|
|
2
|
+
testBlock: 'Test a block on this page',
|
|
3
|
+
pick: 'Choose a block',
|
|
4
|
+
empty: 'There are no blocks on this page to test yet. Add one, save, then come back.',
|
|
5
|
+
blockChanged: 'This block changed while you were working. Nothing was replaced. Try again.',
|
|
6
|
+
replace: 'Replace',
|
|
7
|
+
cancel: 'Cancel',
|
|
8
|
+
retry: 'Try again',
|
|
9
|
+
saveFirst: 'Save your changes, then try again.',
|
|
10
|
+
setup: 'Set up the test',
|
|
11
|
+
copy: 'Copy the Original into this version',
|
|
12
|
+
confirmCopy: 'Replace this version’s content with a copy of the Original? Edits you made to this version will be lost.',
|
|
13
|
+
unwrap: 'Unwrap',
|
|
14
|
+
deleted: "This block's test was deleted; the page shows the Original.",
|
|
15
|
+
discarded: 'This test has ended; the page shows the Original.',
|
|
16
|
+
running: "Running: visitors see different versions. Edit a version's tab to change it; the change is noted on the results.",
|
|
17
|
+
stopped: 'Stopped: visitors see the Original.',
|
|
18
|
+
slotLabels: {
|
|
19
|
+
control: 'Original',
|
|
20
|
+
variantA: 'Variant A',
|
|
21
|
+
variantB: 'Variant B',
|
|
22
|
+
variantC: 'Variant C'
|
|
23
|
+
},
|
|
24
|
+
wrapRefusals: {
|
|
25
|
+
'wrap-target-missing': 'Reload the page and try again.',
|
|
26
|
+
'locked-placement': 'Another editor has this page open. Try again when it is unlocked.',
|
|
27
|
+
'invalid-status': 'This action is not available in the current status.',
|
|
28
|
+
'unknown-collection': "That page type isn't set up for tests.",
|
|
29
|
+
'invalid-body': 'Choose a block test.'
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
export const blockPosition = (label, position)=>`${label} (block ${position})`;
|
|
33
|
+
export const blockTestName = (title, label)=>`${title}: ${label}`;
|
|
34
|
+
export const blockDraftStatus = (name)=>`This block is being tested in the draft test ${name}. Visitors see the Original until it starts.`;
|
|
35
|
+
export const blockDecidedStatus = (winner, rollout)=>`Decided: visitors see ${winner}${rollout ? '; a holdout still sees the Original' : ''}.`;
|
|
36
|
+
export const blockFinishedStatus = (winner)=>`Finished: visitors see ${winner}. Use "Make it the content" on the test to replace this block.`;
|
|
37
|
+
export const blockSaveNotice = (drafts)=>drafts ? 'Save a draft of this page to add the test.' : 'Save this page to add the test.';
|
|
38
|
+
export const blockSyncNotice = (drafts)=>drafts ? 'Versions were updated to match the test. Save draft to keep them.' : 'Versions were updated to match the test. Save to keep them.';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const GOALS_COPY: {
|
|
2
|
+
readonly slugRule: "Use 1–64 lowercase letters, numbers, underscores or hyphens, starting with a letter or number.";
|
|
3
|
+
readonly duplicateSlug: "Another goal already uses this short name. Change it under Advanced.";
|
|
2
4
|
readonly title: "Goals";
|
|
3
5
|
readonly goal: "Goal";
|
|
4
6
|
readonly newGoal: "New goal";
|
package/dist/admin/copy/goals.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { CLICK_GOAL_COPY } from '../../goals/picker/copy.js';
|
|
1
2
|
export const GOALS_COPY = {
|
|
3
|
+
slugRule: CLICK_GOAL_COPY.slugRule,
|
|
4
|
+
duplicateSlug: CLICK_GOAL_COPY.duplicateSlug,
|
|
2
5
|
title: 'Goals',
|
|
3
6
|
goal: 'Goal',
|
|
4
7
|
newGoal: 'New goal',
|
|
@@ -38,7 +41,7 @@ export const GOALS_COPY = {
|
|
|
38
41
|
remove: 'Delete goal',
|
|
39
42
|
thresholdError: 'Enter a percentage of zero or more, or leave blank for 5%.',
|
|
40
43
|
deleting: 'Deleting…',
|
|
41
|
-
saveError:
|
|
44
|
+
saveError: CLICK_GOAL_COPY.saveError,
|
|
42
45
|
deleteError: "Couldn't delete the goal. Try again.",
|
|
43
46
|
breadcrumb: 'Breadcrumb',
|
|
44
47
|
missing: 'This goal no longer exists.',
|
|
@@ -2,6 +2,16 @@ export declare const NEW_TEST_COPY: {
|
|
|
2
2
|
readonly question: "What do you want to test?";
|
|
3
3
|
readonly block: "A block on a page";
|
|
4
4
|
readonly setting: "A setting";
|
|
5
|
+
readonly page: "A whole page";
|
|
6
|
+
readonly pageMeaning: "Show different versions of a whole page at the same address, and see which one works better.";
|
|
7
|
+
readonly collection: "Collection";
|
|
8
|
+
readonly original: "Original";
|
|
9
|
+
readonly originalHelp: "Choose the page visitors see today.";
|
|
10
|
+
readonly review: "Review";
|
|
11
|
+
readonly backToTest: "Back to the test";
|
|
12
|
+
readonly finishSetup: "Finish setup";
|
|
13
|
+
readonly start: "Start";
|
|
14
|
+
readonly blocksHandoff: "Pick the block to test with 'Test a block on this page'.";
|
|
5
15
|
readonly blockMeaning: "Compare different versions of content on a page.";
|
|
6
16
|
readonly settingMeaning: "Compare different choices for a setting on your site.";
|
|
7
17
|
readonly unavailable: "A developer needs to set up pages or settings before you can create a test.";
|
|
@@ -2,6 +2,16 @@ export const NEW_TEST_COPY = {
|
|
|
2
2
|
question: 'What do you want to test?',
|
|
3
3
|
block: 'A block on a page',
|
|
4
4
|
setting: 'A setting',
|
|
5
|
+
page: 'A whole page',
|
|
6
|
+
pageMeaning: 'Show different versions of a whole page at the same address, and see which one works better.',
|
|
7
|
+
collection: 'Collection',
|
|
8
|
+
original: 'Original',
|
|
9
|
+
originalHelp: 'Choose the page visitors see today.',
|
|
10
|
+
review: 'Review',
|
|
11
|
+
backToTest: 'Back to the test',
|
|
12
|
+
finishSetup: 'Finish setup',
|
|
13
|
+
start: 'Start',
|
|
14
|
+
blocksHandoff: "Pick the block to test with 'Test a block on this page'.",
|
|
5
15
|
blockMeaning: 'Compare different versions of content on a page.',
|
|
6
16
|
settingMeaning: 'Compare different choices for a setting on your site.',
|
|
7
17
|
unavailable: 'A developer needs to set up pages or settings before you can create a test.',
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const PAGE_COPY: {
|
|
2
|
+
readonly search: "Search pages by title";
|
|
3
|
+
readonly more: "More";
|
|
4
|
+
readonly loading: "Loading pages…";
|
|
5
|
+
readonly empty: "There are no pages here yet.";
|
|
6
|
+
readonly error: "Couldn't load pages. Try again.";
|
|
7
|
+
readonly retry: "Try again";
|
|
8
|
+
readonly draft: "Draft";
|
|
9
|
+
readonly noPath: "This page has no address yet.";
|
|
10
|
+
readonly choose: "Choose a page";
|
|
11
|
+
readonly change: "Change";
|
|
12
|
+
readonly close: "Close";
|
|
13
|
+
readonly where: "Where it shows";
|
|
14
|
+
readonly open: "Open";
|
|
15
|
+
readonly preview: "Preview";
|
|
16
|
+
readonly contentChecksOff: "Content checks are off for this page type.";
|
|
17
|
+
readonly duplicateStep: "Choose Duplicate from the document's actions. Keep the Original's title on the copy, unless the title is what you're testing.";
|
|
18
|
+
readonly ownAddress: "Give the copy its own address and save it as a draft. Publish it when it's ready; the test can't start until it is.";
|
|
19
|
+
readonly thenChoose: "Then choose the page you made.";
|
|
20
|
+
readonly noPage: "No page chosen yet.";
|
|
21
|
+
};
|
|
22
|
+
export declare const usedPageCopy: (label: string) => string;
|
|
23
|
+
export declare const usedAddressCopy: (label: string, path: string, collection: string) => string;
|
|
24
|
+
export declare const openOriginalCopy: (collection: string) => string;
|
|
25
|
+
export declare const openCollectionCopy: (collection: string) => string;
|
|
26
|
+
export declare const pageStepCopy: (label: string) => string;
|