@webiny/app-headless-cms 6.5.0-beta.1 → 6.5.0-beta.10
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/ContentEntriesModule.js +14 -2
- package/ContentEntriesModule.js.map +1 -1
- package/admin/components/ContentEntries/BulkActions/ActionDelete.d.ts +2 -2
- package/admin/components/ContentEntries/BulkActions/ActionDelete.js.map +1 -1
- package/admin/components/ContentEntries/BulkActions/ActionMove.d.ts +2 -2
- package/admin/components/ContentEntries/BulkActions/ActionMove.js.map +1 -1
- package/admin/components/ContentEntries/BulkActions/ActionPublish.d.ts +2 -2
- package/admin/components/ContentEntries/BulkActions/ActionPublish.js.map +1 -1
- package/admin/components/ContentEntries/BulkActions/ActionUnpublish.d.ts +2 -2
- package/admin/components/ContentEntries/BulkActions/ActionUnpublish.js.map +1 -1
- package/admin/components/ContentEntries/Filters/RefFieldRenderer.d.ts +2 -0
- package/admin/components/ContentEntries/Filters/RefFieldRenderer.js +84 -0
- package/admin/components/ContentEntries/Filters/RefFieldRenderer.js.map +1 -0
- package/admin/components/ContentEntries/Table/Actions/DuplicateEntry.d.ts +2 -0
- package/admin/components/ContentEntries/Table/Actions/DuplicateEntry.js +34 -0
- package/admin/components/ContentEntries/Table/Actions/DuplicateEntry.js.map +1 -0
- package/admin/components/ContentEntries/Table/Actions/index.d.ts +1 -0
- package/admin/components/ContentEntries/Table/Actions/index.js +1 -0
- package/admin/components/ContentModelEditor/ContentModelEditor.js +1 -1
- package/admin/components/ContentModelEditor/ContentModelEditor.js.map +1 -1
- package/admin/components/Dialogs/DuplicateEntryConfirmDialog.d.ts +2 -0
- package/admin/components/Dialogs/DuplicateEntryConfirmDialog.js +22 -0
- package/admin/components/Dialogs/DuplicateEntryConfirmDialog.js.map +1 -0
- package/admin/components/FieldEditor/EditFieldDialog.d.ts +2 -2
- package/admin/components/FieldEditor/Field.js +4 -4
- package/admin/components/FieldEditor/Field.js.map +1 -1
- package/features/contentEntry/CmsEntryError.d.ts +10 -0
- package/features/contentEntry/CmsEntryError.js +11 -0
- package/features/contentEntry/CmsEntryError.js.map +1 -0
- package/features/contentEntry/createEntry/CreateEntryGateway.d.ts +0 -1
- package/features/contentEntry/createEntry/CreateEntryGateway.js +3 -10
- package/features/contentEntry/createEntry/CreateEntryGateway.js.map +1 -1
- package/features/contentEntry/createRevisionFrom/CreateRevisionFromGateway.d.ts +3 -1
- package/features/contentEntry/createRevisionFrom/CreateRevisionFromGateway.js +8 -3
- package/features/contentEntry/createRevisionFrom/CreateRevisionFromGateway.js.map +1 -1
- package/features/contentEntry/duplicateEntry/DuplicateEntryGateway.d.ts +14 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryGateway.js +43 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryGateway.js.map +1 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryRepository.d.ts +15 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryRepository.js +34 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryRepository.js.map +1 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryUseCase.d.ts +11 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryUseCase.js +18 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryUseCase.js.map +1 -0
- package/features/contentEntry/duplicateEntry/abstractions.d.ts +26 -0
- package/features/contentEntry/duplicateEntry/abstractions.js +7 -0
- package/features/contentEntry/duplicateEntry/abstractions.js.map +1 -0
- package/features/contentEntry/duplicateEntry/feature.d.ts +3 -0
- package/features/contentEntry/duplicateEntry/feature.js +21 -0
- package/features/contentEntry/duplicateEntry/feature.js.map +1 -0
- package/features/contentEntry/entryValueGateways.test.d.ts +1 -0
- package/features/contentEntry/entryValueGateways.test.js +106 -0
- package/features/contentEntry/entryValueGateways.test.js.map +1 -0
- package/features/contentEntry/feature.js +2 -0
- package/features/contentEntry/feature.js.map +1 -1
- package/features/contentEntry/singletonEntry/UpdateSingletonEntryGateway.d.ts +0 -1
- package/features/contentEntry/singletonEntry/UpdateSingletonEntryGateway.js +3 -10
- package/features/contentEntry/singletonEntry/UpdateSingletonEntryGateway.js.map +1 -1
- package/features/contentEntry/updateEntry/UpdateEntryGateway.d.ts +0 -1
- package/features/contentEntry/updateEntry/UpdateEntryGateway.js +3 -10
- package/features/contentEntry/updateEntry/UpdateEntryGateway.js.map +1 -1
- package/features/contentEntry/valueTransformers/EntryDataPreparer.d.ts +9 -0
- package/features/contentEntry/valueTransformers/EntryDataPreparer.js +8 -0
- package/features/contentEntry/valueTransformers/EntryDataPreparer.js.map +1 -1
- package/features/contentEntry/valueTransformers/EntryDataPreparer.test.d.ts +1 -0
- package/features/contentEntry/valueTransformers/EntryDataPreparer.test.js +113 -0
- package/features/contentEntry/valueTransformers/EntryDataPreparer.test.js.map +1 -0
- package/features/contentEntry/valueTransformers/entryValueFixtures.d.ts +146 -0
- package/features/contentEntry/valueTransformers/entryValueFixtures.js +171 -0
- package/features/contentEntry/valueTransformers/entryValueFixtures.js.map +1 -0
- package/features/formModel/CmsFormModelBuilder.d.ts +9 -1
- package/features/formModel/CmsFormModelBuilder.js +26 -15
- package/features/formModel/CmsFormModelBuilder.js.map +1 -1
- package/features/formModel/CmsFormModelBuilder.test.d.ts +1 -0
- package/features/formModel/CmsFormModelBuilder.test.js +252 -0
- package/features/formModel/CmsFormModelBuilder.test.js.map +1 -0
- package/features/formModel/CmsLayoutMapper.d.ts +8 -1
- package/features/formModel/CmsLayoutMapper.js +23 -1
- package/features/formModel/CmsLayoutMapper.js.map +1 -1
- package/features/formModel/mappers/DynamicZoneFieldMapper.js +4 -0
- package/features/formModel/mappers/DynamicZoneFieldMapper.js.map +1 -1
- package/features/formModel/mappers/ObjectFieldMapper.js +11 -6
- package/features/formModel/mappers/ObjectFieldMapper.js.map +1 -1
- package/features/model/exportModels/ExportModelsGateway.d.ts +1 -1
- package/features/model/exportModels/ExportModelsGateway.js.map +1 -1
- package/features/model/exportModels/ExportModelsUseCase.js +7 -1
- package/features/model/exportModels/ExportModelsUseCase.js.map +1 -1
- package/features/model/exportModels/abstractions.d.ts +8 -2
- package/features/model/exportModels/abstractions.js.map +1 -1
- package/package.json +26 -26
- package/presentation/contentEntries/form/ContentEntryFormPresenter.js +34 -29
- package/presentation/contentEntries/form/ContentEntryFormPresenter.js.map +1 -1
- package/presentation/contentEntries/form/ContentEntryFormPresenter.test.d.ts +1 -0
- package/presentation/contentEntries/form/ContentEntryFormPresenter.test.js +210 -0
- package/presentation/contentEntries/form/ContentEntryFormPresenter.test.js.map +1 -0
- package/presentation/contentEntries/form/toFormErrors.d.ts +7 -0
- package/presentation/contentEntries/form/toFormErrors.js +49 -0
- package/presentation/contentEntries/form/toFormErrors.js.map +1 -0
- package/presentation/contentEntries/form/toFormErrors.test.d.ts +1 -0
- package/presentation/contentEntries/form/toFormErrors.test.js +175 -0
- package/presentation/contentEntries/form/toFormErrors.test.js.map +1 -0
- package/presentation/contentEntries/list/ContentEntriesPresenter.d.ts +6 -2
- package/presentation/contentEntries/list/ContentEntriesPresenter.js +27 -2
- package/presentation/contentEntries/list/ContentEntriesPresenter.js.map +1 -1
- package/presentation/contentEntries/list/ContentEntriesPresenter.test.js +58 -1
- package/presentation/contentEntries/list/ContentEntriesPresenter.test.js.map +1 -1
- package/presentation/contentEntries/list/abstractions.d.ts +12 -0
- package/presentation/contentEntries/list/abstractions.js.map +1 -1
- package/presentation/contentEntries/preview/LivePreviewPresenter.d.ts +12 -1
- package/presentation/contentEntries/preview/LivePreviewPresenter.js +48 -5
- package/presentation/contentEntries/preview/LivePreviewPresenter.js.map +1 -1
- package/presentation/contentEntries/preview/LivePreviewPresenter.test.d.ts +1 -0
- package/presentation/contentEntries/preview/LivePreviewPresenter.test.js +81 -0
- package/presentation/contentEntries/preview/LivePreviewPresenter.test.js.map +1 -0
- package/presentation/contentEntries/preview/ModelEditorComponentDiscovery.js +1 -1
- package/presentation/contentEntries/preview/ModelEditorComponentDiscovery.js.map +1 -1
- package/presentation/contentEntries/preview/PreviewDecorator.js +25 -6
- package/presentation/contentEntries/preview/PreviewDecorator.js.map +1 -1
- package/presentation/contentEntries/preview/PreviewDomainStatus.d.ts +10 -0
- package/presentation/contentEntries/preview/PreviewDomainStatus.js +19 -0
- package/presentation/contentEntries/preview/PreviewDomainStatus.js.map +1 -0
- package/presentation/contentEntries/preview/PreviewPane.d.ts +10 -1
- package/presentation/contentEntries/preview/PreviewPane.js +81 -45
- package/presentation/contentEntries/preview/PreviewPane.js.map +1 -1
- package/presentation/contentEntries/preview/abstractions.d.ts +8 -0
- package/presentation/contentEntries/preview/abstractions.js.map +1 -1
- package/presentation/contentEntries/preview/resolvePreviewUrl.d.ts +21 -0
- package/presentation/contentEntries/preview/resolvePreviewUrl.js +50 -1
- package/presentation/contentEntries/preview/resolvePreviewUrl.js.map +1 -1
- package/presentation/contentEntries/preview/resolvePreviewUrl.test.d.ts +1 -0
- package/presentation/contentEntries/preview/resolvePreviewUrl.test.js +127 -0
- package/presentation/contentEntries/preview/resolvePreviewUrl.test.js.map +1 -0
- package/presentation/contentEntries/singleEntry/SingleEntryPresenter.js +9 -4
- package/presentation/contentEntries/singleEntry/SingleEntryPresenter.js.map +1 -1
- package/presentation/contentEntries/singleEntry/SingleEntryPresenter.test.d.ts +1 -0
- package/presentation/contentEntries/singleEntry/SingleEntryPresenter.test.js +122 -0
- package/presentation/contentEntries/singleEntry/SingleEntryPresenter.test.js.map +1 -0
- package/presentation/contentEntries/trashBin/CmsTrashBin.d.ts +2 -2
- package/presentation/contentEntries/views/CmsAdvancedSearch.d.ts +2 -2
- package/presentation/contentEntries/views/ContentEntriesListLayout.d.ts +2 -2
- package/presentation/contentEntries/views/ContentEntryFormView.d.ts +2 -2
- package/presentation/contentEntries/views/OlderRevisionAlert.d.ts +2 -2
- package/presentation/contentEntries/views/RevisionDrawer.d.ts +2 -2
- package/presentation/contentEntries/views/RevisionsList.d.ts +2 -2
- package/presentation/contentEntries/views/SingleEntryLayout.d.ts +2 -2
- package/presentation/contentEntries/views/Table/Table.d.ts +2 -2
- package/presentation/contentEntries/views/actions/CreateNewRevisionButton.d.ts +2 -2
- package/presentation/contentEntries/views/actions/DeleteEntryMenuItem.d.ts +2 -2
- package/presentation/contentEntries/views/actions/PublishOnlyButton.d.ts +2 -2
- package/presentation/contentEntries/views/actions/RevisionSelector.d.ts +2 -2
- package/presentation/contentEntries/views/actions/SaveAndPublishButton.d.ts +2 -2
- package/presentation/contentEntries/views/actions/SaveContentButton.d.ts +2 -2
- package/presentation/contentEntries/views/actions/ShowRevisionListMenuItem.d.ts +2 -2
- package/presentation/contentEntries/views/layout/ContentEntryForm.d.ts +12 -12
- package/presentation/contentModels/ContentModelsPresenter.d.ts +5 -5
- package/presentation/contentModels/ContentModelsPresenter.js +1 -1
- package/presentation/contentModels/ContentModelsPresenter.js.map +1 -1
- package/presentation/contentModels/abstractions.d.ts +7 -2
- package/presentation/contentModels/abstractions.js.map +1 -1
- package/presentation/contentModels/components/CloneContentModelDialog.d.ts +2 -2
- package/presentation/contentModels/components/ContentModels.d.ts +2 -2
- package/presentation/contentModels/components/ContentModelsDataList.d.ts +2 -2
- package/presentation/contentModels/components/ContentModelsDataList.js +6 -5
- package/presentation/contentModels/components/ContentModelsDataList.js.map +1 -1
- package/presentation/contentModels/components/NewContentModelDialog.d.ts +2 -2
- package/presentation/contentModels/components/fullDelete/dialog/Content.js +2 -2
- package/presentation/contentModels/components/fullDelete/dialog/Content.js.map +1 -1
- package/presentation/contentModels/components/fullDelete/types.js.map +1 -1
- package/presentation/fieldRenderers/ref/autocomplete/RefAutocompletePresenters.test.d.ts +1 -0
- package/presentation/fieldRenderers/ref/autocomplete/RefAutocompletePresenters.test.js +133 -0
- package/presentation/fieldRenderers/ref/autocomplete/RefAutocompletePresenters.test.js.map +1 -0
- package/presentation/fieldRenderers/ref/autocomplete/multi/RefMultiAutocompletePresenter.js +1 -1
- package/presentation/fieldRenderers/ref/autocomplete/multi/RefMultiAutocompletePresenter.js.map +1 -1
- package/presentation/fieldRenderers/ref/autocomplete/single/RefSingleAutocompletePresenter.js +1 -1
- package/presentation/fieldRenderers/ref/autocomplete/single/RefSingleAutocompletePresenter.js.map +1 -1
- package/presentation/fieldRenderers/ref/components/RefFieldOptions.d.ts +2 -2
- package/presentation/fieldRenderers/ref/components/RefFieldOptions.js +3 -2
- package/presentation/fieldRenderers/ref/components/RefFieldOptions.js.map +1 -1
- package/presentation/importContentModels/ImportContentModelsDialog.d.ts +2 -2
- package/presentation/importContentModels/ImportContentModelsPresenter.js +1 -1
- package/presentation/importContentModels/ImportContentModelsPresenter.js.map +1 -1
- package/presentation/importContentModels/components/DataList.d.ts +2 -2
- package/presentation/importContentModels/components/FileUpload.d.ts +2 -2
- package/presentation/importContentModels/components/FileUpload.js +1 -1
- package/presentation/importContentModels/components/FileUpload.js.map +1 -1
- package/presentation/importContentModels/components/Model/DataListModelItem.js +3 -3
- package/presentation/importContentModels/components/Model/DataListModelItem.js.map +1 -1
- package/presentation/importContentModels/components/Model/DataListModelItemInfo.js +2 -2
- package/presentation/importContentModels/components/Model/DataListModelItemInfo.js.map +1 -1
- package/presentation/importContentModels/components/Model/DataListModels.d.ts +2 -2
- package/presentation/menus/ContentGroupsMenuItems.d.ts +2 -2
- package/presentation/modelGroup/ModelGroupPresenter.js +1 -2
- package/presentation/modelGroup/ModelGroupPresenter.js.map +1 -1
- package/presentation/modelGroup/components/ContentModelGroups.d.ts +2 -2
- package/presentation/modelGroup/components/ContentModelGroupsDataList.d.ts +6 -2
- package/presentation/modelGroup/components/ContentModelGroupsForm.d.ts +8 -2
- package/utils/normalizeIcon.d.ts +4 -0
- package/utils/normalizeIcon.js +9 -6
- package/utils/normalizeIcon.js.map +1 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Container } from "@webiny/di";
|
|
3
|
+
import { FormModelFactory } from "@webiny/app-admin/features/formModel/abstractions.js";
|
|
4
|
+
import { Confirmation } from "@webiny/app-admin/features/confirmation/abstractions.js";
|
|
5
|
+
import { GetEntryUseCase } from "../../../features/contentEntry/getEntry/abstractions.js";
|
|
6
|
+
import { CreateEntryUseCase } from "../../../features/contentEntry/createEntry/abstractions.js";
|
|
7
|
+
import { UpdateEntryUseCase } from "../../../features/contentEntry/updateEntry/abstractions.js";
|
|
8
|
+
import { PublishEntryUseCase } from "../../../features/contentEntry/publishEntry/abstractions.js";
|
|
9
|
+
import { UnpublishEntryUseCase } from "../../../features/contentEntry/unpublishEntry/abstractions.js";
|
|
10
|
+
import { DeleteEntryUseCase } from "../../../features/contentEntry/deleteEntry/abstractions.js";
|
|
11
|
+
import { UpdateRevisionDescriptionUseCase } from "../../../features/contentEntry/updateRevisionDescription/abstractions.js";
|
|
12
|
+
import { CreateRevisionFromUseCase } from "../../../features/contentEntry/createRevisionFrom/abstractions.js";
|
|
13
|
+
import { CmsFormModelBuilder } from "../../../features/formModel/abstractions.js";
|
|
14
|
+
import { CmsModelContext } from "../../../features/contentEntry/abstractions.js";
|
|
15
|
+
import { ContentEntryFormPresenter } from "./abstractions.js";
|
|
16
|
+
import { ContentEntryFormPresenter as external_ContentEntryFormPresenter_js_ContentEntryFormPresenter } from "./ContentEntryFormPresenter.js";
|
|
17
|
+
const createFakeForm = (validation)=>({
|
|
18
|
+
vm: {},
|
|
19
|
+
isDirty: false,
|
|
20
|
+
submit: vi.fn(async ()=>validation ? validation : {
|
|
21
|
+
title: "Draft"
|
|
22
|
+
}),
|
|
23
|
+
setData: vi.fn(),
|
|
24
|
+
reset: vi.fn(),
|
|
25
|
+
setErrors: vi.fn()
|
|
26
|
+
});
|
|
27
|
+
const deferred = ()=>{
|
|
28
|
+
let resolve;
|
|
29
|
+
let reject;
|
|
30
|
+
const promise = new Promise((res, rej)=>{
|
|
31
|
+
resolve = res;
|
|
32
|
+
reject = rej;
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
promise,
|
|
36
|
+
resolve,
|
|
37
|
+
reject
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const createEntry = (entryId)=>({
|
|
41
|
+
id: `${entryId}#0001`,
|
|
42
|
+
entryId,
|
|
43
|
+
meta: {
|
|
44
|
+
locked: false,
|
|
45
|
+
status: "draft",
|
|
46
|
+
title: entryId
|
|
47
|
+
},
|
|
48
|
+
values: {
|
|
49
|
+
title: entryId
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const setup = ()=>{
|
|
53
|
+
const forms = [];
|
|
54
|
+
const create = deferred();
|
|
55
|
+
const update = deferred();
|
|
56
|
+
let nextValidation;
|
|
57
|
+
const pendingLoads = new Map();
|
|
58
|
+
const createExecute = vi.fn(()=>create.promise);
|
|
59
|
+
const updateExecute = vi.fn(()=>update.promise);
|
|
60
|
+
const container = new Container();
|
|
61
|
+
container.registerInstance(FormModelFactory, {
|
|
62
|
+
create: ()=>{
|
|
63
|
+
const form = createFakeForm(nextValidation);
|
|
64
|
+
nextValidation = void 0;
|
|
65
|
+
forms.push(form);
|
|
66
|
+
return form;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
container.registerInstance(CmsFormModelBuilder, {
|
|
70
|
+
build: ()=>({})
|
|
71
|
+
});
|
|
72
|
+
container.registerInstance(CmsModelContext, {
|
|
73
|
+
getModel: ()=>({
|
|
74
|
+
modelId: "article",
|
|
75
|
+
fields: []
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
container.registerInstance(CreateEntryUseCase, {
|
|
79
|
+
execute: createExecute
|
|
80
|
+
});
|
|
81
|
+
container.registerInstance(UpdateEntryUseCase, {
|
|
82
|
+
execute: updateExecute
|
|
83
|
+
});
|
|
84
|
+
container.registerInstance(GetEntryUseCase, {
|
|
85
|
+
execute: async ({ id })=>pendingLoads.get(id) ?? createEntry(id)
|
|
86
|
+
});
|
|
87
|
+
for (const abstraction of [
|
|
88
|
+
Confirmation,
|
|
89
|
+
CreateRevisionFromUseCase,
|
|
90
|
+
PublishEntryUseCase,
|
|
91
|
+
UnpublishEntryUseCase,
|
|
92
|
+
DeleteEntryUseCase,
|
|
93
|
+
UpdateRevisionDescriptionUseCase
|
|
94
|
+
])container.registerInstance(abstraction, {});
|
|
95
|
+
container.register(external_ContentEntryFormPresenter_js_ContentEntryFormPresenter);
|
|
96
|
+
return {
|
|
97
|
+
presenter: container.resolve(ContentEntryFormPresenter),
|
|
98
|
+
forms,
|
|
99
|
+
create,
|
|
100
|
+
createExecute,
|
|
101
|
+
update,
|
|
102
|
+
updateExecute,
|
|
103
|
+
pendingLoads,
|
|
104
|
+
slowValidation: ()=>{
|
|
105
|
+
const validation = deferred();
|
|
106
|
+
nextValidation = validation.promise;
|
|
107
|
+
return validation;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
describe("ContentEntryFormPresenter.saveRevision", ()=>{
|
|
112
|
+
it("puts a rejected save's errors on the form", async ()=>{
|
|
113
|
+
const { presenter, forms, create } = setup();
|
|
114
|
+
presenter.newEntry();
|
|
115
|
+
const saving = presenter.saveRevision();
|
|
116
|
+
create.reject(new Error("Value must be unique."));
|
|
117
|
+
await expect(saving).resolves.toBe(false);
|
|
118
|
+
expect(forms[0].setErrors).toHaveBeenCalledTimes(1);
|
|
119
|
+
});
|
|
120
|
+
it("returns false instead of throwing when the form was reset mid-save", async ()=>{
|
|
121
|
+
const { presenter, create, createExecute } = setup();
|
|
122
|
+
presenter.newEntry();
|
|
123
|
+
const saving = presenter.saveRevision();
|
|
124
|
+
await vi.waitFor(()=>expect(createExecute).toHaveBeenCalled());
|
|
125
|
+
presenter.reset();
|
|
126
|
+
create.reject(new Error("Value must be unique."));
|
|
127
|
+
await expect(saving).resolves.toBe(false);
|
|
128
|
+
expect(presenter.vm.loading).toBe(null);
|
|
129
|
+
});
|
|
130
|
+
it("leaves a form built after the save started alone", async ()=>{
|
|
131
|
+
const { presenter, forms, create, createExecute } = setup();
|
|
132
|
+
presenter.newEntry();
|
|
133
|
+
const saving = presenter.saveRevision();
|
|
134
|
+
await vi.waitFor(()=>expect(createExecute).toHaveBeenCalled());
|
|
135
|
+
presenter.newEntry();
|
|
136
|
+
create.reject(new Error("Value must be unique."));
|
|
137
|
+
await expect(saving).resolves.toBe(false);
|
|
138
|
+
expect(forms).toHaveLength(2);
|
|
139
|
+
expect(forms[0].setErrors).not.toHaveBeenCalled();
|
|
140
|
+
expect(forms[1].setErrors).not.toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
it("saves the open entry and puts the saved values on the form", async ()=>{
|
|
143
|
+
const { presenter, forms, update, updateExecute } = setup();
|
|
144
|
+
await presenter.loadRevision("A");
|
|
145
|
+
const saving = presenter.saveRevision();
|
|
146
|
+
await vi.waitFor(()=>expect(updateExecute).toHaveBeenCalled());
|
|
147
|
+
update.resolve(createEntry("A"));
|
|
148
|
+
await expect(saving).resolves.toBe(true);
|
|
149
|
+
expect(updateExecute).toHaveBeenCalledWith(expect.objectContaining({
|
|
150
|
+
revisionId: "A#0001",
|
|
151
|
+
data: {
|
|
152
|
+
values: {
|
|
153
|
+
title: "Draft"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}));
|
|
157
|
+
expect(forms[0].setData).toHaveBeenLastCalledWith({
|
|
158
|
+
title: "A"
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
it("does not write to an entry opened while the form was validating", async ()=>{
|
|
162
|
+
const { presenter, update, updateExecute, slowValidation } = setup();
|
|
163
|
+
const validation = slowValidation();
|
|
164
|
+
await presenter.loadRevision("A");
|
|
165
|
+
const saving = presenter.saveRevision();
|
|
166
|
+
await presenter.loadRevision("B");
|
|
167
|
+
validation.resolve({
|
|
168
|
+
title: "Values typed into A"
|
|
169
|
+
});
|
|
170
|
+
update.resolve(createEntry("B"));
|
|
171
|
+
await expect(saving).resolves.toBe(false);
|
|
172
|
+
expect(updateExecute).not.toHaveBeenCalled();
|
|
173
|
+
expect(presenter.vm.entry?.entryId).toBe("B");
|
|
174
|
+
});
|
|
175
|
+
it("keeps a write on its entry and reports false when another entry was opened", async ()=>{
|
|
176
|
+
const { presenter, forms, update, updateExecute } = setup();
|
|
177
|
+
await presenter.loadRevision("A");
|
|
178
|
+
const saving = presenter.saveRevision();
|
|
179
|
+
await vi.waitFor(()=>expect(updateExecute).toHaveBeenCalled());
|
|
180
|
+
await presenter.loadRevision("B");
|
|
181
|
+
update.resolve(createEntry("A"));
|
|
182
|
+
await expect(saving).resolves.toBe(false);
|
|
183
|
+
expect(updateExecute).toHaveBeenCalledTimes(1);
|
|
184
|
+
expect(updateExecute).toHaveBeenCalledWith(expect.objectContaining({
|
|
185
|
+
revisionId: "A#0001"
|
|
186
|
+
}));
|
|
187
|
+
expect(presenter.vm.entry?.entryId).toBe("B");
|
|
188
|
+
expect(forms[1].setData).toHaveBeenCalledTimes(1);
|
|
189
|
+
expect(forms[1].setData).toHaveBeenCalledWith({
|
|
190
|
+
title: "B"
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
it("does not clear the loading message of an entry being opened", async ()=>{
|
|
194
|
+
const { presenter, update, updateExecute, pendingLoads } = setup();
|
|
195
|
+
await presenter.loadRevision("A");
|
|
196
|
+
const saving = presenter.saveRevision();
|
|
197
|
+
await vi.waitFor(()=>expect(updateExecute).toHaveBeenCalled());
|
|
198
|
+
const loadB = deferred();
|
|
199
|
+
pendingLoads.set("B", loadB.promise);
|
|
200
|
+
const loading = presenter.loadRevision("B");
|
|
201
|
+
update.resolve(createEntry("A"));
|
|
202
|
+
await expect(saving).resolves.toBe(true);
|
|
203
|
+
expect(presenter.vm.loading).toBe("Loading entry...");
|
|
204
|
+
loadB.resolve(createEntry("B"));
|
|
205
|
+
await loading;
|
|
206
|
+
expect(presenter.vm.loading).toBe(null);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
//# sourceMappingURL=ContentEntryFormPresenter.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/contentEntries/form/ContentEntryFormPresenter.test.js","sources":["../../../../src/presentation/contentEntries/form/ContentEntryFormPresenter.test.ts"],"sourcesContent":["// @vitest-environment jsdom\nimport { describe, it, expect, vi } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { Confirmation } from \"@webiny/app-admin/features/confirmation/abstractions.js\";\nimport { GetEntryUseCase } from \"~/features/contentEntry/getEntry/abstractions.js\";\nimport { CreateEntryUseCase } from \"~/features/contentEntry/createEntry/abstractions.js\";\nimport { UpdateEntryUseCase } from \"~/features/contentEntry/updateEntry/abstractions.js\";\nimport { PublishEntryUseCase } from \"~/features/contentEntry/publishEntry/abstractions.js\";\nimport { UnpublishEntryUseCase } from \"~/features/contentEntry/unpublishEntry/abstractions.js\";\nimport { DeleteEntryUseCase } from \"~/features/contentEntry/deleteEntry/abstractions.js\";\nimport { UpdateRevisionDescriptionUseCase } from \"~/features/contentEntry/updateRevisionDescription/abstractions.js\";\nimport { CreateRevisionFromUseCase } from \"~/features/contentEntry/createRevisionFrom/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport { ContentEntryFormPresenter as Abstraction } from \"./abstractions.js\";\nimport { ContentEntryFormPresenter } from \"./ContentEntryFormPresenter.js\";\n\nconst createFakeForm = (validation?: Promise<unknown>) => ({\n vm: {},\n isDirty: false,\n submit: vi.fn(async () => (validation ? validation : { title: \"Draft\" })),\n setData: vi.fn(),\n reset: vi.fn(),\n setErrors: vi.fn()\n});\n\n/** A promise the test settles by hand, so it can act while a save is still in flight. */\nconst deferred = <T>() => {\n let resolve!: (value: T) => void;\n let reject!: (reason: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n return { promise, resolve, reject };\n};\n\nconst createEntry = (entryId: string) => ({\n id: `${entryId}#0001`,\n entryId,\n meta: { locked: false, status: \"draft\", title: entryId },\n values: { title: entryId }\n});\n\nconst setup = () => {\n const forms: ReturnType<typeof createFakeForm>[] = [];\n const create = deferred<any>();\n const update = deferred<any>();\n /** When set, the next form built validates only once the test resolves this. */\n let nextValidation: Promise<unknown> | undefined;\n /** Entries whose load the test settles by hand. */\n const pendingLoads = new Map<string, Promise<any>>();\n\n const createExecute = vi.fn(() => create.promise);\n const updateExecute = vi.fn(() => update.promise);\n\n const container = new Container();\n container.registerInstance(FormModelFactory, {\n create: () => {\n const form = createFakeForm(nextValidation);\n nextValidation = undefined;\n forms.push(form);\n return form;\n }\n } as unknown as FormModelFactory.Interface);\n container.registerInstance(CmsFormModelBuilder, {\n build: () => ({})\n } as unknown as CmsFormModelBuilder.Interface);\n container.registerInstance(CmsModelContext, {\n getModel: () => ({ modelId: \"article\", fields: [] })\n } as unknown as CmsModelContext.Interface);\n container.registerInstance(CreateEntryUseCase, {\n execute: createExecute\n } as unknown as CreateEntryUseCase.Interface);\n container.registerInstance(UpdateEntryUseCase, {\n execute: updateExecute\n } as unknown as UpdateEntryUseCase.Interface);\n container.registerInstance(GetEntryUseCase, {\n execute: async ({ id }: { id: string }) => pendingLoads.get(id) ?? createEntry(id)\n } as unknown as GetEntryUseCase.Interface);\n\n // Not reached by the paths these tests exercise.\n for (const abstraction of [\n Confirmation,\n CreateRevisionFromUseCase,\n PublishEntryUseCase,\n UnpublishEntryUseCase,\n DeleteEntryUseCase,\n UpdateRevisionDescriptionUseCase\n ]) {\n container.registerInstance(abstraction as any, {});\n }\n container.register(ContentEntryFormPresenter);\n\n return {\n presenter: container.resolve(Abstraction),\n forms,\n create,\n createExecute,\n update,\n updateExecute,\n pendingLoads,\n slowValidation: () => {\n const validation = deferred<unknown>();\n nextValidation = validation.promise;\n return validation;\n }\n };\n};\n\ndescribe(\"ContentEntryFormPresenter.saveRevision\", () => {\n it(\"puts a rejected save's errors on the form\", async () => {\n const { presenter, forms, create } = setup();\n presenter.newEntry();\n\n const saving = presenter.saveRevision();\n create.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n expect(forms[0].setErrors).toHaveBeenCalledTimes(1);\n });\n\n it(\"returns false instead of throwing when the form was reset mid-save\", async () => {\n const { presenter, create, createExecute } = setup();\n presenter.newEntry();\n\n const saving = presenter.saveRevision();\n await vi.waitFor(() => expect(createExecute).toHaveBeenCalled());\n presenter.reset();\n create.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n expect(presenter.vm.loading).toBe(null);\n });\n\n it(\"leaves a form built after the save started alone\", async () => {\n const { presenter, forms, create, createExecute } = setup();\n presenter.newEntry();\n\n const saving = presenter.saveRevision();\n await vi.waitFor(() => expect(createExecute).toHaveBeenCalled());\n presenter.newEntry();\n create.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n expect(forms).toHaveLength(2);\n expect(forms[0].setErrors).not.toHaveBeenCalled();\n expect(forms[1].setErrors).not.toHaveBeenCalled();\n });\n\n it(\"saves the open entry and puts the saved values on the form\", async () => {\n const { presenter, forms, update, updateExecute } = setup();\n await presenter.loadRevision(\"A\");\n\n const saving = presenter.saveRevision();\n await vi.waitFor(() => expect(updateExecute).toHaveBeenCalled());\n update.resolve(createEntry(\"A\"));\n\n await expect(saving).resolves.toBe(true);\n expect(updateExecute).toHaveBeenCalledWith(\n expect.objectContaining({ revisionId: \"A#0001\", data: { values: { title: \"Draft\" } } })\n );\n expect(forms[0].setData).toHaveBeenLastCalledWith({ title: \"A\" });\n });\n\n it(\"does not write to an entry opened while the form was validating\", async () => {\n const { presenter, update, updateExecute, slowValidation } = setup();\n const validation = slowValidation();\n await presenter.loadRevision(\"A\");\n\n const saving = presenter.saveRevision();\n await presenter.loadRevision(\"B\");\n validation.resolve({ title: \"Values typed into A\" });\n // Settled up front, so a write that wrongly goes out finishes instead of hanging.\n update.resolve(createEntry(\"B\"));\n\n await expect(saving).resolves.toBe(false);\n expect(updateExecute).not.toHaveBeenCalled();\n expect(presenter.vm.entry?.entryId).toBe(\"B\");\n });\n\n it(\"keeps a write on its entry and reports false when another entry was opened\", async () => {\n const { presenter, forms, update, updateExecute } = setup();\n await presenter.loadRevision(\"A\");\n\n const saving = presenter.saveRevision();\n await vi.waitFor(() => expect(updateExecute).toHaveBeenCalled());\n await presenter.loadRevision(\"B\");\n update.resolve(createEntry(\"A\"));\n\n // False, so \"Save & Publish\" does not go on to publish B.\n await expect(saving).resolves.toBe(false);\n expect(updateExecute).toHaveBeenCalledTimes(1);\n expect(updateExecute).toHaveBeenCalledWith(\n expect.objectContaining({ revisionId: \"A#0001\" })\n );\n expect(presenter.vm.entry?.entryId).toBe(\"B\");\n expect(forms[1].setData).toHaveBeenCalledTimes(1);\n expect(forms[1].setData).toHaveBeenCalledWith({ title: \"B\" });\n });\n\n it(\"does not clear the loading message of an entry being opened\", async () => {\n const { presenter, update, updateExecute, pendingLoads } = setup();\n await presenter.loadRevision(\"A\");\n\n const saving = presenter.saveRevision();\n await vi.waitFor(() => expect(updateExecute).toHaveBeenCalled());\n\n const loadB = deferred<any>();\n pendingLoads.set(\"B\", loadB.promise);\n const loading = presenter.loadRevision(\"B\");\n update.resolve(createEntry(\"A\"));\n\n // A's form is still on screen, because B hasn't loaded yet.\n await expect(saving).resolves.toBe(true);\n expect(presenter.vm.loading).toBe(\"Loading entry...\");\n\n loadB.resolve(createEntry(\"B\"));\n await loading;\n expect(presenter.vm.loading).toBe(null);\n });\n});\n"],"names":["createFakeForm","validation","vi","deferred","resolve","reject","promise","Promise","res","rej","createEntry","entryId","setup","forms","create","update","nextValidation","pendingLoads","Map","createExecute","updateExecute","container","Container","FormModelFactory","form","undefined","CmsFormModelBuilder","CmsModelContext","CreateEntryUseCase","UpdateEntryUseCase","GetEntryUseCase","id","abstraction","Confirmation","CreateRevisionFromUseCase","PublishEntryUseCase","UnpublishEntryUseCase","DeleteEntryUseCase","UpdateRevisionDescriptionUseCase","ContentEntryFormPresenter","Abstraction","describe","it","presenter","saving","Error","expect","slowValidation","loadB","loading"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FormModel } from "@webiny/app-admin/features/formModel/abstractions.js";
|
|
2
|
+
import type { CmsModel } from "../../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Converts a rejected save into form errors: per-field validation errors from the API are
|
|
5
|
+
* placed on their fields, anything else is shown as a form-level error.
|
|
6
|
+
*/
|
|
7
|
+
export declare const toFormErrors: (error: unknown, model: CmsModel) => FormModel.FormError[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { CmsEntryError } from "../../../features/contentEntry/CmsEntryError.js";
|
|
2
|
+
const isApiFieldError = (value)=>{
|
|
3
|
+
const item = value;
|
|
4
|
+
return "string" == typeof item?.fieldId && "string" == typeof item.error;
|
|
5
|
+
};
|
|
6
|
+
const isIndex = (value)=>/^\d+$/.test(String(value));
|
|
7
|
+
const toFieldPath = (model, error)=>{
|
|
8
|
+
const parents = error.parents ?? [];
|
|
9
|
+
const path = [];
|
|
10
|
+
let fields = model.fields;
|
|
11
|
+
for(let i = 0; i < parents.length; i++){
|
|
12
|
+
const field = fields.find((f)=>f.fieldId === String(parents[i]));
|
|
13
|
+
if (!field) return [
|
|
14
|
+
...parents,
|
|
15
|
+
error.fieldId
|
|
16
|
+
].join(".");
|
|
17
|
+
path.push(field.fieldId);
|
|
18
|
+
if (field.list && isIndex(parents[i + 1])) path.push(String(parents[++i]));
|
|
19
|
+
if ("dynamicZone" === field.type) {
|
|
20
|
+
const templates = field.settings?.templates ?? [];
|
|
21
|
+
const template = templates.find((t)=>t.gqlTypeName === String(parents[i + 1]));
|
|
22
|
+
i++;
|
|
23
|
+
fields = template?.fields ?? [];
|
|
24
|
+
} else fields = field.settings?.fields ?? [];
|
|
25
|
+
}
|
|
26
|
+
return [
|
|
27
|
+
...path,
|
|
28
|
+
error.fieldId
|
|
29
|
+
].join(".");
|
|
30
|
+
};
|
|
31
|
+
const toFormErrors = (error, model)=>{
|
|
32
|
+
if (error instanceof CmsEntryError && Array.isArray(error.data)) {
|
|
33
|
+
const fieldErrors = error.data.filter(isApiFieldError);
|
|
34
|
+
if (fieldErrors.length > 0) return fieldErrors.map((item)=>({
|
|
35
|
+
path: toFieldPath(model, item),
|
|
36
|
+
message: item.error
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
const message = error instanceof Error ? error.message : "Could not save the entry.";
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
path: "",
|
|
43
|
+
message
|
|
44
|
+
}
|
|
45
|
+
];
|
|
46
|
+
};
|
|
47
|
+
export { toFormErrors };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=toFormErrors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/contentEntries/form/toFormErrors.js","sources":["../../../../src/presentation/contentEntries/form/toFormErrors.ts"],"sourcesContent":["import type { FormModel } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { CmsDynamicZoneTemplate, CmsModel, CmsModelField } from \"~/types.js\";\nimport { CmsEntryError } from \"~/features/contentEntry/CmsEntryError.js\";\n\ninterface ApiFieldError {\n fieldId: string;\n error: string;\n parents?: (string | number)[];\n}\n\nconst isApiFieldError = (value: unknown): value is ApiFieldError => {\n const item = value as ApiFieldError;\n return typeof item?.fieldId === \"string\" && typeof item.error === \"string\";\n};\n\nconst isIndex = (value: unknown): boolean => /^\\d+$/.test(String(value));\n\n/**\n * The API lists the parents of a nested field as `fieldId`, then the item index for list\n * fields, then the template's `gqlTypeName` for dynamic zones. Form paths have no template\n * segment, so walk the model to drop it: `blocks.0.PageHero.title` → `blocks.0.title`.\n */\nconst toFieldPath = (model: CmsModel, error: ApiFieldError): string => {\n const parents = error.parents ?? [];\n const path: string[] = [];\n let fields: CmsModelField[] = model.fields;\n\n for (let i = 0; i < parents.length; i++) {\n const field = fields.find(f => f.fieldId === String(parents[i]));\n if (!field) {\n return [...parents, error.fieldId].join(\".\");\n }\n path.push(field.fieldId);\n\n if (field.list && isIndex(parents[i + 1])) {\n path.push(String(parents[++i]));\n }\n\n if (field.type === \"dynamicZone\") {\n const templates = (field.settings?.templates ?? []) as CmsDynamicZoneTemplate[];\n const template = templates.find(t => t.gqlTypeName === String(parents[i + 1]));\n i++;\n fields = template?.fields ?? [];\n } else {\n fields = (field.settings?.fields ?? []) as CmsModelField[];\n }\n }\n\n return [...path, error.fieldId].join(\".\");\n};\n\n/**\n * Converts a rejected save into form errors: per-field validation errors from the API are\n * placed on their fields, anything else is shown as a form-level error.\n */\nexport const toFormErrors = (error: unknown, model: CmsModel): FormModel.FormError[] => {\n if (error instanceof CmsEntryError && Array.isArray(error.data)) {\n const fieldErrors = error.data.filter(isApiFieldError);\n if (fieldErrors.length > 0) {\n return fieldErrors.map(item => ({\n path: toFieldPath(model, item),\n message: item.error\n }));\n }\n }\n\n const message = error instanceof Error ? error.message : \"Could not save the entry.\";\n return [{ path: \"\", message }];\n};\n"],"names":["isApiFieldError","value","item","isIndex","String","toFieldPath","model","error","parents","path","fields","i","field","f","templates","template","t","toFormErrors","CmsEntryError","Array","fieldErrors","message","Error"],"mappings":";AAUA,MAAMA,kBAAkB,CAACC;IACrB,MAAMC,OAAOD;IACb,OAAO,AAAyB,YAAzB,OAAOC,MAAM,WAAwB,AAAsB,YAAtB,OAAOA,KAAK,KAAK;AACjE;AAEA,MAAMC,UAAU,CAACF,QAA4B,QAAQ,IAAI,CAACG,OAAOH;AAOjE,MAAMI,cAAc,CAACC,OAAiBC;IAClC,MAAMC,UAAUD,MAAM,OAAO,IAAI,EAAE;IACnC,MAAME,OAAiB,EAAE;IACzB,IAAIC,SAA0BJ,MAAM,MAAM;IAE1C,IAAK,IAAIK,IAAI,GAAGA,IAAIH,QAAQ,MAAM,EAAEG,IAAK;QACrC,MAAMC,QAAQF,OAAO,IAAI,CAACG,CAAAA,IAAKA,EAAE,OAAO,KAAKT,OAAOI,OAAO,CAACG,EAAE;QAC9D,IAAI,CAACC,OACD,OAAO;eAAIJ;YAASD,MAAM,OAAO;SAAC,CAAC,IAAI,CAAC;QAE5CE,KAAK,IAAI,CAACG,MAAM,OAAO;QAEvB,IAAIA,MAAM,IAAI,IAAIT,QAAQK,OAAO,CAACG,IAAI,EAAE,GACpCF,KAAK,IAAI,CAACL,OAAOI,OAAO,CAAC,EAAEG,EAAE;QAGjC,IAAIC,AAAe,kBAAfA,MAAM,IAAI,EAAoB;YAC9B,MAAME,YAAaF,MAAM,QAAQ,EAAE,aAAa,EAAE;YAClD,MAAMG,WAAWD,UAAU,IAAI,CAACE,CAAAA,IAAKA,EAAE,WAAW,KAAKZ,OAAOI,OAAO,CAACG,IAAI,EAAE;YAC5EA;YACAD,SAASK,UAAU,UAAU,EAAE;QACnC,OACIL,SAAUE,MAAM,QAAQ,EAAE,UAAU,EAAE;IAE9C;IAEA,OAAO;WAAIH;QAAMF,MAAM,OAAO;KAAC,CAAC,IAAI,CAAC;AACzC;AAMO,MAAMU,eAAe,CAACV,OAAgBD;IACzC,IAAIC,iBAAiBW,iBAAiBC,MAAM,OAAO,CAACZ,MAAM,IAAI,GAAG;QAC7D,MAAMa,cAAcb,MAAM,IAAI,CAAC,MAAM,CAACP;QACtC,IAAIoB,YAAY,MAAM,GAAG,GACrB,OAAOA,YAAY,GAAG,CAAClB,CAAAA,OAAS;gBAC5B,MAAMG,YAAYC,OAAOJ;gBACzB,SAASA,KAAK,KAAK;YACvB;IAER;IAEA,MAAMmB,UAAUd,iBAAiBe,QAAQf,MAAM,OAAO,GAAG;IACzD,OAAO;QAAC;YAAE,MAAM;YAAIc;QAAQ;KAAE;AAClC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { toFormErrors } from "./toFormErrors.js";
|
|
3
|
+
import { CmsEntryError } from "../../../features/contentEntry/CmsEntryError.js";
|
|
4
|
+
const field = (fieldId, type, extra = {})=>({
|
|
5
|
+
id: fieldId,
|
|
6
|
+
fieldId,
|
|
7
|
+
type,
|
|
8
|
+
label: fieldId,
|
|
9
|
+
...extra
|
|
10
|
+
});
|
|
11
|
+
const model = {
|
|
12
|
+
modelId: "page",
|
|
13
|
+
fields: [
|
|
14
|
+
field("sku", "text"),
|
|
15
|
+
field("variants", "object", {
|
|
16
|
+
list: true,
|
|
17
|
+
settings: {
|
|
18
|
+
fields: [
|
|
19
|
+
field("code", "text")
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}),
|
|
23
|
+
field("blocks", "dynamicZone", {
|
|
24
|
+
list: true,
|
|
25
|
+
settings: {
|
|
26
|
+
templates: [
|
|
27
|
+
{
|
|
28
|
+
id: "hero",
|
|
29
|
+
gqlTypeName: "PageHero",
|
|
30
|
+
fields: [
|
|
31
|
+
field("title", "text"),
|
|
32
|
+
field("links", "object", {
|
|
33
|
+
list: true,
|
|
34
|
+
settings: {
|
|
35
|
+
fields: [
|
|
36
|
+
field("url", "text")
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}),
|
|
45
|
+
field("single", "dynamicZone", {
|
|
46
|
+
settings: {
|
|
47
|
+
templates: [
|
|
48
|
+
{
|
|
49
|
+
id: "plain",
|
|
50
|
+
gqlTypeName: "PagePlain",
|
|
51
|
+
fields: [
|
|
52
|
+
field("ctrl", "text")
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
};
|
|
60
|
+
const validationError = (data)=>new CmsEntryError({
|
|
61
|
+
message: "Validation failed.",
|
|
62
|
+
code: "Cms/Entry/ValidationError",
|
|
63
|
+
data: data
|
|
64
|
+
}, "Could not update entry");
|
|
65
|
+
describe("toFormErrors", ()=>{
|
|
66
|
+
it("maps a top-level field error to its field", ()=>{
|
|
67
|
+
const error = validationError([
|
|
68
|
+
{
|
|
69
|
+
id: "sku",
|
|
70
|
+
fieldId: "sku",
|
|
71
|
+
error: "Value must be unique.",
|
|
72
|
+
parents: []
|
|
73
|
+
}
|
|
74
|
+
]);
|
|
75
|
+
expect(toFormErrors(error, model)).toEqual([
|
|
76
|
+
{
|
|
77
|
+
path: "sku",
|
|
78
|
+
message: "Value must be unique."
|
|
79
|
+
}
|
|
80
|
+
]);
|
|
81
|
+
});
|
|
82
|
+
it("keeps list item indexes in the path", ()=>{
|
|
83
|
+
const error = validationError([
|
|
84
|
+
{
|
|
85
|
+
fieldId: "code",
|
|
86
|
+
error: "Required.",
|
|
87
|
+
parents: [
|
|
88
|
+
"variants",
|
|
89
|
+
"1"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
]);
|
|
93
|
+
expect(toFormErrors(error, model)).toEqual([
|
|
94
|
+
{
|
|
95
|
+
path: "variants.1.code",
|
|
96
|
+
message: "Required."
|
|
97
|
+
}
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
it("drops dynamic zone template names from the path", ()=>{
|
|
101
|
+
const error = validationError([
|
|
102
|
+
{
|
|
103
|
+
fieldId: "title",
|
|
104
|
+
error: "Required.",
|
|
105
|
+
parents: [
|
|
106
|
+
"blocks",
|
|
107
|
+
"0",
|
|
108
|
+
"PageHero"
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
fieldId: "url",
|
|
113
|
+
error: "Invalid URL.",
|
|
114
|
+
parents: [
|
|
115
|
+
"blocks",
|
|
116
|
+
"2",
|
|
117
|
+
"PageHero",
|
|
118
|
+
"links",
|
|
119
|
+
"1"
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
fieldId: "ctrl",
|
|
124
|
+
error: "Required.",
|
|
125
|
+
parents: [
|
|
126
|
+
"single",
|
|
127
|
+
"PagePlain"
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
]);
|
|
131
|
+
expect(toFormErrors(error, model)).toEqual([
|
|
132
|
+
{
|
|
133
|
+
path: "blocks.0.title",
|
|
134
|
+
message: "Required."
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
path: "blocks.2.links.1.url",
|
|
138
|
+
message: "Invalid URL."
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
path: "single.ctrl",
|
|
142
|
+
message: "Required."
|
|
143
|
+
}
|
|
144
|
+
]);
|
|
145
|
+
});
|
|
146
|
+
it("falls back to the error message when there are no field errors", ()=>{
|
|
147
|
+
const error = new CmsEntryError({
|
|
148
|
+
message: "Entry is locked.",
|
|
149
|
+
code: "Cms/Entry/Locked",
|
|
150
|
+
data: null
|
|
151
|
+
}, "Could not update entry");
|
|
152
|
+
expect(toFormErrors(error, model)).toEqual([
|
|
153
|
+
{
|
|
154
|
+
path: "",
|
|
155
|
+
message: "Entry is locked."
|
|
156
|
+
}
|
|
157
|
+
]);
|
|
158
|
+
});
|
|
159
|
+
it("uses the message of any other error", ()=>{
|
|
160
|
+
expect(toFormErrors(new Error("Network error: Failed to fetch"), model)).toEqual([
|
|
161
|
+
{
|
|
162
|
+
path: "",
|
|
163
|
+
message: "Network error: Failed to fetch"
|
|
164
|
+
}
|
|
165
|
+
]);
|
|
166
|
+
expect(toFormErrors("boom", model)).toEqual([
|
|
167
|
+
{
|
|
168
|
+
path: "",
|
|
169
|
+
message: "Could not save the entry."
|
|
170
|
+
}
|
|
171
|
+
]);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
//# sourceMappingURL=toFormErrors.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/contentEntries/form/toFormErrors.test.js","sources":["../../../../src/presentation/contentEntries/form/toFormErrors.test.ts"],"sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport { toFormErrors } from \"./toFormErrors.js\";\nimport { CmsEntryError } from \"~/features/contentEntry/CmsEntryError.js\";\n\nconst field = (fieldId: string, type: string, extra: Record<string, unknown> = {}) => ({\n id: fieldId,\n fieldId,\n type,\n label: fieldId,\n ...extra\n});\n\nconst model = {\n modelId: \"page\",\n fields: [\n field(\"sku\", \"text\"),\n field(\"variants\", \"object\", {\n list: true,\n settings: { fields: [field(\"code\", \"text\")] }\n }),\n field(\"blocks\", \"dynamicZone\", {\n list: true,\n settings: {\n templates: [\n {\n id: \"hero\",\n gqlTypeName: \"PageHero\",\n fields: [\n field(\"title\", \"text\"),\n field(\"links\", \"object\", {\n list: true,\n settings: { fields: [field(\"url\", \"text\")] }\n })\n ]\n }\n ]\n }\n }),\n field(\"single\", \"dynamicZone\", {\n settings: {\n templates: [\n { id: \"plain\", gqlTypeName: \"PagePlain\", fields: [field(\"ctrl\", \"text\")] }\n ]\n }\n })\n ]\n} as any;\n\nconst validationError = (data: unknown[]) =>\n new CmsEntryError(\n { message: \"Validation failed.\", code: \"Cms/Entry/ValidationError\", data: data as any },\n \"Could not update entry\"\n );\n\ndescribe(\"toFormErrors\", () => {\n it(\"maps a top-level field error to its field\", () => {\n const error = validationError([\n { id: \"sku\", fieldId: \"sku\", error: \"Value must be unique.\", parents: [] }\n ]);\n\n expect(toFormErrors(error, model)).toEqual([\n { path: \"sku\", message: \"Value must be unique.\" }\n ]);\n });\n\n it(\"keeps list item indexes in the path\", () => {\n const error = validationError([\n { fieldId: \"code\", error: \"Required.\", parents: [\"variants\", \"1\"] }\n ]);\n\n expect(toFormErrors(error, model)).toEqual([\n { path: \"variants.1.code\", message: \"Required.\" }\n ]);\n });\n\n it(\"drops dynamic zone template names from the path\", () => {\n const error = validationError([\n { fieldId: \"title\", error: \"Required.\", parents: [\"blocks\", \"0\", \"PageHero\"] },\n {\n fieldId: \"url\",\n error: \"Invalid URL.\",\n parents: [\"blocks\", \"2\", \"PageHero\", \"links\", \"1\"]\n },\n { fieldId: \"ctrl\", error: \"Required.\", parents: [\"single\", \"PagePlain\"] }\n ]);\n\n expect(toFormErrors(error, model)).toEqual([\n { path: \"blocks.0.title\", message: \"Required.\" },\n { path: \"blocks.2.links.1.url\", message: \"Invalid URL.\" },\n { path: \"single.ctrl\", message: \"Required.\" }\n ]);\n });\n\n it(\"falls back to the error message when there are no field errors\", () => {\n const error = new CmsEntryError(\n { message: \"Entry is locked.\", code: \"Cms/Entry/Locked\", data: null },\n \"Could not update entry\"\n );\n\n expect(toFormErrors(error, model)).toEqual([{ path: \"\", message: \"Entry is locked.\" }]);\n });\n\n it(\"uses the message of any other error\", () => {\n expect(toFormErrors(new Error(\"Network error: Failed to fetch\"), model)).toEqual([\n { path: \"\", message: \"Network error: Failed to fetch\" }\n ]);\n expect(toFormErrors(\"boom\", model)).toEqual([\n { path: \"\", message: \"Could not save the entry.\" }\n ]);\n });\n});\n"],"names":["field","fieldId","type","extra","model","validationError","data","CmsEntryError","describe","it","error","expect","toFormErrors","Error"],"mappings":";;;AAIA,MAAMA,QAAQ,CAACC,SAAiBC,MAAcC,QAAiC,CAAC,CAAC,GAAM;QACnF,IAAIF;QACJA;QACAC;QACA,OAAOD;QACP,GAAGE,KAAK;IACZ;AAEA,MAAMC,QAAQ;IACV,SAAS;IACT,QAAQ;QACJJ,MAAM,OAAO;QACbA,MAAM,YAAY,UAAU;YACxB,MAAM;YACN,UAAU;gBAAE,QAAQ;oBAACA,MAAM,QAAQ;iBAAQ;YAAC;QAChD;QACAA,MAAM,UAAU,eAAe;YAC3B,MAAM;YACN,UAAU;gBACN,WAAW;oBACP;wBACI,IAAI;wBACJ,aAAa;wBACb,QAAQ;4BACJA,MAAM,SAAS;4BACfA,MAAM,SAAS,UAAU;gCACrB,MAAM;gCACN,UAAU;oCAAE,QAAQ;wCAACA,MAAM,OAAO;qCAAQ;gCAAC;4BAC/C;yBACH;oBACL;iBACH;YACL;QACJ;QACAA,MAAM,UAAU,eAAe;YAC3B,UAAU;gBACN,WAAW;oBACP;wBAAE,IAAI;wBAAS,aAAa;wBAAa,QAAQ;4BAACA,MAAM,QAAQ;yBAAQ;oBAAC;iBAC5E;YACL;QACJ;KACH;AACL;AAEA,MAAMK,kBAAkB,CAACC,OACrB,IAAIC,cACA;QAAE,SAAS;QAAsB,MAAM;QAA6B,MAAMD;IAAY,GACtF;AAGRE,SAAS,gBAAgB;IACrBC,GAAG,6CAA6C;QAC5C,MAAMC,QAAQL,gBAAgB;YAC1B;gBAAE,IAAI;gBAAO,SAAS;gBAAO,OAAO;gBAAyB,SAAS,EAAE;YAAC;SAC5E;QAEDM,OAAOC,aAAaF,OAAON,QAAQ,OAAO,CAAC;YACvC;gBAAE,MAAM;gBAAO,SAAS;YAAwB;SACnD;IACL;IAEAK,GAAG,uCAAuC;QACtC,MAAMC,QAAQL,gBAAgB;YAC1B;gBAAE,SAAS;gBAAQ,OAAO;gBAAa,SAAS;oBAAC;oBAAY;iBAAI;YAAC;SACrE;QAEDM,OAAOC,aAAaF,OAAON,QAAQ,OAAO,CAAC;YACvC;gBAAE,MAAM;gBAAmB,SAAS;YAAY;SACnD;IACL;IAEAK,GAAG,mDAAmD;QAClD,MAAMC,QAAQL,gBAAgB;YAC1B;gBAAE,SAAS;gBAAS,OAAO;gBAAa,SAAS;oBAAC;oBAAU;oBAAK;iBAAW;YAAC;YAC7E;gBACI,SAAS;gBACT,OAAO;gBACP,SAAS;oBAAC;oBAAU;oBAAK;oBAAY;oBAAS;iBAAI;YACtD;YACA;gBAAE,SAAS;gBAAQ,OAAO;gBAAa,SAAS;oBAAC;oBAAU;iBAAY;YAAC;SAC3E;QAEDM,OAAOC,aAAaF,OAAON,QAAQ,OAAO,CAAC;YACvC;gBAAE,MAAM;gBAAkB,SAAS;YAAY;YAC/C;gBAAE,MAAM;gBAAwB,SAAS;YAAe;YACxD;gBAAE,MAAM;gBAAe,SAAS;YAAY;SAC/C;IACL;IAEAK,GAAG,kEAAkE;QACjE,MAAMC,QAAQ,IAAIH,cACd;YAAE,SAAS;YAAoB,MAAM;YAAoB,MAAM;QAAK,GACpE;QAGJI,OAAOC,aAAaF,OAAON,QAAQ,OAAO,CAAC;YAAC;gBAAE,MAAM;gBAAI,SAAS;YAAmB;SAAE;IAC1F;IAEAK,GAAG,uCAAuC;QACtCE,OAAOC,aAAa,IAAIC,MAAM,mCAAmCT,QAAQ,OAAO,CAAC;YAC7E;gBAAE,MAAM;gBAAI,SAAS;YAAiC;SACzD;QACDO,OAAOC,aAAa,QAAQR,QAAQ,OAAO,CAAC;YACxC;gBAAE,MAAM;gBAAI,SAAS;YAA4B;SACpD;IACL;AACJ"}
|
|
@@ -8,12 +8,14 @@ import { DeleteEntryUseCase } from "../../../features/contentEntry/deleteEntry/a
|
|
|
8
8
|
import { PublishEntryUseCase } from "../../../features/contentEntry/publishEntry/abstractions.js";
|
|
9
9
|
import { UnpublishEntryUseCase } from "../../../features/contentEntry/unpublishEntry/abstractions.js";
|
|
10
10
|
import { MoveEntryUseCase } from "../../../features/contentEntry/moveEntry/abstractions.js";
|
|
11
|
+
import { DuplicateEntryUseCase } from "../../../features/contentEntry/duplicateEntry/abstractions.js";
|
|
11
12
|
import { UpdateRevisionDescriptionUseCase } from "../../../features/contentEntry/updateRevisionDescription/abstractions.js";
|
|
12
13
|
import { ContentEntriesCacheProvider, CmsModelContext } from "../../../features/contentEntry/abstractions.js";
|
|
13
|
-
import { ContentEntriesPresenter as Abstraction, type IContentEntriesViewModel } from "./abstractions.js";
|
|
14
|
+
import { ContentEntriesPresenter as Abstraction, type IContentEntriesViewModel, type IDuplicateEntryResult } from "./abstractions.js";
|
|
14
15
|
export declare const TRASH_ENTRY_DIALOG = "trash-entry";
|
|
15
16
|
export declare const PUBLISH_ENTRY_DIALOG = "publish-entry";
|
|
16
17
|
export declare const UNPUBLISH_ENTRY_DIALOG = "unpublish-entry";
|
|
18
|
+
export declare const DUPLICATE_ENTRY_DIALOG = "duplicate-entry";
|
|
17
19
|
declare class ContentEntriesPresenterImpl implements Abstraction.Interface {
|
|
18
20
|
private listPresenter;
|
|
19
21
|
private foldersPresenter;
|
|
@@ -24,12 +26,13 @@ declare class ContentEntriesPresenterImpl implements Abstraction.Interface {
|
|
|
24
26
|
private publishEntryUseCase;
|
|
25
27
|
private unpublishEntryUseCase;
|
|
26
28
|
private moveEntryUseCase;
|
|
29
|
+
private duplicateEntryUseCase;
|
|
27
30
|
private updateRevisionDescriptionUseCase;
|
|
28
31
|
private cacheProvider;
|
|
29
32
|
private getDescendantFoldersUseCase;
|
|
30
33
|
private selectedEntryId;
|
|
31
34
|
private disposeReaction;
|
|
32
|
-
constructor(listPresenter: ListPresenter.Interface<CmsContentEntry>, foldersPresenter: FolderTreePresenter.Interface, confirmation: Confirmation.Interface, modelAccessor: CmsModelContext.Interface, listEntriesUseCase: ListEntriesUseCase.Interface, deleteEntryUseCase: DeleteEntryUseCase.Interface, publishEntryUseCase: PublishEntryUseCase.Interface, unpublishEntryUseCase: UnpublishEntryUseCase.Interface, moveEntryUseCase: MoveEntryUseCase.Interface, updateRevisionDescriptionUseCase: UpdateRevisionDescriptionUseCase.Interface, cacheProvider: ContentEntriesCacheProvider.Interface, getDescendantFoldersUseCase: GetDescendantFoldersUseCase.Interface);
|
|
35
|
+
constructor(listPresenter: ListPresenter.Interface<CmsContentEntry>, foldersPresenter: FolderTreePresenter.Interface, confirmation: Confirmation.Interface, modelAccessor: CmsModelContext.Interface, listEntriesUseCase: ListEntriesUseCase.Interface, deleteEntryUseCase: DeleteEntryUseCase.Interface, publishEntryUseCase: PublishEntryUseCase.Interface, unpublishEntryUseCase: UnpublishEntryUseCase.Interface, moveEntryUseCase: MoveEntryUseCase.Interface, duplicateEntryUseCase: DuplicateEntryUseCase.Interface, updateRevisionDescriptionUseCase: UpdateRevisionDescriptionUseCase.Interface, cacheProvider: ContentEntriesCacheProvider.Interface, getDescendantFoldersUseCase: GetDescendantFoldersUseCase.Interface);
|
|
33
36
|
private get model();
|
|
34
37
|
get vm(): IContentEntriesViewModel;
|
|
35
38
|
init(config?: Abstraction.InitConfig): void;
|
|
@@ -42,6 +45,7 @@ declare class ContentEntriesPresenterImpl implements Abstraction.Interface {
|
|
|
42
45
|
publishEntry(id: string): Promise<boolean>;
|
|
43
46
|
unpublishEntry(id: string): Promise<boolean>;
|
|
44
47
|
moveEntry(id: string, folderId: string): Promise<boolean>;
|
|
48
|
+
duplicateEntry(entry: CmsContentEntry): Promise<IDuplicateEntryResult | false>;
|
|
45
49
|
dispose(): void;
|
|
46
50
|
}
|
|
47
51
|
export declare const ContentEntriesPresenter: typeof ContentEntriesPresenterImpl & {
|
|
@@ -9,6 +9,7 @@ import { DeleteEntryUseCase } from "../../../features/contentEntry/deleteEntry/a
|
|
|
9
9
|
import { PublishEntryUseCase } from "../../../features/contentEntry/publishEntry/abstractions.js";
|
|
10
10
|
import { UnpublishEntryUseCase } from "../../../features/contentEntry/unpublishEntry/abstractions.js";
|
|
11
11
|
import { MoveEntryUseCase } from "../../../features/contentEntry/moveEntry/abstractions.js";
|
|
12
|
+
import { DuplicateEntryUseCase } from "../../../features/contentEntry/duplicateEntry/abstractions.js";
|
|
12
13
|
import { UpdateRevisionDescriptionUseCase } from "../../../features/contentEntry/updateRevisionDescription/abstractions.js";
|
|
13
14
|
import { CmsModelContext, ContentEntriesCacheProvider } from "../../../features/contentEntry/abstractions.js";
|
|
14
15
|
import { ContentEntriesPresenter } from "./abstractions.js";
|
|
@@ -16,8 +17,9 @@ import { ContentEntriesDataSource } from "./ContentEntriesDataSource.js";
|
|
|
16
17
|
const TRASH_ENTRY_DIALOG = "trash-entry";
|
|
17
18
|
const PUBLISH_ENTRY_DIALOG = "publish-entry";
|
|
18
19
|
const UNPUBLISH_ENTRY_DIALOG = "unpublish-entry";
|
|
20
|
+
const DUPLICATE_ENTRY_DIALOG = "duplicate-entry";
|
|
19
21
|
class ContentEntriesPresenterImpl {
|
|
20
|
-
constructor(listPresenter, foldersPresenter, confirmation, modelAccessor, listEntriesUseCase, deleteEntryUseCase, publishEntryUseCase, unpublishEntryUseCase, moveEntryUseCase, updateRevisionDescriptionUseCase, cacheProvider, getDescendantFoldersUseCase){
|
|
22
|
+
constructor(listPresenter, foldersPresenter, confirmation, modelAccessor, listEntriesUseCase, deleteEntryUseCase, publishEntryUseCase, unpublishEntryUseCase, moveEntryUseCase, duplicateEntryUseCase, updateRevisionDescriptionUseCase, cacheProvider, getDescendantFoldersUseCase){
|
|
21
23
|
this.listPresenter = listPresenter;
|
|
22
24
|
this.foldersPresenter = foldersPresenter;
|
|
23
25
|
this.confirmation = confirmation;
|
|
@@ -27,6 +29,7 @@ class ContentEntriesPresenterImpl {
|
|
|
27
29
|
this.publishEntryUseCase = publishEntryUseCase;
|
|
28
30
|
this.unpublishEntryUseCase = unpublishEntryUseCase;
|
|
29
31
|
this.moveEntryUseCase = moveEntryUseCase;
|
|
32
|
+
this.duplicateEntryUseCase = duplicateEntryUseCase;
|
|
30
33
|
this.updateRevisionDescriptionUseCase = updateRevisionDescriptionUseCase;
|
|
31
34
|
this.cacheProvider = cacheProvider;
|
|
32
35
|
this.getDescendantFoldersUseCase = getDescendantFoldersUseCase;
|
|
@@ -41,6 +44,7 @@ class ContentEntriesPresenterImpl {
|
|
|
41
44
|
publishEntryUseCase: false,
|
|
42
45
|
unpublishEntryUseCase: false,
|
|
43
46
|
moveEntryUseCase: false,
|
|
47
|
+
duplicateEntryUseCase: false,
|
|
44
48
|
updateRevisionDescriptionUseCase: false,
|
|
45
49
|
cacheProvider: false,
|
|
46
50
|
getDescendantFoldersUseCase: false,
|
|
@@ -153,6 +157,26 @@ class ContentEntriesPresenterImpl {
|
|
|
153
157
|
});
|
|
154
158
|
return true;
|
|
155
159
|
}
|
|
160
|
+
async duplicateEntry(entry) {
|
|
161
|
+
const model = this.model;
|
|
162
|
+
return this.confirmation.confirm(DUPLICATE_ENTRY_DIALOG, {
|
|
163
|
+
entry
|
|
164
|
+
}, async ()=>{
|
|
165
|
+
try {
|
|
166
|
+
const duplicate = await this.duplicateEntryUseCase.execute({
|
|
167
|
+
model,
|
|
168
|
+
revisionId: entry.id
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
entry: duplicate
|
|
172
|
+
};
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return {
|
|
175
|
+
error: error
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
156
180
|
dispose() {
|
|
157
181
|
if (this.disposeReaction) {
|
|
158
182
|
this.disposeReaction();
|
|
@@ -172,11 +196,12 @@ const ContentEntriesPresenter_ContentEntriesPresenter = ContentEntriesPresenter.
|
|
|
172
196
|
PublishEntryUseCase,
|
|
173
197
|
UnpublishEntryUseCase,
|
|
174
198
|
MoveEntryUseCase,
|
|
199
|
+
DuplicateEntryUseCase,
|
|
175
200
|
UpdateRevisionDescriptionUseCase,
|
|
176
201
|
ContentEntriesCacheProvider,
|
|
177
202
|
GetDescendantFoldersUseCase
|
|
178
203
|
]
|
|
179
204
|
});
|
|
180
|
-
export { ContentEntriesPresenter_ContentEntriesPresenter as ContentEntriesPresenter, PUBLISH_ENTRY_DIALOG, TRASH_ENTRY_DIALOG, UNPUBLISH_ENTRY_DIALOG };
|
|
205
|
+
export { ContentEntriesPresenter_ContentEntriesPresenter as ContentEntriesPresenter, DUPLICATE_ENTRY_DIALOG, PUBLISH_ENTRY_DIALOG, TRASH_ENTRY_DIALOG, UNPUBLISH_ENTRY_DIALOG };
|
|
181
206
|
|
|
182
207
|
//# sourceMappingURL=ContentEntriesPresenter.js.map
|